First upload
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# LVTL-W: port of LVTL-R to Busybox/GNU AWK in under 130 SLOC
|
||||
# should also work in any other AWK version with bitwise operations
|
||||
#
|
||||
# Usage: [busybox] awk -f lvtl.awk [prog.vtl -]
|
||||
# (don't forget the - after the program or LVTL will exit upon preloading)
|
||||
#
|
||||
# Any program valid for LVTL-W will also run on LVTL-R and LVTL-O and,
|
||||
# if it doesn't use bitwise ops, on the original VTL-2 and VTL02 too
|
||||
#
|
||||
# Differences from the C version of LVTL-R (besides being much slower):
|
||||
# - lines are stored in an AWK array where line numbers are keys
|
||||
# - maximum line length is not enforced
|
||||
# - the variables and array areas are fully separated from line memory
|
||||
# - because of all this, & is always 0 and * is always 65535
|
||||
# - extra whitespace before and after line numbers is accepted
|
||||
# - only LFs are printed instead of CRLFs
|
||||
# - strings are allowed in immediate statements
|
||||
#
|
||||
# Differences from the original VTL-2 for Altair:
|
||||
# - both LF and CRLF are accepted (but not saved into RAM)
|
||||
# - only LFs are printed instead of CRLFs
|
||||
# - maximum line length is not enforced
|
||||
# - all whitespace after the line number is fully ignored
|
||||
# - any whitespace within expressions is also insignificant
|
||||
# - parentheses are NOT auto-closed at the end of the statement
|
||||
# - input with ? is NOT evaluated and only numbers are accepted
|
||||
# - only 26 characters (A-Z) are valid generic variable names
|
||||
# - supports all standard VTL-2 binary operators: +, -, *, /, =, >, <
|
||||
# - also supports bitwise operators: & (and), ! (or), # (xor)
|
||||
# - jumps to itself (like 10 #=10) are prohibited and counted as nops
|
||||
# - no file I/O (only the option to preload VTL code from a file)
|
||||
# - self-modifying programs won't work correctly
|
||||
#
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
# Global arrays and vars: ORD, PROGLINES, VARS, SCRATCH, PROCCHARS
|
||||
|
||||
# fatal error reporter
|
||||
function trapout(errmsg) {printf("Fatal error: %s\n", errmsg); exit(1)}
|
||||
|
||||
# read a generic/system variable
|
||||
function getvar(varname, c) {
|
||||
res = 0
|
||||
if(varname == "$") { # get a single character from stdin
|
||||
(cmd="c='';read -r -n 1 c;echo \"$c\"") | getline c
|
||||
close(cmd)
|
||||
res = ORD[substr(c,1,1)]
|
||||
}
|
||||
else if(varname == "?") { # get a number from stdin
|
||||
getline c # read the line
|
||||
res = and(int(c), 65535) # cast to 16-bit integer
|
||||
}
|
||||
else if(varname == "'") res = int(rand()*65536) # get a pseudorandom number
|
||||
else if(varname in VARS) res = VARS[varname] # regular or system variable
|
||||
return res
|
||||
}
|
||||
|
||||
# set a generic/system variable
|
||||
function setvar(varname, value, qi) {
|
||||
if(varname == "$") printf("%c", and(int(value), 255)) # output a character
|
||||
else if(varname == "?") { # output a string or a number
|
||||
if(VARS["\""]) { # we print a string
|
||||
qi = index(value, "\"") # find the ending quote
|
||||
printf("%s", substr(value, 1, qi - 1))
|
||||
# also print LF unless stated otherwise with ; modifier
|
||||
if(substr(value, qi+1, 1) != ";") printf("\n")
|
||||
VARS["\""] = 0 # reset the printing flag
|
||||
}
|
||||
else printf("%u", and(int(value), 65535)) # we print a number, no LF
|
||||
}
|
||||
else if(varname in VARS) { # regular or system variable
|
||||
value = and(int(value), 65535) # cast to 16-bit integer
|
||||
# if the var is #, cache the return address before writing
|
||||
if(varname == "#" && value > 0) VARS["!"] = VARS["#"] + 1
|
||||
VARS[varname] = value # update the value
|
||||
}
|
||||
else trapout("invalid variable name!")
|
||||
}
|
||||
|
||||
# strict LTR evaluator
|
||||
function evalexpr(expr, tkn, i, l, opex, acc, cop, oprnd, subs, res) {
|
||||
opex = 1
|
||||
res = cop = acc = oprnd = 0
|
||||
l = length(expr) # GAWK doesn't support multi-init in fors, busybox does
|
||||
for(i=1;i<=l;i++) { # token position is stored in i
|
||||
tkn = substr(expr, i, 1) # get a single character token
|
||||
if(tkn == ")") break # ditch the rest of expression
|
||||
if(tkn == " " || tkn == "\t") continue # skip all whitespace
|
||||
if(opex = 1 - opex) { # we expect an operator
|
||||
if(index("+-*/=<>&!#", tkn)) cop = tkn # save the valid operator
|
||||
else trapout(sprintf("unexpected binary operator at %u\n", VARS["#"]))
|
||||
}
|
||||
else { # we expect a value, a variable, a subscript or a subexpr
|
||||
if(tkn == "\"") { # quote immediately prints and returns
|
||||
VARS[tkn] = 1
|
||||
return substr(expr, i+1) # return everything after the quote
|
||||
}
|
||||
# extract and evaluate a parens or subscript expression
|
||||
else if(tkn == ":" || tkn == "(") {
|
||||
subs = evalexpr(substr(expr, i+1)) # process everything after tkn
|
||||
# set the operand to array elem or eval result
|
||||
oprnd = (tkn == ":") ? SCRATCH[subs] : subs
|
||||
i += PROCCHARS # update the outer index
|
||||
}
|
||||
else if(index("0123456789", tkn)) { # it's a digit
|
||||
oprnd = int(substr(expr, i)) # read the integer
|
||||
i += length(oprnd) - 1 # skip the rest
|
||||
}
|
||||
else oprnd = getvar(tkn) # we assume it's a valid variable name
|
||||
# now, perform the calculation - no default branch!
|
||||
if(oprnd < 0) oprnd += 65536 # keep the operand positive
|
||||
oprnd = and(oprnd, 65535) # keep the operand within 16 bits
|
||||
if(cop == 0) acc = oprnd
|
||||
else if(cop == "+") acc += oprnd
|
||||
else if(cop == "-") acc -= oprnd
|
||||
else if(cop == "*") acc *= oprnd
|
||||
else if(cop == "/" && oprnd) {
|
||||
VARS["%"] = acc % oprnd # keep the remainder in the % sysvar
|
||||
acc = int(acc/oprnd)
|
||||
}
|
||||
else if(cop == "=") acc = (acc == oprnd) ? 1 : 0
|
||||
else if(cop == ">") acc = (acc >= oprnd) ? 1 : 0
|
||||
else if(cop == "<") acc = (acc < oprnd) ? 1 : 0
|
||||
else if(cop == "&") acc = and(acc, oprnd)
|
||||
else if(cop == "!") acc = or(acc, oprnd)
|
||||
else if(cop == "#") acc = xor(acc, oprnd)
|
||||
if(acc < 0) acc += 65536 # keep the accumulator positive
|
||||
res = acc = and(acc, 65535) # keep the accumulator within 16 bits
|
||||
}
|
||||
}
|
||||
PROCCHARS = i # because we started from 1
|
||||
return res
|
||||
}
|
||||
|
||||
# run the statement
|
||||
function exec_stmt(stmt, ln, lcache, lhs, rhs, ei, vn) {
|
||||
lcache = ln # cache the line number
|
||||
ei = index(stmt, "=") # get the first occurrence of =
|
||||
lhs = substr(stmt, 1, ei - 1) # extract LHS as everything before =
|
||||
rhs = substr(stmt, ei + 1) # extract RHS as everything after =
|
||||
if(!length(rhs) || !length(lhs)) return # nop on invalid statements
|
||||
ei = evalexpr(rhs) # evaluate RHS
|
||||
vn = substr(lhs, 1, 1) # extract the varname
|
||||
# evaluate the array subscript and update the array
|
||||
if(vn == ":") SCRATCH[evalexpr(substr(lhs, 2))] = ei
|
||||
else setvar(vn, ei) # update the (pseudo) variable
|
||||
# seek the next statement
|
||||
ln = VARS["#"]
|
||||
# we're starting from interactive mode OR updating the number
|
||||
if((!ln && lcache) || (ln > 0 && ln == lcache)) ln = lcache + 1
|
||||
if(ln > 0) {
|
||||
for(ei=ln;ei<65536;ei++) if(ei in PROGLINES) {ln = ei; break} # search
|
||||
if(ei >= 65536) ln = 0 # line not found
|
||||
VARS["#"] = ln # save the closest line number
|
||||
if(ln) exec_stmt(PROGLINES[ln], ln) # pass new statement for execution
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN { # interpreter init
|
||||
"date +%N"|getline rseed;srand(rseed) # init the PRNG
|
||||
PROCCHARS = 0 # global cache to track processed characters
|
||||
for(i=0;i<256;i++) ORD[sprintf("%c", i)] = i # init char-to-ASCII mapping
|
||||
for(i=0;i<26;i++) VARS[sprintf("%c", i+ORD["A"])] = 0 # init the var memory
|
||||
# initialize sysvars
|
||||
VARS["!"] = VARS["\""] = VARS["#"] = VARS["%"] = VARS["&"] = 0
|
||||
VARS["*"] = 65535
|
||||
for(i=0;i<65536;i++) SCRATCH[i] = 0 # initialize the scratch area
|
||||
print "LVTL-W by Luxferre\nPress Ctrl+C to exit\n\nOK"
|
||||
}
|
||||
|
||||
{ # main line-by-line processing
|
||||
ln = and(int($1), 65535) # attempt to scan line number (default to 0)
|
||||
if(NF > 1) $1 = "" # prepare to scan the statement
|
||||
gsub(/^[ \t]+|[ \t]+$/, "") # trim the statement
|
||||
if(ln > 0) { # we have a line number
|
||||
if($0 == "" || $0 == ln) delete PROGLINES[ln] # delete the line
|
||||
else PROGLINES[ln] = $0 # cache the statement
|
||||
}
|
||||
else { # it's an immediate statement
|
||||
if($0 == "0") { # list the program
|
||||
for(i=1;i<65536;i++) # start from 1 because 0 is not a valid line number
|
||||
if(i in PROGLINES) print i, PROGLINES[i] # list the found line
|
||||
}
|
||||
else exec_stmt($0, 0) # pass to immediate execution
|
||||
print "\nOK"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user