Files

327 lines
8.8 KiB
Awk
Raw Permalink Normal View History

2026-06-22 16:08:44 +03:00
#!/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<l;i++) {
c = substr(expr, i+1, 1)
if(c == int(c)) valbuf = valbuf c
else {
if(length(valbuf) > 0) {
stackpush(VALSTACK, norm(valbuf))
valbuf = ""
}
if(substr(expr, i+1, 3) == "RND") {
2026-06-22 16:08:44 +03:00
stackpush(VALSTACK, 0)
c = "$"
i += 2
}
if(c ~ /[A-Z]/) stackpush(VALSTACK, norm(VARS[c]))
2026-06-22 16:08:44 +03:00
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 = substr(stmt, 1, 1)
2026-06-22 16:08:44 +03:00
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(rest, kw)
2026-06-22 16:08:44 +03:00
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(toupper(stmt), varlist, ",")
2026-06-22 16:08:44 +03:00
printf "? "
inputstr = ttyinput()
gsub("[[:space:]]+", "", inputstr)
split(toupper(inputstr), inputlist, ",")
for(i=1;i<=l;i++) {
v = substr(varlist[i], 1, 1)
2026-06-22 16:08:44 +03:00
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<l;i++) {
c = substr(stmt, i+1, 1)
if(c == "\"") {
quoting = 1 - quoting
item = item c
} else if(quoting == 1) {item = item c}
else {
if(c == "," || c == ";") {
if(length(item) > 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<l;i++) {
item = printlist[i]
if(substr(item, 1, 1) == "\"") { # verbatim output
gsub(/^"|"$/, "", item)
printf("%s", item)
} else if(item == ",") printf("\t")
else if(item != ";") printf("%d", expreval(item))
}
if(l == 0 || (printlist[l-1] != "," && printlist[l-1] != ";")) printf("\n")
}
function do_EN(stmt) {IP = -1; split("", CALLSTACK)}
# Program line execution function
function progexec(stmt, kw, ws, body) {
kw = substr(stmt, 1, 2)
2026-06-22 16:08:44 +03:00
if(substr(kw, 2, 1) == "=") {
kw = "LE"
stmt = "LE " stmt
}
if(kw == "LE" || kw == "GO" || kw =="IF" || kw == "IN" || kw == "PR" || kw == "RE" || kw == "EN") {
ws = index(stmt, " ")
if(ws < 1) ws = index(stmt, "\t")
if(ws > 0) body = wstrim(substr(stmt, ws))
else body = ""
if(kw == "GO") {
if(substr(stmt, 3, 1) == "S") do_GOS(body)
2026-06-22 16:08:44 +03:00
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<l;i++) {
c = substr(stmt, i+1, 1)
if(c == int(c)) lno = lno * 10 + c
else if(c != " " && c != "\t") break
}
stmt = substr(stmt, i+1)
if(lno > 0) PROGMEM[lno] = stmt
else if(index(stmt, "REM") != 1) progexec(stmt)
2026-06-22 16:08:44 +03:00
}
# 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(toupper(wstrim(s)))
while((getline s < fname) > 0) proginput(toupper(wstrim(s)))
2026-06-22 16:08:44 +03:00
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(PROGMEM[IP], "REM") != 1)
2026-06-22 16:08:44 +03:00
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 = toupper(ttyinput())
2026-06-22 16:08:44 +03:00
if(length(cmd) > 0) {
if(cmd == "BYE") {print "Bye!"; break}
else if(cmd == "RUN") ext_RUN()
else if(cmd == "LIST") ext_LIST("")
else if(cmd == "NEW" || cmd == "CLEAR") ext_CLEAR()
else if(cmd == "LOAD") {
2026-06-22 16:08:44 +03:00
printf "File name: "
ext_LOAD(ttyinput())
}
else if(cmd == "SAVE") {
2026-06-22 16:08:44 +03:00
printf "File name: "
ext_LIST(ttyinput())
}
else proginput(cmd) # pass as the program input
}
}
}