From 0a78720644e45bd27c4a57cbddf2d32aacb555c3 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Wed, 29 Jun 2022 00:59:59 -0500 Subject: Probably version ... whatever the next higher one is --- examples/add2.bf | 27 ++++ examples/hello-world.bf | 45 ++++++ examples/helloworld.bf | 1 - examples/helloworld.trainfuck | 152 -------------------- examples/rot13.bf | 32 +++++ fucktrain | 4 +- trainfuck | 123 +--------------- trainfuck.awk | 321 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 433 insertions(+), 272 deletions(-) create mode 100644 examples/add2.bf create mode 100644 examples/hello-world.bf delete mode 100644 examples/helloworld.bf delete mode 100644 examples/helloworld.trainfuck create mode 100644 examples/rot13.bf create mode 100755 trainfuck.awk diff --git a/examples/add2.bf b/examples/add2.bf new file mode 100644 index 0000000..42361db --- /dev/null +++ b/examples/add2.bf @@ -0,0 +1,27 @@ +[ Add two values +from https://en.wikipedia.org/wiki/Brainfuck#Adding_two_values +] + +++ Cell c0 = 2 +> +++++ Cell c1 = 5 + +[ Start your loops with your cell pointer on the loop counter (c1 in our case) +< + Add 1 to c0 +> - Subtract 1 from c1 +] End your loops with the cell pointer on the loop counter + +At this point our program has added 5 to 2 leaving 7 in c0 and 0 in c1 +but we cannot output this value to the terminal since it is not ASCII encoded + +To display the ASCII character "7" we must add 48 to the value 7 +We use a loop to compute 48 = 6 * 8 + +++++ ++++ c1 = 8 and this will be our loop counter again +[ +< +++ +++ Add 6 to c0 +> - Subtract 1 from c1 +] +< . Print out c0 which has the value 55 which translates to "7"! + +Finally print a newline: +---------------------------------------------. diff --git a/examples/hello-world.bf b/examples/hello-world.bf new file mode 100644 index 0000000..dce24e2 --- /dev/null +++ b/examples/hello-world.bf @@ -0,0 +1,45 @@ +[ This program prints "Hello World!" and a newline to the screen, its +length is 106 active command characters. [It is not the shortest.] + +This loop is an "initial comment loop", a simple way of adding a comment +to a BF program such that you don't have to worry about any command +characters. Any ".", ",", "+", "-", "<" and ">" characters are simply +ignored, the "[" and "]" characters just have to be balanced. This +loop and the commands it contains are ignored because the current cell +defaults to a value of 0; the 0 value causes this loop to be skipped. + +from https://en.wikipedia.org/wiki/Brainfuck#Hello_World! +] +++++++++ Set Cell #0 to 8 +[ +>++++ Add 4 to Cell #1; this will always set Cell #1 to 4 +[ as the cell will be cleared by the loop +>++ Add 2 to Cell #2 +>+++ Add 3 to Cell #3 +>+++ Add 3 to Cell #4 +>+ Add 1 to Cell #5 +<<<<- Decrement the loop counter in Cell #1 +] Loop until Cell #1 is zero; number of iterations is 4 +>+ Add 1 to Cell #2 +>+ Add 1 to Cell #3 +>- Subtract 1 from Cell #4 +>>+ Add 1 to Cell #6 +[<] Move back to the first zero cell you find; this will +be Cell #1 which was cleared by the previous loop +<- Decrement the loop Counter in Cell #0 +] Loop until Cell #0 is zero; number of iterations is 8 + +The result of this is: +Cell no : 0 1 2 3 4 5 6 +Contents: 0 0 72 104 88 32 8 +Pointer : ^ + +>>. Cell #2 has value 72 which is 'H' +>---. Subtract 3 from Cell #3 to get 101 which is 'e' ++++++++..+++. Likewise for 'llo' from Cell #3 +>>. Cell #5 is 32 for the space +<-. Subtract 1 from Cell #4 for 87 to give a 'W' +<. Cell #3 was set to 'o' from the end of 'Hello' ++++.------.--------. Cell #3 for 'rl' and 'd' +>>+. Add 1 to Cell #5 gives us an exclamation point +>++. And finally a newline from Cell #6 diff --git a/examples/helloworld.bf b/examples/helloworld.bf deleted file mode 100644 index 8fa0f72..0000000 --- a/examples/helloworld.bf +++ /dev/null @@ -1 +0,0 @@ -++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++. diff --git a/examples/helloworld.trainfuck b/examples/helloworld.trainfuck deleted file mode 100644 index 1f68f11..0000000 --- a/examples/helloworld.trainfuck +++ /dev/null @@ -1,152 +0,0 @@ -TRAINFUCK BABEEE -CHOO CHOO MUTHERFUCKER - -This example adapted from https://en.wikipedia.org/wiki/Brainfuck - -This program prints "Hello World!" and a newline to the screen, its -length is 106 active command characters. - -The program doesn't start until we have ALL ABOARD on a line by itself, so -we've got one better than an initial comment loop. - -This file was handwritten, but soon(TM) we'll have a fucktrain command, -that'll translate to trainfuck from brainfuck. - -ALL ABOARD -chug chug chug chug chug chug chug chug -END OF THE LINE - Set Cell #0 to 8 - (Also note: you can have comments betwen END OF THE LINE and ALL ABOARD) -ALL ABOARD -tickets please -choo -chug chug chug chug -END OF THE LINE - Add 4 to Cell #1; this will always set Cell #1 to 4 -ALL ABOARD -tickets please -END OF THE LINE - as the cell will be cleared by the loop -ALL ABOARD -choo -chug chug -END OF THE LINE - Add 2 to Cell #2 -ALL ABOARD -choo -chug chug chug -END OF THE LINE - Add 3 to Cell #3 -ALL ABOARD -choo -chug chug chug -END OF THE LINE - Add 3 to Cell #4 -ALL ABOARD -choo -chug -END OF THE LINE - Add 1 to Cell #5 -ALL ABOARD -choo choo -choo choo -choo choo -choo choo -chugga -END OF THE LINE - Decrement the loop counter in Cell #1 -ALL ABOARD -your ticket please -END OF THE LINE - Loop till Cell #1 is zero; number of iterations is 4 -ALL ABOARD -choo -chug -END OF THE LINE - Add 1 to Cell #2 -ALL ABOARD -choo -chug -END OF THE LINE - Add 1 to Cell #3 -ALL ABOARD -choo -chugga -END OF THE LINE - Subtract 1 from Cell #4 -ALL ABOARD -choo -choo -chug -END OF THE LINE - Add 1 to Cell #6 -ALL ABOARD -tickets pleasechoo choo -your ticket please -END OF THE LINE - Move back to the first zero cell you find; this will -ALL ABOARD - -END OF THE LINE - be Cell #1 which was cleared by the previous loop -ALL ABOARD -choo choo -chugga -END OF THE LINE - Decrement the loop Counter in Cell #0 -ALL ABOARD -your ticket please -END OF THE LINE - Loop till Cell #0 is zero; number of iterations is 8 -ALL ABOARD -END OF THE LINE -The result of this is: -Cell No : 0 1 2 3 4 5 6 -Contents: 0 0 72 104 88 32 8 -Pointer : ^ - -ALL ABOARD -choo -choo -click -END OF THE LINE - Cell #2 has value 72 which is 'H' -ALL ABOARD -choo -chugga chugga chugga click -END OF THE LINE - Subtract 3 from Cell #3 to get 101 which is 'e' -ALL ABOARD -chug chug chug chug chug chug chug click click chug chug chug click -END OF THE LINE - Likewise for 'llo' from Cell #3 -ALL ABOARD -choo -choo -click -END OF THE LINE - Cell #5 is 32 for the space -ALL ABOARD -choo choo -chugga click -END OF THE LINE - Subtract 1 from Cell #4 for 87 to give a 'W' -ALL ABOARD -choo choo -click -END OF THE LINE - Cell #3 was set to 'o' from the end of 'Hello' -ALL ABOARD -chug chug chug click chugga chugga chugga chugga chugga chugga click chugga chugga chugga chugga chugga chugga chugga chugga click -END OF THE LINE - Cell #3 for 'rl' and 'd' -ALL ABOARD -choo -choo -chug click -END OF THE LINE - Add 1 to Cell #5 gives us an exclamation point -ALL ABOARD -choo -chug chug click -END OF THE LINE diff --git a/examples/rot13.bf b/examples/rot13.bf new file mode 100644 index 0000000..bfcde73 --- /dev/null +++ b/examples/rot13.bf @@ -0,0 +1,32 @@ +[ ROT13 in Brainfuck +from https://en.wikipedia.org/wiki/Brainfuck#ROT13 +] + +-,+[ Read first character and start outer character reading loop +-[ Skip forward if character is 0 +>>++++[>++++++++<-] Set up divisor (32) for division loop +(MEMORY LAYOUT: dividend copy remainder divisor quotient zero zero) +<+<-[ Set up dividend (x minus 1) and enter division loop +>+>+>-[>>>] Increase copy and remainder / reduce divisor / Normal case: skip forward +<[[>+<-]>>+>] Special case: move remainder back to divisor and increase quotient +<<<<<- Decrement dividend +] End division loop +]>>>[-]+ End skip loop; zero former divisor and reuse space for a flag +>--[-[<->+++[-]]]<[ Zero that flag unless quotient was 2 or 3; zero quotient; check flag +++++++++++++<[ If flag then set up divisor (13) for second division loop +(MEMORY LAYOUT: zero copy dividend divisor remainder quotient zero zero) +>-[>+>>] Reduce divisor; Normal case: increase remainder +>[+[<+>-]>+>>] Special case: increase remainder / move it back to divisor / increase quotient +<<<<<- Decrease dividend +] End division loop +>>[<+>-] Add remainder back to divisor to get a useful 13 +>[ Skip forward if quotient was 0 +-[ Decrement quotient and skip forward if quotient was 1 +-<<[-]>> Zero quotient and divisor if quotient was 2 +]<<[<<->>-]>> Zero divisor and subtract 13 from copy if quotient was 1 +]<<[<<+>>-] Zero divisor and add 13 to copy if quotient was 0 +] End outer skip loop (jump to here if ((character minus 1)/32) was not 2 or 3) +<[-] Clear remainder from first division if second division was skipped +<.[-] Output ROT13ed character from copy and clear it +<-,+ Read next character +] End character reading loop diff --git a/fucktrain b/fucktrain index d382efa..7ade621 100755 --- a/fucktrain +++ b/fucktrain @@ -17,7 +17,9 @@ BEGIN { gsub(/,/, "clack ", $0) gsub(/\[/, "tickets please ", $0) gsub(/\]/, "your ticket please ", $0) - print + if ($0) { + print + } } END { diff --git a/trainfuck b/trainfuck index 602fc92..22d1009 100755 --- a/trainfuck +++ b/trainfuck @@ -1,128 +1,15 @@ -#!/bin/awk -f -# TRAINFUCK: CHOO CHOO MUTHAFUCKA -*- awk -*- +#!/bin/sh +# TRAINFUCK: CHOO CHOO MUTHAFUCKA -*- sh -*- # Author: Case Duckworth # License: WTFPL # Version: #9 ### Commentary: -# LANGUAGE - -# trainfuck is not case-sensitive -# -- except for ALL ABOARD and END OF THE LINE -# ignore everything before ALL ABOARD -# ignore everything after END OF THE LINE -# (this means you can comment between these) -# bf tf -# + chug -# - chugga -# > choo -# < choo choo -# . click OR clickety -# , clack -# [ tickets please -# ] your ticket please -# syntax does NOT WRAP across line breaks -# anything else is an error and DERAILS the train +# shell wrapper around trainfuck.awk, which see. ### Code: -BEGIN { - aboard = 0 - width = 30 - print "[" -} - -/^ALL ABOARD$/ { - if (! header) { - print "]" - } - header++ - aboard = 1 - next -} - -/^END OF THE LINE$/ { - aboard = 0 - next -} - -aboard { - gsub(/[[:space:]]/, "", $0) - buf = buf proc(toupper($0)) -} - -! aboard { - printbuf() - if (header) { - gsub(/[-+<>.,\[\]]/, "", $0) - } - print - buf = "" -} - -END { - if (DERAIL_ERR) { - print DERAIL_ERR - exit 9 - } - printbuf() - printf "\n" -} - - -function derail(err) -{ - print "TRAIN DERAILED at input line", FNR - DERAIL_ERR = err - exit -} -function printbuf(newline) -{ - for (ss = 1; ss <= length(buf); ss += width) { - printf "%s\n", substr(buf, ss, width) - } -} +TRAINFUCK=trainfuck.awk -function proc(t) -{ - if (! match(t, /CHUGGA|CHUG|CHOO|CLICK|CLICKETY|CLACK|TICKETSPLEASE|YOURTICKETPLEASE|$/)) { - derail("WTF") - } - pre = substr(t, 1, RSTART - 1) - tok = substr(t, RSTART, RLENGTH) - pst = substr(t, RSTART + RLENGTH) - if (tok == "CHUGGA") { # needs to be first - tok = "-" - return (pre tok proc(pst)) - } - if (tok == "CHUG") { - tok = "+" - return (pre tok proc(pst)) - } - if (tok == "CHOO") { - if (substr(pst, 1, 4) == "CHOO") { - tok = "<" - sub(/CHOO/, "", pst) - } else { - tok = ">" - } - return (pre tok proc(pst)) - } - if (tok == "CLICK" || tok == "CLICKETY") { - tok = "." - return (pre tok proc(pst)) - } - if (tok == "CLACK") { - tok = "," - return (pre tok proc(pst)) - } - if (tok == "TICKETSPLEASE") { - tok = "[" - return (pre tok proc(pst)) - } - if (tok == "YOURTICKETPLEASE") { - tok = "]" - return (pre tok proc(pst)) - } -} +gawk -v EXE_NAME="$(basename $0)" -f "$TRAINFUCK" -- "$@" diff --git a/trainfuck.awk b/trainfuck.awk new file mode 100755 index 0000000..df89821 --- /dev/null +++ b/trainfuck.awk @@ -0,0 +1,321 @@ +#!/bin/awk -f +# TRAINFUCK: CHOO CHOO MUTHAFUCKA -*- awk -*- +# Author: Case Duckworth +# License: WTFPL +# Version: #9 + +### Commentary: + +# LANGUAGE + +# trainfuck is not case-sensitive +# -- except for ALL ABOARD and END OF THE LINE +# ignore everything before ALL ABOARD +# ignore everything after END OF THE LINE +# (this means you can comment between these) +# bf tf +# + chug +# - chugga +# > choo +# < choo choo +# . click OR clickety +# , clack +# [ tickets please +# ] your ticket please +# syntax does NOT WRAP across line breaks +# anything else is an error and DERAILS the train + +### Code: +BEGIN { + # Configuration + BF_WIDTH = 42 + BF_PRINT = 1 + BF_PRINT_COMMENTS = 1 + BF_COMMENTS_COMPACT = 1 + BF_EXECUTE = 0 + BF_OUTPUT = "/dev/stderr" + BF_MODE = "trainfuck" + process_commandline() + # Constants + EXE_NAME = (EXE_NAME ? EXE_NAME : "trainfuck") + ERR_SYNTAX = 1 +} + +BEGIN { + # State variables and beginning output + ABOARD = 0 + FIRST_LINE = 1 + OFS = "\t" +} + +BF_MODE == "brainfuck" { + bf_program = bf_program $0 + next +} + +FIRST_LINE { + if (BF_PRINT_COMMENTS && first_line != "[") { + eprint("[") + } + eprint(first_line) + FIRST_LINE = 0 +} + +/^ALL ABOARD$/ { + if (! header && BF_PRINT_COMMENTS) { + eprint("]") + } + header++ + ABOARD = 1 + next +} + +/^END OF THE LINE$/ { + ABOARD = 0 + next +} + +ABOARD { + gsub(/[[:space:]]/, "", $0) + buf = buf tf_convert(toupper($0)) +} + +! ABOARD && BF_PRINT_COMMENTS { + printbuf() + eprint($0) + buf = "" +} + +END { + if (ABOARD) { + die("Didn't disembark from the train!", ERR_SYNTAX) + } + if (dead) { + exit dead + } + printbuf() + if (BF_EXECUTE) { + brainfuck(bf_program) + } +} + + +function brainfuck(buffer) +{ + split(buffer, bf, "") + i = 1 + c = 1 + for (n = 1; n < 30000; n++) { + tape[n] = 0 + } + while (c <= length(bf)) { + # print i, tape[i], c, bf[c] + if (bf[c] == "<" && c > 1) { + i-- + } else if (bf[c] == ">") { + i++ + } else if (bf[c] == "+") { + tape[i]++ + } else if (bf[c] == "-") { + tape[i]-- + } else if (bf[c] == ".") { + printf "%c", tape[i] + } else if (bf[c] == ",") { + tape[i] = char2number(getchar()) + } else if (bf[c] == "[") { + bracket = 1 + if (! tape[i]) { + while (c <= length(bf)) { + c++ + if (bf[c] == "[") { + bracket++ + } else if (bf[c] == "]") { + bracket-- + if (! bracket) { + break + } + } + } + if (c > length(bf)) { + die("Mismatched bracket: " c, ERR_SYNTAX) + } + } + } else if (bf[c] == "]") { + bracket = 1 + if (tape[i]) { + while (c) { + c-- + if (bf[c] == "]") { + bracket++ + } else if (bf[c] == "[") { + bracket-- + if (! bracket) { + break + } + } + } + if (! c) { + die("Mismatched bracket: " c, ERR_SYNTAX) + } + } + } + c++ + } +} + +function die(message, errnum) +{ + print(message) > "/dev/stderr" + dead = errnum +} + +function eprint(message, suppress_newline) +{ + printf("%s" (suppress_newline ? "" : "\n"), message) > BF_OUTPUT +} + +function getchar() +{ + system("stty raw") + cmd = "dd bs=1 count=1 2>/dev/null" + cmd | getline ch + close(cmd) + system("stty cooked") + return ch +} + +function printbuf(newline) +{ + suppress_newline = BF_PRINT_COMMENTS && BF_COMMENTS_COMPACT + for (ss = 1; ss <= length(buf); ss += BF_WIDTH) { + eprint(substr(buf, ss, BF_WIDTH), suppress_newline) + suppress_newline = 0 + } + bf_program = bf_program buf +} + +function process_commandline() +{ + for (a in ARGV) { + if (ARGV[a] == "--") { + delete ARGV[a] + break + } else if (ARGV[a] == "-h" || ARGV[a] == "--help") { + usage() + } else if (ARGV[a] == "-w") { + delete ARGV[a] + BF_WIDTH = ARGV[++a] + delete ARGV[a] + } else if (ARGV[a] == "-o") { + delete ARGV[a] + BF_OUTPUT = ARGV[++a] + delete ARGV[a] + } else if (ARGV[a] == "-c" || ARGV[a] == "--comments") { + BF_PRINT_COMMENTS = 1 + delete ARGV[a] + } else if (ARGV[a] == "-C" || ARGV[a] == "--no-comments") { + BF_PRINT_COMMENTS = 0 + delete ARGV[a] + } else if (ARGV[a] == "-z" || ARGV[a] == "--compact") { + BF_COMMENTS_COMPACT = 1 + delete ARGV[a] + } else if (ARGV[a] == "-Z" || ARGV[a] == "--no-compact") { + BF_COMMENTS_COMPACT = 0 + delete ARGV[a] + } else if (ARGV[a] == "-x" || ARGV[a] == "--execute") { + BF_EXECUTE = 1 + delete ARGV[a] + } else if (ARGV[a] == "-X" || ARGV[a] == "--no-execute") { + BF_EXECUTE = 0 + delete ARGV[a] + } else if (ARGV[a] == "-t" || ARGV[a] == "--trainfuck") { + BF_MODE = "trainfuck" + BF_MODE_FORCE = 1 + delete ARGV[a] + } else if (ARGV[a] == "-b" || ARGV[a] == "--brainfuck") { + BF_MODE = "brainfuck" + BF_MODE_FORCE = 1 + delete ARGV[a] + } + } + if (! BF_MODE_FORCE) { + if (ARGV[1] ~ /\.tf$/ || ARGV[1] ~ /\.trainfuck$/) { + BF_MODE = "trainfuck" + } else if (ARGV[1] ~ /\.bf$/ || ARGV[1] ~ /\.brainfuck$/) { + BF_MODE = "brainfuck" + } + } + if (BF_MODE == "brainfuck") { + BF_EXECUTE = 1 + } +} + +function tf_convert(t) +{ + if (! match(t, /CHUGGA|CHUG|CHOO|CLICK|CLICKETY|CLACK|TICKETSPLEASE|YOURTICKETPLEASE|$/)) { + die("Derailed at input line " FNR, ERR_SYNTAX) + } + pre = substr(t, 1, RSTART - 1) + tok = substr(t, RSTART, RLENGTH) + pst = substr(t, RSTART + RLENGTH) + if (tok == "CHUGGA") { # needs to be first + tok = "-" + return (pre tok tf_convert(pst)) + } + if (tok == "CHUG") { + tok = "+" + return (pre tok tf_convert(pst)) + } + if (tok == "CHOO") { + if (substr(pst, 1, 4) == "CHOO") { + tok = "<" + sub(/CHOO/, "", pst) + } else { + tok = ">" + } + return (pre tok tf_convert(pst)) + } + if (tok == "CLICK" || tok == "CLICKETY") { + tok = "." + return (pre tok tf_convert(pst)) + } + if (tok == "CLACK") { + tok = "," + return (pre tok tf_convert(pst)) + } + if (tok == "TICKETSPLEASE") { + tok = "[" + return (pre tok tf_convert(pst)) + } + if (tok == "YOURTICKETPLEASE") { + tok = "]" + return (pre tok tf_convert(pst)) + } +} + +function usage() +{ + eprint("TRAINFUCK: CHOO CHOO") + eprint("Usage: ") + eprint(EXE_NAME " -h|--help") + eprint(EXE_NAME " [-w WIDTH] [FLAGS...] INPUTFILE [-o OUTPUTFILE]") + eprint() + eprint("Parameters:") + eprint(" INPUTFILE\tThe trainfuck file to process.") + eprint("\t\tIf INPUTFILE ends in .tf or .trainfuck, it will be interpreted") + eprint("\t\tas a TRAINFUCK file; if it ends in .bf or .brainfuck, it will ") + eprint("\t\tbe interpreted as a BRAINFUCK file. Otherwise, the file will ") + eprint("\t\tbe assumed to be the default filetype (" BF_MODE ").") + eprint(" -w WIDTH\tSet the brainfuck output width (default: " BF_WIDTH ").") + eprint(" -o OUTPUTFILE\tWhere to send transpiled brainfuck (default: " BF_OUTPUT ").") + eprint() + eprint("FLAGS:") + eprint(" -h, --help\t\tShow this help") + eprint(" -c, --comments\t\tShow comments" (BF_PRINT_COMMENTS ? " (default)" : "")) + eprint(" -C, --no-comments\tHide comments" (BF_PRINT_COMMENTS ? "" : " (default)")) + eprint(" -z, --compact\t\tShow \"compact\" comments" (BF_COMMENTS_COMPACT ? " (default)" : "")) + eprint(" -Z, --no-compact\tShow \"wide\" comments" (BF_COMMENTS_COMPACT ? "" : " (default)")) + eprint(" -x, --execute\t\tExecute trainspiled brainfuck" (BF_EXECUTE ? " (default)" : "")) + eprint(" -X, --no-execute\tOnly trainspile, do not execute" (BF_EXECUTE ? "" : " (default)")) + exit +} -- cgit 1.4.1-21-gabe81