lvtl.awk (8112B)
1 # LVTL-W: port of LVTL-R to Busybox/GNU AWK in under 130 SLOC 2 # should also work in any other AWK version with bitwise operations 3 # 4 # Usage: [busybox] awk -f lvtl.awk [prog.vtl -] 5 # (don't forget the - after the program or LVTL will exit upon preloading) 6 # 7 # Any program valid for LVTL-W will also run on LVTL-R and LVTL-O and, 8 # if it doesn't use bitwise ops, on the original VTL-2 and VTL02 too 9 # 10 # Differences from the C version of LVTL-R (besides being much slower): 11 # - lines are stored in an AWK array where line numbers are keys 12 # - maximum line length is not enforced 13 # - the variables and array areas are fully separated from line memory 14 # - because of all this, & is always 0 and * is always 65535 15 # - extra whitespace before and after line numbers is accepted 16 # - only LFs are printed instead of CRLFs 17 # - strings are allowed in immediate statements 18 # 19 # Differences from the original VTL-2 for Altair: 20 # - both LF and CRLF are accepted (but not saved into RAM) 21 # - only LFs are printed instead of CRLFs 22 # - maximum line length is not enforced 23 # - all whitespace after the line number is fully ignored 24 # - any whitespace within expressions is also insignificant 25 # - parentheses are NOT auto-closed at the end of the statement 26 # - input with ? is NOT evaluated and only numbers are accepted 27 # - only 26 characters (A-Z) are valid generic variable names 28 # - supports all standard VTL-2 binary operators: +, -, *, /, =, >, < 29 # - also supports bitwise operators: & (and), ! (or), # (xor) 30 # - jumps to itself (like 10 #=10) are prohibited and counted as nops 31 # - no file I/O (only the option to preload VTL code from a file) 32 # - self-modifying programs won't work correctly 33 # 34 # Created by Luxferre in 2023, released into public domain 35 36 # Global arrays and vars: ORD, PROGLINES, VARS, SCRATCH, PROCCHARS 37 38 # fatal error reporter 39 function trapout(errmsg) {printf("Fatal error: %s\n", errmsg); exit(1)} 40 41 # read a generic/system variable 42 function getvar(varname, c) { 43 res = 0 44 if(varname == "$") { # get a single character from stdin 45 (cmd="c='';read -r -n 1 c;echo \"$c\"") | getline c 46 close(cmd) 47 res = ORD[substr(c,1,1)] 48 } 49 else if(varname == "?") { # get a number from stdin 50 getline c # read the line 51 res = and(int(c), 65535) # cast to 16-bit integer 52 } 53 else if(varname == "'") res = int(rand()*65536) # get a pseudorandom number 54 else if(varname in VARS) res = VARS[varname] # regular or system variable 55 return res 56 } 57 58 # set a generic/system variable 59 function setvar(varname, value, qi) { 60 if(varname == "$") printf("%c", and(int(value), 255)) # output a character 61 else if(varname == "?") { # output a string or a number 62 if(VARS["\""]) { # we print a string 63 qi = index(value, "\"") # find the ending quote 64 printf("%s", substr(value, 1, qi - 1)) 65 # also print LF unless stated otherwise with ; modifier 66 if(substr(value, qi+1, 1) != ";") printf("\n") 67 VARS["\""] = 0 # reset the printing flag 68 } 69 else printf("%u", and(int(value), 65535)) # we print a number, no LF 70 } 71 else if(varname in VARS) { # regular or system variable 72 value = and(int(value), 65535) # cast to 16-bit integer 73 # if the var is #, cache the return address before writing 74 if(varname == "#" && value > 0) VARS["!"] = VARS["#"] + 1 75 VARS[varname] = value # update the value 76 } 77 else trapout("invalid variable name!") 78 } 79 80 # strict LTR evaluator 81 function evalexpr(expr, tkn, i, l, opex, acc, cop, oprnd, subs, res) { 82 opex = 1 83 res = cop = acc = oprnd = 0 84 l = length(expr) # GAWK doesn't support multi-init in fors, busybox does 85 for(i=1;i<=l;i++) { # token position is stored in i 86 tkn = substr(expr, i, 1) # get a single character token 87 if(tkn == ")") break # ditch the rest of expression 88 if(tkn == " " || tkn == "\t") continue # skip all whitespace 89 if(opex = 1 - opex) { # we expect an operator 90 if(index("+-*/=<>&!#", tkn)) cop = tkn # save the valid operator 91 else trapout(sprintf("unexpected binary operator at %u\n", VARS["#"])) 92 } 93 else { # we expect a value, a variable, a subscript or a subexpr 94 if(tkn == "\"") { # quote immediately prints and returns 95 VARS[tkn] = 1 96 return substr(expr, i+1) # return everything after the quote 97 } 98 # extract and evaluate a parens or subscript expression 99 else if(tkn == ":" || tkn == "(") { 100 subs = evalexpr(substr(expr, i+1)) # process everything after tkn 101 # set the operand to array elem or eval result 102 oprnd = (tkn == ":") ? SCRATCH[subs] : subs 103 i += PROCCHARS # update the outer index 104 } 105 else if(index("0123456789", tkn)) { # it's a digit 106 oprnd = int(substr(expr, i)) # read the integer 107 i += length(oprnd) - 1 # skip the rest 108 } 109 else oprnd = getvar(tkn) # we assume it's a valid variable name 110 # now, perform the calculation - no default branch! 111 if(oprnd < 0) oprnd += 65536 # keep the operand positive 112 oprnd = and(oprnd, 65535) # keep the operand within 16 bits 113 if(cop == 0) acc = oprnd 114 else if(cop == "+") acc += oprnd 115 else if(cop == "-") acc -= oprnd 116 else if(cop == "*") acc *= oprnd 117 else if(cop == "/" && oprnd) { 118 VARS["%"] = acc % oprnd # keep the remainder in the % sysvar 119 acc = int(acc/oprnd) 120 } 121 else if(cop == "=") acc = (acc == oprnd) ? 1 : 0 122 else if(cop == ">") acc = (acc >= oprnd) ? 1 : 0 123 else if(cop == "<") acc = (acc < oprnd) ? 1 : 0 124 else if(cop == "&") acc = and(acc, oprnd) 125 else if(cop == "!") acc = or(acc, oprnd) 126 else if(cop == "#") acc = xor(acc, oprnd) 127 if(acc < 0) acc += 65536 # keep the accumulator positive 128 res = acc = and(acc, 65535) # keep the accumulator within 16 bits 129 } 130 } 131 PROCCHARS = i # because we started from 1 132 return res 133 } 134 135 # run the statement 136 function exec_stmt(stmt, ln, lcache, lhs, rhs, ei, vn) { 137 lcache = ln # cache the line number 138 ei = index(stmt, "=") # get the first occurrence of = 139 lhs = substr(stmt, 1, ei - 1) # extract LHS as everything before = 140 rhs = substr(stmt, ei + 1) # extract RHS as everything after = 141 if(!length(rhs) || !length(lhs)) return # nop on invalid statements 142 ei = evalexpr(rhs) # evaluate RHS 143 vn = substr(lhs, 1, 1) # extract the varname 144 # evaluate the array subscript and update the array 145 if(vn == ":") SCRATCH[evalexpr(substr(lhs, 2))] = ei 146 else setvar(vn, ei) # update the (pseudo) variable 147 # seek the next statement 148 ln = VARS["#"] 149 # we're starting from interactive mode OR updating the number 150 if((!ln && lcache) || (ln > 0 && ln == lcache)) ln = lcache + 1 151 if(ln > 0) { 152 for(ei=ln;ei<65536;ei++) if(ei in PROGLINES) {ln = ei; break} # search 153 if(ei >= 65536) ln = 0 # line not found 154 VARS["#"] = ln # save the closest line number 155 if(ln) exec_stmt(PROGLINES[ln], ln) # pass new statement for execution 156 } 157 } 158 159 BEGIN { # interpreter init 160 "date +%N"|getline rseed;srand(rseed) # init the PRNG 161 PROCCHARS = 0 # global cache to track processed characters 162 for(i=0;i<256;i++) ORD[sprintf("%c", i)] = i # init char-to-ASCII mapping 163 for(i=0;i<26;i++) VARS[sprintf("%c", i+ORD["A"])] = 0 # init the var memory 164 # initialize sysvars 165 VARS["!"] = VARS["\""] = VARS["#"] = VARS["%"] = VARS["&"] = 0 166 VARS["*"] = 65535 167 for(i=0;i<65536;i++) SCRATCH[i] = 0 # initialize the scratch area 168 print "LVTL-W by Luxferre\nPress Ctrl+C to exit\n\nOK" 169 } 170 171 { # main line-by-line processing 172 ln = and(int($1), 65535) # attempt to scan line number (default to 0) 173 if(NF > 1) $1 = "" # prepare to scan the statement 174 gsub(/^[ \t]+|[ \t]+$/, "") # trim the statement 175 if(ln > 0) { # we have a line number 176 if($0 == "" || $0 == ln) delete PROGLINES[ln] # delete the line 177 else PROGLINES[ln] = $0 # cache the statement 178 } 179 else { # it's an immediate statement 180 if($0 == "0") { # list the program 181 for(i=1;i<65536;i++) # start from 1 because 0 is not a valid line number 182 if(i in PROGLINES) print i, PROGLINES[i] # list the found line 183 } 184 else exec_stmt($0, 0) # pass to immediate execution 185 print "\nOK" 186 } 187 }