Version 24 of Brainfuck

Updated 2016-01-11 18:47:29 by dbohdan

I don't think it qualifies as an actual language, but it is so interesting it does deserve a wiki page. Learn more about it here:

http://www.muppetlabs.com/~breadbox/bf/

and

http://cydathria.com/bf/brainfuck.html

not to mention

http://www.hevanet.com/cristofd/brainfuck/brainfuck.html

Also lo and behold the classic "99 Bottles of Beer" written in brainfuck:

http://99-bottles-of-beer.ls-la.net/b.html#Brainfuck

Here's a random number generator:

 >>>++[<++++++++[<[<++>-]>>[>>]+>>+[-[->>+<<<[<[<<]<+>]>[>[>>]]]<[>>[-]]>[>[-
 <<]>[<+<]]+<<]<[>+<-]>>-]<.[-]>>] http://www.hevanet.com/cristofd/brainfuck/

And a brainfuck interpreter written in Tcl (you can type brainfuck — or a close approximation — directly into a tcl interpreter):

http://www.fishpool.com/~setok/proj/tclbf/

PT writes: And here is a BrainFuck interpreter as an Internet Explorer plugin language — http://brainscript.sourceforge.net/

And BF is a real language — it's Turing complete after all.

Now Malbolge on the other hand....

Another amusing language: Beatnik

AM (18 may 2009) And then there is Intercal ... http://en.wikipedia.org/wiki/Intercal - mentioned several times on this Wiki.

Interpreter

RHS A brainfuck interpreter written in Tcl... can take input as a filename, or from stdin

#!/bin/sh
# This line continues for Tcl, but is a single line for 'sh' \
exec tclsh8.5 "$0" ${1+"$@"}

proc getp {} {
    global pc program
    lindex $program $pc
}
proc getd {} {
    global xc data
    while {[llength $data]-1 < $xc} {
        lappend data 0
    }
    lindex $data $xc
}
proc setd {c} {
    global xc data
    while {[llength $data]-1 < $xc} {
        lappend data 0
    }
    lset data $xc $c
}

proc main {commands} {
    global program data pc xc

    set program [split $commands ""]
    set plen [llength $program]
    set data {0}
    set xc 0

    for {set pc 0} {$pc < $plen} {incr pc} {
        switch [lindex $program $pc] {
            > {
                incr xc
            }
            < {
                incr xc -1
            }
            + {
                setd [expr {[getd] + 1}]
            }
            - {
                setd [expr {[getd] - 1}]
            }
            . {
                puts -nonewline [format "%c" [getd]]
            }
            , { 
                if {![eof stdin]} {
                    scan [read stdin 1] "%c" var
                    setd $var
                } else {
                    setd 0
                }
            }

            \[ {
                if {[getd] == 0} {
                    incr pc
                    set nest 0
                    while {$nest || [getp] ne "\]"} {
                        switch [getp] {
                            \[ {incr nest}
                            \] {incr nest -1}
                        }
                        incr pc
                    }
                }
            }
            \] {
                if {[getd] != 0} {
                    incr pc -1
                    set nest 0
                    while {$nest || [getp] ne "\["} {
                        switch [getp] {
                            \[ {incr nest -1}
                            \] {incr nest}
                        }
                        incr pc -1
                    }
                }
            }
            \# {
                # Purely for debugging
                puts -nonewline "\nDebug: "
                for {set tmp 0} {$tmp < 10} {incr tmp} {
                    puts -nonewline "[lindex $data $tmp]:"
                }
                puts ""
            }
        }
    }
}

proc readfile {args} {
    switch [llength $args] {
        0 {
            set fd stdin
        }
        1 {
            set fd [open [lindex $args 0]]
        }
        default {
            puts stderr "Usage: [file tail [info script]] ?filename?"
            exit 1
        }
    }
    set text [read $fd]
    if {$fd ne "stdin"} {
        close $fd
    }
    return $text
}

fconfigure stdout -buffering none
fconfigure stdin -buffering none

set commands [readfile {*}$argv]
main $commands

puts ""
[The version at Rosetta Code is derived from this one.]

