#!/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 ERR_ARGUMENT = 2 } BEGIN { # State variables and beginning output ABOARD = 0 FIRST_LINE = 1 OFS = "\t" header = 0 } BF_MODE == "brainfuck" { bf_program = bf_program $0 next } FIRST_LINE { if (BF_PRINT_COMMENTS && $0 !~ /^[ \t]\[/) { eprint("[ ", 1) } FIRST_LINE = 0 } /^ALL ABOARD$/ { if (NR == 1) { eprint(ARGV[1] " ", 1) } 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() print "" 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 } len_bf = 0 for (i in bf) { len_bf++ } while (c <= len_bf) { 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 char2number(ch) { ascii = "" # NUL is undefined in POSIX awk ... XXX: ascii = ascii "\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017" ascii = ascii "\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" ascii = ascii " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO" ascii = ascii "PQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\177" ascii = ascii "\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217" ascii = ascii "\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237" ascii = ascii "\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257" ascii = ascii "\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277" ascii = ascii "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317" ascii = ascii "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" ascii = ascii "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357" ascii = ascii "\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377" # print ch return index(ascii, ch) } function die(message, errnum, now) { print(message) > "/dev/stderr" dead = errnum if (now) { exit errnum } } function eprint(message, suppress_newline) { printf("%s" (suppress_newline ? "" : "\n"), message) > BF_OUTPUT } function getchar() { system("stty raw") chcmd = "dd bs=1 count=1 2>/dev/null" chcmd | getline ch close(chcmd) 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] } else if (ARGV[a] == "-q" || ARGV[a] == "--quiet") { BF_OUTPUT = "/dev/null" delete ARGV[a] } else if (ARGV[a] ~ /^-/) { die("Unknown option '" ARGV[a] "'", ERR_ARGUMENT, 1) } } 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(" --\t\tStop processing arguments; interpret the rest as filenames") 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)")) eprint(" -t, --trainfuck\tForce TRAINFUCK mode: interpret files as trainfuck") eprint(" -b, --brainfuck\tForce BRAINFUCK mode: interpret files as brainfuck") eprint(" -q, --quiet\t\tDon't print any more than is necessary") exit }