#!/usr/bin/env awk -f # Luxferre's Truly Tiny BASIC, POSIX AWK port # Adheres to the LTTB Implementation Notes # Usage: POSIXLY_CORRECT=1 awk -f lttb.awk [ -- program.bas] # Created by Luxferre in 2026, released into the public domain # Helper functions (AWK-specific) function wstrim(s) {gsub(/^[[:space:]]+|[[:space:]]+$/, "", s);return s} function alen(a, i, k) {k = 0;for(i in a) k++;return k} function ttyinput(s) { if ((getline s < "/dev/stdin") <= 0) exit return wstrim(s) } function stackpush(a, el, l) { l = alen(a) a[l] = el return l + 1 } function stackpop(a, l, el) { l = alen(a) - 1 el = a[l] delete a[l] return el } function precmap(op) { if(op == "(") return 0 else if(op == "+" || op == "-") return 1 else if(op == "*" || op == "/") return 2 else if(op == "$") return 3 else return -1 } function stacktop(a) {return a[alen(a) - 1]} # Main LTTB algorithms start here # numeric value normalizer to 16-bit signed integers function norm(val) { if(length(val) == 0) return 0 val = int(val) val = (val + 32768) % 65536 if(val < 0) val += 65536 return val - 32768 } # evaluation helper (operates on VALSTACK and OPSTACK) function evalhelper(op, v1, v2, res) { res = 0 op = stackpop(OPSTACK) v2 = stackpop(VALSTACK) v1 = stackpop(VALSTACK) if(op == "$") res = int(rand() * ((v2 + 32768) % 32768)) else if(op == "+") res = v1 + v2 else if(op == "-") res = v1 - v2 else if(op == "*") res = v1 * v2 else if(op == "/" && v2 != 0) res = v1 / v2 stackpush(VALSTACK, norm(res)) } # main expression evaluator function expreval(expr, valbuf, c, prevc, i, l) { gsub("[[:space:]]+", "", expr) if(length(expr) == 0) return 0 split("", OPSTACK) split("", VALSTACK) i = 0 l = length(expr) valbuf = prevc = "" for(i=0;i 0) { stackpush(VALSTACK, norm(valbuf)) valbuf = "" } if(substr(toupper(expr), i+1, 3) == "RND") { stackpush(VALSTACK, 0) c = "$" i += 2 } if(c ~ /[a-zA-Z]/) stackpush(VALSTACK, norm(VARS[toupper(c)])) else if(c == "(") stackpush(OPSTACK, "(") else if(c == ")") { while(stacktop(OPSTACK) != "(" && alen(OPSTACK) > 0) evalhelper() if(alen(OPSTACK) > 0) stackpop(OPSTACK) } if(c == "$" || c == "+" || c == "-" || c == "*" || c == "/") { if((c == "+" || c == "-") && (prevc == "" || prevc == "(" || prevc == "$" || prevc == "+" || prevc == "-" || prevc == "*" || prevc == "/")) stackpush(VALSTACK, 0) while(alen(OPSTACK) > 0 && precmap(stacktop(OPSTACK)) >= precmap(c)) evalhelper() stackpush(OPSTACK, c) } } prevc = c } if(length(valbuf) > 0) stackpush(VALSTACK, norm(valbuf)) while(alen(OPSTACK) > 0) evalhelper() return stackpop(VALSTACK) } # internal language routines function do_GO(stmt) {IP = expreval(stmt) - 1} function do_GOS(stmt) {stackpush(CALLSTACK, IP); do_GO(stmt)} function do_RE(stmt) {IP = stackpop(CALLSTACK)} function do_LE(stmt, vname, eqp, expr) { vname = toupper(substr(stmt, 1, 1)) if(vname ~ /[A-Z]/) { eqp = index(stmt, "=") expr = substr(stmt, eqp + 1) VARS[vname] = expreval(expr) } } function do_IF(stmt, ropos, roend, relop, thenpos, rest, restpos, kw, expr1, expr2, r1, r2, c, k) { ropos = index(stmt, "<") r1 = index(stmt, ">") r2 = index(stmt, "=") if(ropos == 0 || (r1 > 0 && r1 < ropos)) ropos = r1 if(ropos == 0 || (r2 > 0 && r2 < ropos)) ropos = r2 roend = ropos c = substr(stmt, ropos + 1, 1) if(c == ">" || c == "<" || c == "=") roend++ relop = substr(stmt, ropos, roend - ropos + 1) expr1 = substr(stmt, 1, ropos - 1) rest = substr(stmt, roend + 1) thenpos = 0 for(k=1; k<=10; k++) { kw = IF_KWS[k] thenpos = index(toupper(rest), kw) if(thenpos > 0) { if(kw == "=") thenpos-- restpos = thenpos if(kw == "THEN") restpos += 4 break } } expr2 = substr(rest, 1, thenpos-1) rest = substr(rest, restpos) r1 = expreval(expr1) r2 = expreval(expr2) c = 0 if(relop == "<>" || relop == "><") c = (r1 != r2) ? 1 : 0 if(relop == "=" || relop == "==") c = (r1 == r2) ? 1 : 0 if(relop == ">") c = (r1 > r2) ? 1 : 0 if(relop == "<") c = (r1 < r2) ? 1 : 0 if(relop == ">=") c = (r1 >= r2) ? 1 : 0 if(relop == "<=") c = (r1 <= r2) ? 1 : 0 if(c == 1) proginput(rest) } function do_IN(stmt, v, i, l) { gsub("[[:space:]]+", "", stmt) l = split(stmt, varlist, ",") printf "? " inputstr = ttyinput() gsub("[[:space:]]+", "", inputstr) split(toupper(inputstr), inputlist, ",") for(i=1;i<=l;i++) { v = toupper(substr(varlist[i], 1, 1)) VARS[v] = expreval(inputlist[i]) } } function do_PR(stmt, item, quoting, c, l, i) { item = "" split("", printlist) quoting = 0 l = length(stmt) for(i=0;i 0) {stackpush(printlist, item); item = ""} stackpush(printlist, c) } else if(c != " " && c != "\t" && c != "\r") item = item c } } if(length(item) > 0) {stackpush(printlist, item); item = ""} l = alen(printlist) for(i=0;i 0) body = wstrim(substr(stmt, ws)) else body = "" if(kw == "GO") { if(substr(toupper(stmt), 3, 1) == "S") do_GOS(body) else do_GO(body) } else if(kw == "LE") do_LE(body) else if(kw == "IF") do_IF(body) else if(kw == "IN") do_IN(body) else if(kw == "PR") do_PR(body) else if(kw == "RE") do_RE(body) else if(kw == "EN") do_EN(body) } } # Program line input function function proginput(stmt, lno, i, l, c) { l = length(stmt) if(l == 0) return lno = 0 for(i=0;i 0) PROGMEM[lno] = stmt else if(index(toupper(stmt), "REM") != 1) progexec(stmt) } # Clearing action function ext_CLEAR(i) { split("", PROGMEM) # init program memory split("", CALLSTACK) # init call stack array split("", VARS) # init vars associative array for(i=0;i<26;i++) VARS[sprintf("%c", 65+i)] = 0 IP = 0 # instruction pointer } function ext_LOAD(fname, err, s) { ext_CLEAR() err = (getline s < fname) if(err < 0) { print "Error: File " fname " does not exist or cannot be read." return } if(err > 0) proginput(wstrim(s)) while((getline s < fname) > 0) proginput(wstrim(s)) close(fname) } function ext_LIST(fname, i, sep) { fmt = (fname == "") ? "%d\t%s\n" : "%d %s\n" for(i = 1; i > 0 && i < 32768; i++) if(i in PROGMEM && length(PROGMEM[i]) > 0) { if(fname == "") printf(fmt, i, PROGMEM[i]) else printf(fmt, i, PROGMEM[i]) >> fname } if(fname != "") { fflush(fname) close(fname) } } function ext_RUN() { for(IP = 1; IP > 0 && IP < 32768; IP++) if(IP in PROGMEM && length(PROGMEM[IP]) > 0 && index(toupper(PROGMEM[IP]), "REM") != 1) progexec(PROGMEM[IP]) } # Init actions BEGIN { srand() # init the PRNG split("THEN IF LET = RETURN GOSUB INPUT PRINT GOTO END", IF_KWS) ext_CLEAR() # init the memory # print the welcome banner print "-----------------------------" print " Luxferre's Truly Tiny BASIC" print " AWK Edition" print " @ Copyleft 2026" print " All wrongs reserved" print "-----------------------------" if(ARGC > 1) { printf "Preloading LTTB program from %s...\n", ARGV[1] ext_LOAD(ARGV[1]) } exit } # Main interpreter loop END { while(1) { printf "> " cmd = wstrim(ttyinput()) if(length(cmd) > 0) { if(toupper(cmd) == "BYE") {print "Bye!"; break} else if(toupper(cmd) == "RUN") ext_RUN() else if(toupper(cmd) == "LIST") ext_LIST("") else if(toupper(cmd) == "NEW" || toupper(cmd) == "CLEAR") ext_CLEAR() else if(toupper(cmd) == "LOAD") { printf "File name: " ext_LOAD(ttyinput()) } else if(toupper(cmd) == "SAVE") { printf "File name: " ext_LIST(ttyinput()) } else proginput(cmd) # pass as the program input } } }