PT You could try the following program with the above interpreter...

 =
 =  badger badger mushroom snake
 =
 
 >>++++[<+++++[<+++++>-]>-]<<---[>>+>+>+>+>+>+<<<<<<<-]
 >>+     b
 >       a
 >+++    d
 >++++++ g
 >++++   e
 >>++++[<++++>-]<+ r
 
 <<<<<<<
 >++++[<++++>-]<
 [>+++++[>.>.>.>.>.>.<<<<<<-]<-]
 
 mushroom
 ++++[>>+++>+++>+++>>+++<<<<<<-]>>->++++++++>+++>+>+>
 [>>>>+<<<<-]
 >>>>[<+<+<+<+>>>>-]
 <<-----<---<---<
 <<<<<<
 print
 ++[>>.>.>.>.>.>.>.>.<<<<<<<<<-]
 
 snake!
 >>>--.-----.>>>[-]>[-]<<[>+>+<<-]>-------.>+++.<++++.
 <<<[-]>[-]++++++[<+++++>-]<+++.

Brainfuck-to-Tcl transpiler

dbohdan 2016-02-11: The following module leverages the Tcl interpreter itself to interpret Brainfuck. It was originally developed to compete in a cross-language microbenchmark. I measured its performance when run with Tcl 8.6.4 on x86_64 Linux.

The translate command translates Brainfuck instructions into Tcl code, which is then run with apply (to ensure bytecode compilation). This is up to several times faster than interpreting the code directly, e.g., when running mandel.b , and I find it more idiomatic. Storing the tape data in dictionaries resulted in better performance than arrays, lists or dynamically named variables. Compressing repeated + and - into dict incr tape $pos ±N where N is the number of times the instruction is repeated noticeably improved the performance. Compressing < and >, which change a value not stored in a dictionary, had minimal impact.

package require Tcl 8.5

namespace eval ::brainfuck {
    variable verson 0.2.1
    variable debug 0

    proc emit code {
        upvar 1 transl transl
        foreach line [split [string trim $code] \n] {
            append transl [string trimleft $line]\n
        }
    }

    proc translate source {
        set transl {}
        emit {
            if {![info exists tape] || ($tape eq {})} {
                for {set i 0} {$i < 30000} {incr i} {
                    dict set tape $i 0
                }
            }
            if {![info exists pos] || ($pos eq {})} {
                set pos 0
            }
        }

        set commands [split $source {}]
        for {set i 0} {$i < [llength $commands]} {incr i} {
            switch -exact -- [lindex $commands $i] {
                [ {
                    emit [format %s\{ {if {[dict get $tape $pos] != 0} }]
                    emit "while 1 \{"
                }
                ] {
                    emit {if {[dict get $tape $pos] == 0} {break}}
                    emit \}
                    emit \}
                }
                > -
                < {
                    # Compress repeated Brainfuck instructions into a single Tcl
                    # command.
                    if {[lindex $commands $i] eq {<}} {
                        set op <
                        set sign -
                    } else {
                        set op >
                        set sign +
                    }

                    set count 0
                    while {[lindex $commands $i] eq $op} {
                        incr count
                        incr i
                    }
                    incr i -1

                    emit [format {incr pos %s%d} $sign $count]
                }
                + -
                - {
                    if {[lindex $commands $i] eq {+}} {
                        set op +
                    } else {
                        set op -
                    }

                    set count 0
                    while {[lindex $commands $i] eq $op} {
                        incr count
                        incr i
                    }
                    incr i -1

                    emit [format {dict incr tape $pos %s%d} $op $count]
                }
                . {
                    emit {
                        puts -nonewline [format %c [dict get $tape $pos]]
                    }
                }
                , {
                    # Noncompliant implementation.
                    emit {
                        set input [read stdin 1]
                        dict set tape $pos [scan $input %c]
                    }
                }
                default {
                    # Ignore.
                }
            }
        }
        emit {
            return [list $tape $pos]
        }
        return $transl
    }

    proc interpret source {
        variable debug

        set translated [translate $source]
        if {$debug} {
            puts $translated
        }
        set result [apply [list {{tape {}} {pos {}}} $translated]]
        return $result
    }
}

proc main filename {
    set ch [open $filename r]
    set source [read $ch]
    close $ch

    fconfigure stdout -buffering none

    ::brainfuck::interpret $source
}

main [lindex $argv 0]