added POSIX AWK port
This commit is contained in:
@@ -27,6 +27,7 @@ If you want to create your own implementation of LTTB, see the [Implementation N
|
||||
Here are all currently known implementations of LTTB:
|
||||
|
||||
* [Jim Tcl port](./lttb.tcl) (284 SLOC)
|
||||
* [POSIX AWK port](./lttb.awk) (280 SLOC)
|
||||
|
||||
## Test suite
|
||||
|
||||
|
||||
+4
-2
@@ -24,7 +24,7 @@ Initial values:
|
||||
* `PROGMEM` = empty
|
||||
* `VARS["A"]..VARS["Z"] = 0`
|
||||
* `CALLSTACK` = empty
|
||||
* `IP` = 0
|
||||
* `IP = 0`
|
||||
|
||||
## Overall interpreter session flow
|
||||
|
||||
@@ -90,6 +90,8 @@ Simple two-value evaluator. Accepts two stacks, value stack `VALSTACK` and opera
|
||||
|
||||
Mathematical expression and intrinsic function application engine. Accepts a single `EXPR` string as a parameter.
|
||||
|
||||
Requires a precedence map `PRECMAP` to be pre-defined as follows: `"(" => 0, "+" => 1, "-" => 1, "*" => 2, "/" => 2, "$" => 3`.
|
||||
|
||||
1. Trim leading/trailing whitespace from `EXPR`.
|
||||
2. If `EXPR` is empty, return 0.
|
||||
3. Initialize `VALSTACK` and `OPSTACK` as empty stacks.
|
||||
@@ -198,7 +200,7 @@ Value and string printing routine. Accepts `STMT` string as a parameter.
|
||||
1. Initialize an empty list `PRINTLIST` and an empty string buffer `ITEM`.
|
||||
2. Set the `QUOTING` flag to 0.
|
||||
3. Fetch the next character `C` from `STMT`. Go to step 10 if there are no more characters.
|
||||
4. If `C` is a double quote `"`, then flip the `QUOTING` flag (set `QUOTING = 1 - QUOTING`), append the value of `C` to `ITEM` and go to step 3. Otherwise, go to step 6.
|
||||
4. If `C` is a double quote `"`, then flip the `QUOTING` flag (set `QUOTING = 1 - QUOTING`), append the value of `C` to `ITEM` and go to step 3. Otherwise, go to step 5.
|
||||
5. If the `QUOTING` flag is set, append the value of `C` to `ITEM` and go to step 3.
|
||||
6. If `C` is either a comma (`,`) or a semicolon (`;`), go to step 7, otherwise go to step 9.
|
||||
7. If the current `ITEM` value is not empty, append it to the `PRINTLIST` and clear the `ITEM` variable.
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
#!/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(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<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 = toupper(substr(stmt, 1, 2))
|
||||
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(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<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(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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user