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:
|
Here are all currently known implementations of LTTB:
|
||||||
|
|
||||||
* [Jim Tcl port](./lttb.tcl) (284 SLOC)
|
* [Jim Tcl port](./lttb.tcl) (284 SLOC)
|
||||||
|
* [POSIX AWK port](./lttb.awk) (280 SLOC)
|
||||||
|
|
||||||
## Test suite
|
## Test suite
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -24,7 +24,7 @@ Initial values:
|
|||||||
* `PROGMEM` = empty
|
* `PROGMEM` = empty
|
||||||
* `VARS["A"]..VARS["Z"] = 0`
|
* `VARS["A"]..VARS["Z"] = 0`
|
||||||
* `CALLSTACK` = empty
|
* `CALLSTACK` = empty
|
||||||
* `IP` = 0
|
* `IP = 0`
|
||||||
|
|
||||||
## Overall interpreter session flow
|
## 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.
|
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`.
|
1. Trim leading/trailing whitespace from `EXPR`.
|
||||||
2. If `EXPR` is empty, return 0.
|
2. If `EXPR` is empty, return 0.
|
||||||
3. Initialize `VALSTACK` and `OPSTACK` as empty stacks.
|
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`.
|
1. Initialize an empty list `PRINTLIST` and an empty string buffer `ITEM`.
|
||||||
2. Set the `QUOTING` flag to 0.
|
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.
|
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.
|
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.
|
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.
|
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