From 18e7c1fedf506ad569e6ad9857ecbd0663e4d8f2 Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 22 Jun 2026 21:52:37 +0300 Subject: [PATCH] init upload --- README.md | 39 +++++++++++++++++ lvtl | 4 ++ lvtl-x.awk | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 README.md create mode 100755 lvtl create mode 100644 lvtl-x.awk diff --git a/README.md b/README.md new file mode 100644 index 0000000..553c50b --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# LVTL-X: a compact and fair VTL-2 port to POSIX AWK + +## About + +LVTL-X is a port of the [VTL-2](https://rosettacode.org/wiki/Category:VTL-2) retro programming language interpreter to the POSIX AWK runtime. Unlike the previous effort, LVTL-W, that only worked in BusyBox and GAWK, this version works in any POSIX-compliant environment. On top of that, LVTL-X is extremely compact and takes under 100 SLOC of portable and readable AWK code. + +## Usage + +The easiest way is to run the `./lvtl` shell wrapper, either stand-alone or with a program file path to preload. You can also specify a non-default AWK interpreter to use with the `AWK` environment variable for the wrapper to use. + +Alternatively, you can run the LVTL-X script directly like this: + +```sh +POSIXLY_CORRECT=1 awk -f lvtl-x.awk -- [/path/to/your/vtl/program.vtl] - +``` + +If running without a wrapper and specifying a program to preload, don't forget to put another `-` after it. + +## Differences from the Altair original + +LVTL-X stands out from other LVTL versions by being closer to the original VTL-2, but still has some important differences that shouldn't be ignored: + +- 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 +- only 26 characters (A-Z) are valid generic variable names +- supports all standard VTL-2 binary operators: +, -, *, /, =, >, < +- 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 + +Nevertheless, any program that's valid on LVTL-X will run on the original VTL-2/02 implementations too. + +## Credits + +Created by Luxferre in 2026, released into the public domain with no warranties. diff --git a/lvtl b/lvtl new file mode 100755 index 0000000..60ee1e0 --- /dev/null +++ b/lvtl @@ -0,0 +1,4 @@ +#!/bin/sh +# LVTL-X wrapper/loader +[ -z "$AWK" ] && AWK=awk +POSIXLY_CORRECT=1 $AWK -f lvtl-x.awk -- "$1" - diff --git a/lvtl-x.awk b/lvtl-x.awk new file mode 100644 index 0000000..af362f1 --- /dev/null +++ b/lvtl-x.awk @@ -0,0 +1,122 @@ +# LVTL-X: port of VTL-2 to POSIX AWK +# +# Usage: POSIXLY_CORRECT=1 awk -f lvtl-x.awk [prog.vtl -] +# (don't forget the - after the program or LVTL will exit upon preloading) +# +# Any program valid for LVTL-X will run on the original VTL-2 and VTL02 too +# +# 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 +# - only 26 characters (A-Z) are valid generic variable names +# - supports all standard VTL-2 binary operators: +, -, *, /, =, >, < +# - 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 2026 (based on LVTL-W from 2023), released into public domain + +function err(m) {print "Fatal error: " m; exit 1} + +# read a generic/system variable +function getvar(varname, c, cmd) { + if(varname == "$") { + cmd = "dd bs=1 count=1 2>/dev/null | od -An -t u1" + c = (cmd | getline c) > 0 ? int(c) : 0 + close(cmd); return c + } + if(varname == "?") { getline c; return evalexpr(c) } + if(varname == "'") return int(rand()*65536) + return (varname in VARS) ? VARS[varname] : 0 +} + +# set a generic/system variable +function setvar(varname, value, qi) { + if(varname == "$") printf("%c", value % 256) + else if(varname == "?") { + if(VARS["\""]) { + qi = index(value, "\"") + printf("%s%s", substr(value, 1, qi - 1), substr(value, qi+1, 1) == ";" ? "" : "\n") + VARS["\""] = 0 + } else printf("%d", value % 65536) + } else if(varname in VARS) { + value %= 65536; if(varname == "#" && value > 0) VARS["!"] = VARS["#"] + 1 + VARS[varname] = value + } else err("invalid variable name!") +} + +# strict LTR evaluator +function evalexpr(expr, tkn, i, l, opex, acc, cop, oprnd, subs) { + opex = 1; l = length(expr) + for(i=1; i<=l; i++) { + tkn = substr(expr, i, 1) + if(tkn == ")") break + if(tkn == " " || tkn == "\t") continue + if(opex = 1 - opex) { + if(index("+-*/=<>&!#", tkn)) cop = tkn + else err("unexpected binary operator at " VARS["#"]) + } else { + if(tkn == "\"") { VARS[tkn] = 1; return substr(expr, i+1) } + if(tkn == ":" || tkn == "(") { + subs = evalexpr(substr(expr, i+1)); oprnd = (tkn == ":") ? SCRATCH[subs] : subs + i += PROCCHARS + } else if(index("0123456789", tkn)) { + oprnd = int(substr(expr, i)); i += length(oprnd) - 1 + } else oprnd = getvar(tkn) + oprnd %= 65536 + 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; acc = int(acc/oprnd) } + else if(cop == "=") acc = (acc == oprnd) + else if(cop == ">") acc = (acc >= oprnd) + else if(cop == "<") acc = (acc < oprnd) + acc = (acc % 65536 + 65536) % 65536 + } + } + PROCCHARS = i; return acc +} + +# run the statement (iterative implementation) +function exec_stmt(stmt, ln, ei, lhs, rhs, vn, lcache) { + while(stmt != "") { + if(!(ei = index(stmt, "="))) return + if((lhs = substr(stmt, 1, ei - 1)) == "" || (rhs = substr(stmt, ei + 1)) == "") return + ei = evalexpr(rhs); vn = substr(lhs, 1, 1) + if(vn == ":") SCRATCH[evalexpr(substr(lhs, 2))] = ei + else setvar(vn, ei) + lcache = ln; ln = VARS["#"] + 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} + VARS["#"] = ln = (ei >= 65536) ? 0 : ln; stmt = PROGLINES[ln] + } else stmt = "" + } +} + +BEGIN { + srand() + for(i=65; i<=90; i++) VARS[sprintf("%c", i)] = 0 + VARS["!"] = VARS["\""] = VARS["#"] = VARS["%"] = VARS["&"] = 0 + VARS["*"] = 65535 + print "LVTL-X by Luxferre\nPress Ctrl+C to exit\n\nOK" +} + +{ + ln = int($1) % 65536; if(NF > 1) $1 = "" + gsub(/^[[:blank:]]+|[[:blank:]]+$/, "") + if(ln > 0) { + if($0 == "" || $0 == ln) delete PROGLINES[ln] + else PROGLINES[ln] = toupper($0) + } else { + if($0 == "0") { + for(i=1; i<65536; i++) if(i in PROGLINES) print i, PROGLINES[i] + } else exec_stmt(toupper($0), 0) + print "\nOK" + } +}