diff --git a/.gitignore b/.gitignore index 651f29f..fd6610f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,11 @@ bin/* build/* .gradle/* TerrarumBuild.jar +My_BASIC_Programs/* +buildapp/out/TerranBASIC* +buildapp/TerranBASIC_linux.* +buildapp/TerranBASIC_macOS.* +buildapp/TerranBASIC_windows.* # Java native errors hs_err_pid* diff --git a/.idea/artifacts/TerranBASIC.xml b/.idea/artifacts/TerranBASIC.xml index d3cfa03..5672138 100644 --- a/.idea/artifacts/TerranBASIC.xml +++ b/.idea/artifacts/TerranBASIC.xml @@ -1,6 +1,6 @@ - $PROJECT_DIR$/out/artifacts/TerranBASIC + $PROJECT_DIR$/out diff --git a/.idea/artifacts/TerrarumTSVM.xml b/.idea/artifacts/TerrarumTSVM.xml index de20b74..ac7aebf 100644 --- a/.idea/artifacts/TerrarumTSVM.xml +++ b/.idea/artifacts/TerrarumTSVM.xml @@ -1,6 +1,6 @@ - $PROJECT_DIR$/out/artifacts/TerrarumTSVM + $PROJECT_DIR$/out diff --git a/TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java b/TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java index 1af328a..308d836 100644 --- a/TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java +++ b/TerranBASICexecutable/src/net/torvald/tsvm/TerranBASIC.java @@ -5,6 +5,8 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import net.torvald.tsvm.peripheral.*; +import java.io.File; +import java.io.IOException; import java.util.HashMap; public class TerranBASIC { @@ -15,7 +17,24 @@ public class TerranBASIC { public static int WIDTH = 640; public static int HEIGHT = 480; + + public static String diskDir = "My_BASIC_Programs"; + + private static void createDirs() { + File[] dirs = { + new File(diskDir) + }; + + for (File it : dirs) { + if (!it.exists()) + it.mkdirs(); + } + } + public static void main(String[] args) { + createDirs(); + + ShaderProgram.pedantic = false; appConfig = new Lwjgl3ApplicationConfiguration(); @@ -30,8 +49,8 @@ public class TerranBASIC { HashMap watchdogs = new HashMap<>(); - VM tbasvm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TBASRelBios.INSTANCE}, 2, watchdogs); - EmulInstance tbasrunner = new EmulInstance(tbasvm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", "assets/disk0", 560, 448); + VM tbasvm = new VM("./assets", 64 << 10, new TheRealWorld(), new VMProgramRom[]{TerranBASICreleaseBios.INSTANCE}, 2, watchdogs); + EmulInstance tbasrunner = new EmulInstance(tbasvm, "net.torvald.tsvm.peripheral.ReferenceGraphicsAdapter", diskDir, 560, 448); new Lwjgl3Application(new VMGUI(tbasrunner, WIDTH, HEIGHT), appConfig); } } diff --git a/assets/bios/basic.js b/assets/bios/basic.js index 6357e05..54d06fe 100644 --- a/assets/bios/basic.js +++ b/assets/bios/basic.js @@ -2,6 +2,7 @@ // Version 1.0 Release Date 2020-12-28 // Version 1.1 Release Date 2021-01-28 // Version 1.2 Release Date 2021-05-05 +// Version 1.2.1 Release Date 2021-12-02 /* Copyright (c) 2020-2021 CuriousTorvald @@ -25,13 +26,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -if (exec_args !== undefined && exec_args[1] !== undefined && exec_args[1].startsWith("-?")) { - println("Usage: basic ") - println("When the optional basic program is set, the interpreter will run the program and then quit if successful, remain open if the program had an error.") - return 0 -} +// show TerranBASIC on the character LCD (aka the window title) +[..."TerranBASIC"].map(s=>s.charCodeAt(0)).forEach((c,i)=>{ + sys.poke(-1025 - i, c) +}) -const THEVERSION = "1.2" +let _BIOS = {} +_BIOS.FIRST_BOOTABLE_PORT=[0,1] + +const THEVERSION = "1.2.1" const PROD = true let INDEX_BASE = 0 @@ -39,7 +42,7 @@ let TRACEON = (!PROD) && true let DBGON = (!PROD) && true let DATA_CURSOR = 0 let DATA_CONSTS = [] -const BASIC_HOME_PATH = "/home/basic/" +const BASIC_HOME_PATH = "/" if (sys.maxmem() < 8192) { println("Out of memory. BASIC requires 8K or more User RAM") @@ -262,10 +265,10 @@ let BasicVar = function(literal, type) { // creates empty tree node let astToString = function(ast, depth, isFinalLeaf) { let l__ = "| " - + let recDepth = depth || 0 if (!isAST(ast)) return "" - + let hastStr = ast.astHash let sb = "" let marker = ("lit" == ast.astType) ? "i" : @@ -297,7 +300,7 @@ let monadToString = function(monad, depth) { (isMonad(e)) ? `M"${e.mHash}"` : e } - + let m = monad.mVal while (1) { sb += elemToStr(m.p) @@ -465,11 +468,11 @@ let findHighestIndex = function(exprTree) { if (it.astType == "defun_args") { let recIndex = it.astValue[0] let ordIndex = it.astValue[1] - + if (recIndex > highestIndex[0]) { highestIndex = [recIndex, 0] } - + if (recIndex == highestIndex[0] && ordIndex > highestIndex[1]) { highestIndex[1] = ordIndex } @@ -489,15 +492,15 @@ let indexDec = function(node, recIndex) { } else return node } -let curryDefun = function(inputTree, inputValue) { +let curryDefun = function(inputTree, inputValue) { let exprTree = cloneObject(inputTree) let value = cloneObject(inputValue) let highestIndex = findHighestIndex(exprTree)[0] - + if (DBGON) { serial.println("[curryDefun] highest index to curry: "+highestIndex) } - + let substitution = new BasicAST() if (isAST(value)) { substitution = value @@ -507,29 +510,29 @@ let curryDefun = function(inputTree, inputValue) { substitution.astType = JStoBASICtype(value) substitution.astValue = value } - + // substitute the highest index with given value /*bF._recurseApplyAST(exprTree, it => { return (it.astType == "defun_args" && it.astValue[0] === highestIndex[0] && it.astValue[1] === highestIndex[1]) ? substitution : it });*/ - + // substitute the highest index [max recIndex, 0] with given value // and if recIndex is same as the highestIndex and ordIndex is greater than zero, // decrement the ordIndex bF._recurseApplyAST(exprTree, it => { return (it.astType == "defun_args" && it.astValue[0] === highestIndex && it.astValue[1] === 0) ? substitution : indexDec(it, highestIndex) }) - + return exprTree } let getMonadEvalFun = (monad) => function(lnum, stmtnum, args, sep) { if (!isMonad(monad)) throw lang.badFunctionCallFormat(lnum, "not a monad") - + if (DBGON) { serial.println("[BASIC.MONADEVAL] monad:") serial.println(monadToString(monad)) } - + if (monad.mType == "funseq") { let arg = args[0] monad.mVal.forEach(f => { @@ -552,7 +555,7 @@ let countArgs = function(defunTree) { if (it.astType == "defun_args" && it.astValue > cnt) cnt = it.astValue }) - + return cnt+1 } let argCheckErr = function(lnum, o) { @@ -717,7 +720,7 @@ bS.getArrayIndexFun = function(lnum, stmtnum, arrayName, array) { let dimcnt = 1 let oldIndexingStr = "" let indexingstr = "" - + dims.forEach(d => { oldIndexingStr = indexingstr indexingstr += `[${d-INDEX_BASE}]` @@ -741,25 +744,25 @@ bS.getArrayIndexFun = function(lnum, stmtnum, arrayName, array) { */ bS.getDefunThunk = function(exprTree, norename) { if (!isRunnable(exprTree)) throw new BASICerror("not a syntax tree") - + // turns funseq-monad into runnable function if (isMonad(exprTree)) return getMonadEvalFun(exprTree) - + let tree = cloneObject(exprTree) // ALWAYS create new tree instance! - + return function(lnum, stmtnum, args, seps) { if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) - + if (!norename) { let argsMap = args.map(it => { //argCheckErr(lnum, it) let rit = resolve(it) return [JStoBASICtype(rit), rit] // substitute for [astType, astValue] }) - + // bind arguments lambdaBoundVars.unshift(argsMap) - + if (DBGON) { serial.println("[BASIC.getDefunThunk.invoke] unthunking: ") serial.println(astToString(tree)) @@ -768,7 +771,7 @@ bS.getDefunThunk = function(exprTree, norename) { serial.println("[BASIC.getDefunThunk.invoke] lambda bound vars:") serial.println(theLambdaBoundVars()) } - + // perform renaming bF._recurseApplyAST(tree, (it) => { if ("defun_args" == it.astType) { @@ -776,33 +779,33 @@ bS.getDefunThunk = function(exprTree, norename) { serial.println("[BASIC.getDefunThunk.invoke] thunk renaming arg-tree branch:") serial.println(astToString(it)) } - + let recIndex = it.astValue[0] let argIndex = it.astValue[1] - + let theArg = lambdaBoundVars[recIndex][argIndex] // instanceof theArg == resolved version of SyntaxTreeReturnObj - + if (theArg !== undefined) { // this "forgiveness" is required to implement currying if (DBGON) { serial.println("[BASIC.getDefunThunk.invoke] thunk renaming-theArg: "+theArg) serial.println(`${Object.entries(theArg)}`) } - + if (theArg[0] === "null") { throw new BASICerror(`Bound variable is ${theArg}; lambdaBoundVars: ${theLambdaBoundVars()}`) } - + it.astValue = theArg[1] it.astType = theArg[0] } - + if (DBGON) { serial.println("[BASIC.getDefunThunk.invoke] thunk successfully renamed arg-tree branch:") serial.println(astToString(it)) } } }) - + if (DBGON) { serial.println("[BASIC.getDefunThunk.invoke] resulting thunk tree:") serial.println(astToString(tree)) @@ -814,42 +817,42 @@ bS.getDefunThunk = function(exprTree, norename) { serial.println(astToString(tree)) } } - + // evaluate new tree if (DBGON) { serial.println("[BASIC.getDefunThunk.invoke] evaluating tree:") } let ret = resolve(bF._executeSyntaxTree(lnum, stmtnum, tree, 0)) - + // unbind previously bound arguments if (!norename) { lambdaBoundVars.shift() } - + return ret } } bS.wrapBuiltinToUsrdefun = function(funcname) { let argCount = bS.builtin[funcname].argc - + if (argCount === undefined) throw new BASICerror(`${funcname} cannot be wrapped into usrdefun`) - + let leaves = [] for (let k = 0; k < argCount; k++) { let l = new BasicAST() l.astLnum = "**" l.astValue = [0,k] l.astType = "defun_args" - + leaves.push(l) } - + let tree = new BasicAST() tree.astLnum = "**" tree.astValue = funcname tree.astType = "function" tree.astLeaves = leaves - + return tree } /* Accepts troValue, assignes to BASIC variable, and returns internal_assign_object @@ -867,6 +870,7 @@ bS.addAsBasicVar = function(lnum, troValue, rh) { let varname = troValue.toUpperCase() //println("input varname: "+varname) if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname) + if (bS.builtin[varname]) throw lang.asgnOnConst(lnum, varname) let type = JStoBASICtype(rh) bS.vars[varname] = new BasicVar(rh, type) return {asgnVarName: varname, asgnValue: rh} @@ -1305,12 +1309,12 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length | leaves: 0 | value: undefined (type: undefined) `----------------- -| +| | ΒΆ Line 10 (string) | | leaves: 0 | | value: what is your name (type: string) | `----------------- -| +| | i Line 10 (literal) | | leaves: 0 | | value: A$ (type: string) @@ -1325,7 +1329,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length | | leaves: 0 | | value: what is your name (type: string) | `----------------- -| +| | i Line 10 (literal) | | leaves: 0 | | value: A$ (type: string) @@ -1593,7 +1597,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length "$" : {argc:2, f:function(lnum, stmtnum, args) { let fn = resolve(args[0]) let value = resolve(args[1]) // FIXME undefined must be allowed as we cannot distinguish between tree-with-value-of-undefined and just undefined - + if (DBGON) { serial.println("[BASIC.BUILTIN.APPLY] applying this function tree... "+fn) serial.println(astToString(fn)) @@ -1601,7 +1605,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length if (value !== undefined) serial.println(Object.entries(value)) } - + if (fn.mType == "funseq") { return getMonadEvalFun(fn)(lnum, stmtnum, [value]) } @@ -1610,8 +1614,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length valueTree.astLnum = lnum valueTree.astType = JStoBASICtype(value) valueTree.astValue = value - - + + let newTree = new BasicAST() newTree.astLnum = lnum newTree.astValue = fn @@ -1622,7 +1626,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length serial.println("[BASIC.BUILTIN.APPLY] Here's your applied tree:") serial.println(astToString(newTree)) } - + return bF._executeSyntaxTree(lnum, stmtnum, newTree, 0) } }}, @@ -1631,7 +1635,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length }}, "REDUCE" : {noprod:1, argc:1, f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, bv => { - if (isAST(bv)) { + if (isAST(bv)) { if (DBGON) { serial.println("[BASIC.BUILTIN.REDUCE] reducing:") serial.println(astToString(bv)) @@ -1640,23 +1644,23 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length serial.println(astToString(tree.astValue)) }*/ } - + let reduced = bF._uncapAST(bv, it => { // TODO beta-eta reduction return it }) - + if (DBGON) { serial.println("[BASIC.BUILTIN.REDUCE] reduced: "+reduced) serial.println(astToString(reduced)) } - + // re-wrap because tree-executor wants encapsulated function let newTree = new BasicAST() newTree.astLnum = lnum newTree.astType = JStoBASICtype(reduced) newTree.astValue = reduced - + return newTree } else { @@ -1673,24 +1677,24 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return twoArg(lnum, stmtnum, args, (ma, a_to_mb) => { if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma)) if (!isRunnable(a_to_mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a usrdefun: got "+JStoBASICtype(a_to_mb)) - + if (DBGON) { serial.println("[BASIC.BIND] binder:") serial.println(monadToString(ma)) serial.println("[BASIC.BIND] bindee:") serial.println(astToString(a_to_mb)) } - + let a = ma.mVal let mb = bS.getDefunThunk(a_to_mb)(lnum, stmtnum, [a]) - + if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand function did not return a monad") - + if (DBGON) { serial.println("[BASIC.BIND] bound monad:") serial.println(monadToString(mb)) } - + return mb }) }}, @@ -1703,17 +1707,17 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return twoArg(lnum, stmtnum, args, (ma, mb) => { if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma)) if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a monad: got "+JStoBASICtype(mb)) - + if (DBGON) { serial.println("[BASIC.BIND] binder:") serial.println(monadToString(ma)) serial.println("[BASIC.BIND] bindee:") serial.println(monadToString(mb)) } - + let a = ma.mVal let b = mb.mVal - + return mb }) }}, @@ -1726,10 +1730,10 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return twoArg(lnum, stmtnum, args, (fa, fb) => { if (!isRunnable(fa)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fa)) if (!isRunnable(fb)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fb)) - + let ma = (isAST(fa)) ? [fa] : fa.mVal let mb = (isAST(fb)) ? [fb] : fb.mVal - + let mc = mb.concat(ma) return new BasicFunSeq(mc) }) @@ -1773,8 +1777,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length }) }}, /** type: (list of function) <*> (a functor) - * Sequnetial application - * + * SEQUENTIAL APPLICATION + * * Can be implemented on pure TerranBASIC using: * APL = [FS, XS, ARR] ~> IF (LEN(FS) == 0) THEN ARR ELSE APL(TAIL(FS), XS, (IF (ARR == UNDEFINED) THEN {} ELSE ARR) # MAP(HEAD(FS), XS)) */ @@ -1791,14 +1795,14 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length }) }}, /** type: (a function) <*> (a functor) - * Infix MAP + * INFIX MAP */ "<$>" : {argc:2, f:function(lnum, stmtnum, args) { return bS.builtin.MAP.f(lnum, stmtnum, args) }}, /** type: (a function/list of functions) <~> (a functor) * SEQUENTIAL CURRY-MAP - * + * * returns a list of functions curried with each element of the functor */ "<~>" : {argc:2, f:function(lnum, stmtnum, args) { @@ -1809,12 +1813,46 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length if (isGenerator(functor)) functor = genToArray(functor) // single function? if (!Array.isArray(fns)) fns = [fns] - + let ret = [] fns.forEach(fn => ret = ret.concat(functor.map(it => bS.builtin["~<"].f(lnum, stmtnum, [fn, it])))) return ret }) }}, +/** Writes to the serial device and blocks until a status code is returned + * @param device number (int), message (str) + * @return status code from the device. + */ +"CPUT" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (devnum, msg) => { + if (!isNumable(devnum)) throw lang.illegalType(lnum, "LH:"+Object.entries(devnum)) + com.sendMessage(devnum, msg) + return com.getStatusCode(devnum) + }) +}}, +/** Reads the entire message (may be larger than 4096 bytes) from the serial device to the scratchpad memory + * @param device number (int), destination pointer (int) + * @return length of the message being read; specified memory will be filled with the actual message + */ +"CGET" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (devnum, ptr) => { + let msg = com.pullMessage(devnum) + let len = msg.length|0 + for (let i = 0; i < len; i++) { + sys.poke(ptr + i, msg.charCodeAt(i)) + } + return len + }) +}}, +/** Gets the status code of the serial device + * @param device number (int) + * @return status code 0..255 + */ +"CSTA" : {argc:2, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (devnum) => { + return com.getStatusCode(devnum) + }) +}}, "OPTIONDEBUG" : {f:function(lnum, stmtnum, args) { oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line) @@ -1831,7 +1869,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return oneArg(lnum, stmtnum, args, (it) => { println(monadToString(it)) }) -}}, +}}, "RESOLVE" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, (it) => { if (isAST(it)) { @@ -1917,7 +1955,7 @@ bF._opPrc = { // function call in itself has highest precedence "`":10, // MJOIN "^":20, - "*":30,"/":30,"\\":20, + "*":30,"/":30,"\\":30, "MOD":40, "+":50,"-":50, "NOT":60,"BNOT":60, @@ -2284,14 +2322,14 @@ bF._tokenise = function(lnum, cmd) { bF._parserElaboration = function(lnum, ltokens, lstates) { let _debugprintElaboration = (!PROD) && true if (_debugprintElaboration) serial.println("@@ ELABORATION @@") - + let tokens = cloneObject(ltokens) let states = cloneObject(lstates) - + let k = 0 // NOTE: malformed numbers (e.g. "_b3", "_", "__") must be re-marked as literal or syntax error - + while (k < states.length) { // using while loop because array size will change during the execution // turn errenously checked as number back into a literal if (states[k] == "num" && !reNumber.test(tokens[k])) @@ -2302,7 +2340,7 @@ bF._parserElaboration = function(lnum, ltokens, lstates) { // turn TRUE and FALSE into boolean else if ((tokens[k].toUpperCase() == "TRUE" || tokens[k].toUpperCase() == "FALSE") && states[k] == "paren") states[k] = "bool" - + // decimalise hex/bin numbers (because Nashorn does not support binary literal) if (states[k] == "num") { if (tokens[k].toUpperCase().startsWith("0B")) { @@ -2312,18 +2350,18 @@ bF._parserElaboration = function(lnum, ltokens, lstates) { k += 1 } - + k = 0; let l = states.length while (k < l) { let lookahead012 = tokens[k]+tokens[k+1]+tokens[k+2] let lookahead01 = tokens[k]+tokens[k+1] - + // turn three consecutive ops into a trigraph if (k < states.length - 3 && states[k] == "op" && states[k+1] == "op" && states[k+2] == "op" && bF._opPrc[lookahead012]) { if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Trigraph (${lookahead012}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`) - + tokens[k] = lookahead012 - + // remove two future elements by splicing them let oldtkn = cloneObject(tokens) let oldsts = cloneObject(states) @@ -2334,9 +2372,9 @@ bF._parserElaboration = function(lnum, ltokens, lstates) { // turn two consecutive ops into a digraph else if (k < states.length - 2 && states[k] == "op" && states[k+1] == "op" && bF._opPrc[lookahead01]) { if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Digraph (${lookahead01}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`) - + tokens[k] = lookahead01 - + // remove two future elements by splicing them let oldtkn = cloneObject(tokens) let oldsts = cloneObject(states) @@ -2350,34 +2388,34 @@ bF._parserElaboration = function(lnum, ltokens, lstates) { k += 1 } - + return {"tokens":tokens, "states":states} } /** * Destructively transforms an AST (won't unpack capsulated trees by default) - * + * * To NOT modify the tree, make sure you're not modifying any properties of the object */ bF._recurseApplyAST = function(tree, action) { if (!isAST(tree)) throw new BASICerror(`tree is not a AST (${tree})`) - + if (tree.astLeaves !== undefined && tree.astLeaves[0] === undefined) { /*if (DBGON) { serial.println(`RECURSE astleaf`) serial.println(astToString(tree)) }*/ - + return action(tree) || tree } else { let newLeaves = tree.astLeaves.map(it => bF._recurseApplyAST(it, action)) - + /*if (DBGON) { serial.println(`RECURSE ast`) serial.println(astToString(tree)) }*/ - + let newTree = action(tree) - + if (newTree !== undefined) { tree.astLnum = newTree.astLnum tree.astValue = newTree.astValue @@ -2404,7 +2442,7 @@ bF._uncapAST = function(tree, action) { it.astType = capTree.astType it.astLeaves = capTree.astLeaves } - + return action(it) }) action(expr) @@ -2417,8 +2455,8 @@ bF._uncapAST = function(tree, action) { line = linenumber , stmt , {":" , stmt} - | linenumber , "REM" , ? basically anything ? -linenumber = digits + | linenumber , "REM" , ? basically anything ? +linenumber = digits stmt = "REM" , ? anything ? @@ -2428,7 +2466,7 @@ stmt = | "(" , stmt , ")" | expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call e.g. NEXT for FOR loop *) - + expr = (* this basically blocks some funny attemps such as using DEFUN as anon function because everything is global in BASIC *) ? empty string ? @@ -2441,46 +2479,46 @@ expr = (* this basically blocks some funny attemps such as using DEFUN as anon f | expr , op , expr | op_uni , expr | kywd , expr - "(" - | function_call - -expr_sans_asgn = ? identical to expr except errors out whenever "=" is found ? - -ident_tuple = "[" , ident , {"," , ident} , "]" - + | function_call + +expr_sans_asgn = ? identical to expr except errors out whenever "=" is found ? + +ident_tuple = "[" , ident , {"," , ident} , "]" + function_call = ident , "(" , [expr , {argsep , expr} , [argsep]] , ")" - | ident , expr , {argsep , expr} , [argsep] -kywd = ? words that exists on the list of predefined function that are not operators ? - -(* don't bother looking at these, because you already know the stuff *) - -argsep = "," | ";" + | ident , expr , {argsep , expr} , [argsep] +kywd = ? words that exists on the list of predefined function that are not operators ? + +(* don't bother looking at these, because you already know the stuff *) + +argsep = "," | ";" ident = alph , [digits] ; (* variable and function names *) lit = alph , [digits] | num | string ; (* ident + numbers and string literals *) op = "^" | "*" | "/" | "\" | "MOD" | "+" | "-" | "<<" | ">>" | "<" | ">" | "<=" | "=<" | ">=" | "=>" | "==" | "<>" | "><" | "MIN" | "MAX" | "BAND" | "BXOR" | "BOR" | "AND" | "OR" | "TO" | "STEP" | "!" | "~" | "#" | "." | "$" | "&" | "~<" | "<$>" | "<*>" - | "<~>" | "~>" | ">>~" | ">>=" | "=" -op_uni = "-" | "+" | "NOT" | "BNOT" | "`" | "@" + | "<~>" | "~>" | ">>~" | ">>=" | "=" +op_uni = "-" | "+" | "NOT" | "BNOT" | "`" | "@" -alph = letter | letter , alph -digits = digit | digit , digits -hexdigits = hexdigit | hexdigit , hexdigits -bindigits = bindigit | bindigit , bindigits +alph = letter | letter , alph +digits = digit | digit , digits +hexdigits = hexdigit | hexdigit , hexdigits +bindigits = bindigit | bindigit , bindigits num = digits | digits , "." , [digits] | "." , digits - | ("0x"|"0X") , hexdigits + | ("0x"|"0X") , hexdigits | ("0b"|"0B") , bindigits ; (* sorry, no e-notation! *) -visible = ? ASCII 0x20 to 0x7E ? -string = '"' , {visible} , '"' +visible = ? ASCII 0x20 to 0x7E ? +string = '"' , {visible} , '"' letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" - | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "_" -digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "_" +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" hexdigit = "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" | "0" | "1" - | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" -bindigit = "0" | "1" + | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" +bindigit = "0" | "1" (* all possible token states: lit num op bool qot paren sep *) (* below are schematic of trees generated after parsing the statements *) @@ -2553,7 +2591,7 @@ bF.parserPrintdbgline = function(icon, msg, lnum, recDepth) { */ bF._parseTokens = function(lnum, tokens, states) { if (tokens.length !== states.length) throw Error("unmatched tokens and states length") - + bF.parserPrintdbg2('Line ', lnum, tokens, states, 0) if (tokens.length !== states.length) throw lang.syntaxfehler(lnum) @@ -2565,7 +2603,7 @@ bF._parseTokens = function(lnum, tokens, states) { let parenStart = -1 let parenEnd = -1 let seps = [] - + // scan for parens and (:)s for (let k = 0; k < tokens.length; k++) { // increase paren depth and mark paren start position @@ -2582,17 +2620,17 @@ bF._parseTokens = function(lnum, tokens, states) { if (parenDepth == 0 && tokens[k] == ":" && states[k] == "seq") seps.push(k) } - + let startPos = [0].concat(seps.map(k => k+1)) let stmtPos = startPos.map((s,i) => {return{start:s, end:(seps[i] || tokens.length)}}) // use end of token position as separator position - + return stmtPos.map((x,i) => { if (stmtPos.length > 1) bF.parserPrintdbgline('Line ', 'Statement #'+(i+1), lnum, 0) - + // check for empty tokens if (x.end - x.start <= 0) throw new ParserError("Malformed Line") - + let tree = bF._parseStmt(lnum, tokens.slice(x.start, x.end), states.slice(x.start, x.end), @@ -2799,7 +2837,7 @@ bF._parseStmt = function(lnum, tokens, states, recDepth) { /*************************************************************************/ // ## case for: - // | expr + // | expr try { bF.parserPrintdbgline('$', 'Trying Expression Call...', lnum, recDepth) return bF._parseExpr(lnum, tokens, states, recDepth + 1) @@ -2825,7 +2863,7 @@ expr = (* this basically blocks some funny attemps such as using DEFUN as anon f | expr , op , expr | op_uni , expr | kywd , expr - "(" - | function_call + | function_call * @return: BasicAST */ @@ -2874,7 +2912,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { let curlyStart = -1 let curlyEnd = -1 let uptkn = "" - + // Scan for unmatched parens and mark off the right operator we must deal with // every function_call need to re-scan because it is recursively called for (let k = 0; k < tokens.length; k++) { @@ -2898,11 +2936,11 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k curlyDepth -= 1 } - + // determine the right operator to deal with if (parenDepth == 0 && curlyDepth == 0) { let uptkn = tokens[k].toUpperCase() - + if (states[k] == "op" && bF.isSemanticLiteral(tokens[k-1], states[k-1]) && ((bF._opPrc[uptkn] > topmostOpPrc) || (!bF._opRh[uptkn] && bF._opPrc[uptkn] == topmostOpPrc)) @@ -2919,7 +2957,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) /*************************************************************************/ - + // ## case for: // | ident_tuple try { @@ -2931,7 +2969,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { if (!(e instanceof ParserError)) throw e bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) } - + /*************************************************************************/ // ## case for: @@ -2940,10 +2978,10 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { bF.parserPrintdbgline('e', "Array", lnum, recDepth) return bF._parseArrayLiteral(lnum, tokens, states, recDepth + 1) } - + /*************************************************************************/ - + // ## case for: // | "(" , [expr] , ")" if (parenStart == 0 && parenEnd == tokens.length - 1) { @@ -2983,7 +3021,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { if (!(e instanceof ParserError)) throw e bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) } - + /*************************************************************************/ // ## case for: @@ -3032,7 +3070,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { return treeHead } - + /*************************************************************************/ // ## case for: @@ -3048,7 +3086,7 @@ bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { /*************************************************************************/ // ## case for: - // | function_call + // | function_call if (topmostOp === undefined) { // don't remove this IF statement! try { bF.parserPrintdbgline('e', "Trying Function Call...", lnum, recDepth) @@ -3073,7 +3111,7 @@ bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) { bF.parserPrintdbg2('{', lnum, tokens, states, recDepth) /*************************************************************************/ - + let parenDepth = 0 let parenStart = -1 let parenEnd = -1 @@ -3115,21 +3153,21 @@ bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) { // unmatched brackets, duh! if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) if (curlyStart == -1) throw new ParserError("not an array") - + /*************************************************************************/ bF.parserPrintdbgline('{', `curlyStart=${curlyStart}, curlyEnd=${curlyEnd}, argSeps=${argSeps}`, lnum, recDepth) - + let argStartPos = [1].concat(argSeps.map(k => k+1)) let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || curlyEnd)}}) // use end of token position as separator position bF.parserPrintdbgline("{", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth) - + let treeHead = new BasicAST() treeHead.astLnum = lnum treeHead.astValue = "ARRAY CONSTRUCTOR" treeHead.astType = "function" - + if (curlyStart == 0 && curlyEnd == 1) { treeHead.astLeaves = [] } @@ -3139,7 +3177,7 @@ bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) { // check for empty tokens if (x.end - x.start < 0) throw new lang.syntaxfehler(lnum) - + return bF._parseExpr(lnum, tokens.slice(x.start, x.end), states.slice(x.start, x.end), @@ -3147,9 +3185,9 @@ bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) { )} ) } - + return treeHead - + } /** Parses following EBNF rule: "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] @@ -3245,36 +3283,36 @@ bF._parseForLoop = function(lnum, tokens, states, recDepth) { let headTkn = tokens[0].toUpperCase() let headSta = states[0] - + let treeHead = new BasicAST() treeHead.astLnum = lnum // ## case for: // ("FOR"|"FOREACH") , expr if (("FOR" == headTkn || "FOREACH" == headTkn) && "lit" == headSta) { - + treeHead.astValue = headTkn treeHead.astType = "function" - + treeHead.astLeaves[0] = bF._parseExpr(lnum, - tokens.slice(1), + tokens.slice(1), states.slice(1), recDepth + 1 ) - + return treeHead } throw new ParserError("not an FOR/FOREACH expression") - + } // END of FOR /** Parses following EBNF rule: -ident_tuple = "[" , ident , ["," , ident] , "]" +ident_tuple = "[" , ident , ["," , ident] , "]" * @return: BasicAST */ bF._parseTuple = function(lnum, tokens, states, recDepth) { bF.parserPrintdbg2(']', lnum, tokens, states, recDepth) - + /*************************************************************************/ let parenDepth = 0 @@ -3295,7 +3333,7 @@ bF._parseTuple = function(lnum, tokens, states, recDepth) { if (parenEnd == -1 && parenDepth == 1) parenEnd = k parenDepth -= 1 } - + // where are the argument separators if (parenDepth == 1 && parenEnd == -1 && states[k] == "sep") argSeps.push(k) @@ -3309,9 +3347,9 @@ bF._parseTuple = function(lnum, tokens, states, recDepth) { if (parenStart != 0 || parenEnd != tokens.length - 1) throw new ParserError("not a Tuple expression") - + /*************************************************************************/ - + let treeHead = new BasicAST() treeHead.astLnum = lnum treeHead.astValue = undefined @@ -3323,13 +3361,13 @@ bF._parseTuple = function(lnum, tokens, states, recDepth) { bF.parserPrintdbgline(']', 'Tuple comma position: '+defunArgDeclSeps, lnum, recDepth) treeHead.astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1)) - + return treeHead } /** Parses following EBNF rule: function_call = ident , "(" , [expr , {argsep , expr} , [argsep]] , ")" - | ident , expr , {argsep , expr} , [argsep] + | ident , expr , {argsep , expr} , [argsep] * @return: BasicAST */ bF._parseFunctionCall = function(lnum, tokens, states, recDepth) { @@ -3344,7 +3382,7 @@ bF._parseFunctionCall = function(lnum, tokens, states, recDepth) { let _argsepsOnLevelOne = [] // argseps collected when parenDepth == 1 let currentParenMode = [] // a stack; must be able to distinguish different kinds of parens as closures use [ this paren ] let depthsOfRoundParen = [] - + // Scan for unmatched parens and mark off the right operator we must deal with // every function_call need to re-scan because it is recursively called for (let k = 0; k < tokens.length; k++) { @@ -3358,7 +3396,7 @@ bF._parseFunctionCall = function(lnum, tokens, states, recDepth) { else if (bF._isParenClose(tokens[k]) && states[k] == "paren") { if (!bF._isMatchingParen(currentParenMode[0], tokens[k])) throw lang.syntaxfehler(lnum, `Opening paren: ${currentParenMode[0]}, closing paren: ${tokens[k]}`) - + if (parenEnd == -1 && parenDepth == 1) parenEnd = k if (currentParenMode[0] == '(') depthsOfRoundParen.pop() parenDepth -= 1; currentParenMode.shift() @@ -3378,12 +3416,12 @@ bF._parseFunctionCall = function(lnum, tokens, states, recDepth) { // this prevents "RND(~~)*K" to be parsed as [RND, (~~)*K] bF.parserPrintdbgline("F", `parenStart: ${parenStart}, parenEnd: ${parenEnd}`, lnum, recDepth) - + /*************************************************************************/ // ## case for: // ident , "(" , [expr , {argsep , expr} , [argsep]] , ")" - // | ident , expr , {argsep , expr} , [argsep] + // | ident , expr , {argsep , expr} , [argsep] bF.parserPrintdbgline("F", `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth) let treeHead = new BasicAST() @@ -3471,9 +3509,9 @@ bF._findDeBruijnIndex = function(varname, offset) { /** * @return: BasicAST */ -bF._pruneTree = function(lnum, tree, recDepth) { +bF._pruneTree = function(lnum, tree, recDepth) { if (tree === undefined) return - + if (DBGON) { serial.println("[Parser.PRUNE] pruning following subtree, lambdaBoundVars = "+Object.entries(lambdaBoundVars)) // theLambdaBoundVars() were not formatted for this use case! serial.println(astToString(tree)) @@ -3482,16 +3520,16 @@ bF._pruneTree = function(lnum, tree, recDepth) { serial.println(astToString(tree.astValue)) } } - + let defunName = undefined - + // catch all the bound variables for function definition if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") { - + let nameTree = tree.astLeaves[0] if (tree.astValue == "DEFUN") { defunName = nameTree.astValue - + if (DBGON) { serial.println("[Parser.PRUNE.~>] met DEFUN, function name: "+defunName) } @@ -3500,9 +3538,9 @@ bF._pruneTree = function(lnum, tree, recDepth) { if (it.astType !== "lit") throw new ParserError("Malformed bound variable for function definition; tree:\n"+astToString(nameTree)) return it.astValue }) - + lambdaBoundVars.unshift(vars) - + if (DBGON) { serial.println("[Parser.PRUNE.~>] added new bound variables: "+Object.entries(lambdaBoundVars)) } @@ -3522,53 +3560,53 @@ bF._pruneTree = function(lnum, tree, recDepth) { tree.astType = "num" tree.astLeaves = [] } - - + + // depth-first run if (tree.astLeaves[0] != undefined) { tree.astLeaves.forEach(it => bF._pruneTree(lnum, it, recDepth + 1)) } - - + + if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") { - + if (tree.astLeaves.length !== 2) throw lang.syntaxfehler(lnum, tree.astLeaves.length+lang.aG) - + let nameTree = tree.astLeaves[0] let exprTree = tree.astLeaves[1] - + // test print new tree if (DBGON) { serial.println("[Parser.PRUNE.~>] closure bound variables: "+Object.entries(lambdaBoundVars)) } - + // rename the parameters bF._recurseApplyAST(exprTree, (it) => { - if (it.astType == "lit" || it.astType == "function") { + if (it.astType == "lit" || it.astType == "function") { // check if parameter name is valid // if the name is invalid, regard it as a global variable (i.e. do nothing) try { let dbi = bF._findDeBruijnIndex(it.astValue) - + if (DBGON) { serial.println(`index for ${it.astValue}: ${dbi}`) } - - + + it.astValue = dbi it.astType = "defun_args" } catch (_) {} } }) - + tree.astType = "usrdefun" tree.astValue = exprTree tree.astLeaves = [] - + lambdaBoundVars.shift() } - + // for DEFUNs, build assign tree such that: // DEFUN F lambda // turns into: @@ -3578,20 +3616,20 @@ bF._pruneTree = function(lnum, tree, recDepth) { nameTree.astLnum = tree.astLnum nameTree.astType = "lit" nameTree.astValue = defunName - + let newTree = new BasicAST() newTree.astLnum = tree.astLnum newTree.astType = "op" newTree.astValue = "=" newTree.astLeaves = [nameTree, tree] - + tree = newTree - + if (DBGON) { serial.println(`[Parser.PRUNE] has DEFUN, function name: ${defunName}`) } } - + if (DBGON) { serial.println("[Parser.PRUNE] pruned subtree:") serial.println(astToString(tree)) @@ -3599,10 +3637,10 @@ bF._pruneTree = function(lnum, tree, recDepth) { serial.println("[Parser.PRUNE] unpacking astValue:") serial.println(astToString(tree.astValue)) } - + serial.println("======================================================\n") } - + return tree } @@ -3643,43 +3681,43 @@ bF._makeRunnableFunctionFromExprTree = function(lnum, stmtnum, expression, args, return [JStoBASICtype(rit), rit] }) lambdaBoundVars.unshift(defunArgs) - + if (_debugExec) { serial.println(recWedge+"usrdefun dereference") serial.println(recWedge+"usrdefun dereference function: ") serial.println(astToString(expression)) serial.println(recWedge+"usrdefun dereference bound vars: "+theLambdaBoundVars()) } - + // insert bound variables to its places let bindVar = function(tree, recDepth) { bF._recurseApplyAST(tree, it => { - + if (_debugExec) { serial.println(recWedge+`usrdefun${recDepth} trying to bind some variables to:`) serial.println(astToString(it)) } - + if (it.astType == "defun_args") { let recIndex = it.astValue[0] - recDepth let varIndex = it.astValue[1] - + if (_debugExec) { serial.println(recWedge+`usrdefun${recDepth} bindvar d(${recIndex},${varIndex})`) } - + let theVariable = undefined try { theVariable = lambdaBoundVars[recIndex][varIndex] } catch (e0) {} - + // this will make partial applying work, but completely remove the ability of catching errors... if (theVariable !== undefined) { it.astValue = theVariable[1] it.astType = theVariable[0] } - + if (_debugExec) { serial.println(recWedge+`usrdefun${recDepth} the bindvar: ${theVariable}`) serial.println(recWedge+`usrdefun${recDepth} modified tree:`) @@ -3692,13 +3730,13 @@ bF._makeRunnableFunctionFromExprTree = function(lnum, stmtnum, expression, args, } }) };bindVar(expression, 0) - - + + if (_debugExec) { serial.println(recWedge+"usrdefun dereference final tree:") serial.println(astToString(expression)) } - + return bS.getDefunThunk(expression, true) } /** @@ -3714,14 +3752,14 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { if (syntaxTree.astLeaves === undefined && syntaxTree.astValue === undefined) { throw new BASICerror("not a syntax tree") } - + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) let _debugExec = (!PROD) && true let _debugPrintCurrentLine = (!PROD) && true let recWedge = ">".repeat(recDepth+1) + " " let tearLine = "\n =====ExecSyntaxTree===== "+("<".repeat(recDepth+1))+"\n" - + if (_debugExec || _debugPrintCurrentLine) serial.println(recWedge+`@@ EXECUTE ${lnum}:${stmtnum} @@`) if (_debugPrintCurrentLine) { serial.println("Syntax Tree in "+lnum+":") @@ -3732,7 +3770,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { // do NOT substitute (syntaxTree.astType == "usrdefun") with isAST; doing so will break (=) operator // calling usrdefun without any args will make leaves[0] to be null-node but not undefined // funseq-monad will be dealt with on (func === undefined) branch - + if (syntaxTree.astValue == undefined && syntaxTree.mVal == undefined) { // empty meaningless parens if (syntaxTree.astLeaves.length > 1) throw Error("WTF") return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth) @@ -3796,7 +3834,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { : (iftest) ? bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[1], recDepth + 1) : bF._troNOP(lnum, stmtnum) - + if (_debugExec) serial.println(tearLine) return r } @@ -3827,13 +3865,13 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { } else { let args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)) - + if (_debugExec) { serial.println(recWedge+`fn caller: "${callerHash}"`) serial.println(recWedge+`fn call name: "${funcName}"`) serial.println(recWedge+"fn call args: "+(args.map(it => (it == undefined) ? it : (it.troType+" "+it.troValue)).join(", "))) } - + // func not in builtins (e.g. array access, user-defined function defuns) if (func === undefined) { var someVar = bS.vars[funcName] @@ -3863,7 +3901,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { throw lang.syntaxfehler(lnum, funcName + " is not a function or an array") } } - + // call whatever the 'func' has whether it's builtin or we just made shit up right above if (func === undefined) { serial.printerr(lnum+` ${funcName} is undefined`) @@ -3871,20 +3909,20 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { } let funcCallResult = func(lnum, stmtnum, args, syntaxTree.astSeps) - + if (funcCallResult instanceof SyntaxTreeReturnObj) return funcCallResult - + let retVal = (funcCallResult instanceof JumpObj) ? funcCallResult.jmpReturningValue : funcCallResult - + let theRealRet = new SyntaxTreeReturnObj( JStoBASICtype(retVal), retVal, (funcCallResult instanceof JumpObj) ? funcCallResult.jmpNext : [lnum, stmtnum + 1] ) - + // unregister variables if (lambdaBoundVarsAppended) lambdaBoundVars.shift() - + if (_debugExec) serial.println(tearLine) return theRealRet } @@ -3951,7 +3989,7 @@ bF._interpretLine = function(lnum, cmd) { let newtoks = bF._parserElaboration(lnum, tokens, states) tokens = newtoks.tokens states = newtoks.states - + // PARSING (SYNTAX ANALYSIS) let syntaxTrees = bF._parseTokens(lnum, tokens, states).map(it => { if (lambdaBoundVars.length != 0) @@ -4066,7 +4104,7 @@ bF.troff = function(args) { } bF.delete = function(args) { if (args.length != 2 && args.length != 3) throw lang.syntaxfehler() - + // stupid Javascript can't even Array.prototype.remove(int) let start = 0; let end = 0 if (args.length == 2) { @@ -4079,7 +4117,7 @@ bF.delete = function(args) { start = args[1]|0 end = args[2]|0 } - + let newcmdbuf = [] cmdbuf.forEach((v,i) => {if (i < start || i > end) newcmdbuf[i]=v}) cmdbuf = newcmdbuf @@ -4160,8 +4198,8 @@ bF.save = function(args) { // SAVE function bF.load = function(args) { // LOAD function if (args[1] === undefined) throw lang.missingOperand var fileOpened = fs.open(args[1], "R") - - + + if (replUsrConfirmed || cmdbuf.length == 0) { if (!fileOpened) { fileOpened = fs.open(args[1]+".BAS", "R") @@ -4194,9 +4232,9 @@ bF.load = function(args) { // LOAD function bF.yes = function() { if (replCmdBuf.length > 0) { replUsrConfirmed = true - + bF[replCmdBuf[0].toLowerCase()](replCmdBuf.slice(1, replCmdBuf.length)) - + replCmdBuf = [] replUsrConfirmed = false } @@ -4217,18 +4255,6 @@ bF.catalog = function(args) { // CATALOG function } Object.freeze(bF) -if (exec_args !== undefined && exec_args[1] !== undefined) { - bF.load(["load", exec_args[1]]) - try { - bF.run() - return 0 - } - catch (e) { - serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`) - println(`${e}`) - } -} - while (!tbasexit) { var line = sys.read().trim() @@ -4258,5 +4284,3 @@ while (!tbasexit) { println(prompt) } } - -0 diff --git a/assets/bios/basic_1.js b/assets/bios/basic_1.js new file mode 100644 index 0000000..218f66b --- /dev/null +++ b/assets/bios/basic_1.js @@ -0,0 +1,3157 @@ +[..."TerranBASIC"].map(s=>s.charCodeAt(0)).forEach((c,i)=>{ + sys.poke(-1025 - i, c) +}) +let _BIOS = {} +_BIOS.FIRST_BOOTABLE_PORT=[0,1] +const THEVERSION = "1.2.1" +const PROD = true +let INDEX_BASE = 0 +let TRACEON = (!PROD) && true +let DBGON = (!PROD) && true +let DATA_CURSOR = 0 +let DATA_CONSTS = [] +const BASIC_HOME_PATH = "/" +if (sys.maxmem() < 8192) { + println("Out of memory. BASIC requires 8K or more User RAM") + throw Error("Out of memory") +} +let vmemsize = sys.maxmem() +let cmdbuf = [] +let gotoLabels = {} +let cmdbufMemFootPrint = 0 +let prompt = "Ok" +let prescan = false +let replCmdBuf = [] +let replUsrConfirmed = false +let lambdaBoundVars = [] +function isNumable(s) { + if (Array.isArray(s)) return false + if (s === undefined) return false + if (typeof s.trim == "function" && s.trim().length == 0) return false + return !isNaN(s) +} +let tonum = (t) => t*1.0 +function cloneObject(o) { return JSON.parse(JSON.stringify(o)) } +class ParserError extends Error { + constructor(...args) { + super(...args) + Error.captureStackTrace(this, ParserError) + } +} +class BASICerror extends Error { + constructor(...args) { + super(...args) + Error.captureStackTrace(this, ParserError) + } +} +let lang = {} +lang.badNumberFormat = Error("Illegal number format") +lang.badOperatorFormat = Error("Illegal operator format") +lang.divByZero = Error("Division by zero") +lang.badFunctionCallFormat = function(line, reason) { + return Error("Illegal function call" + ((line) ? " in "+line : "") + ((reason) ? ": "+reason : "")) +} +lang.unmatchedBrackets = Error("Unmatched brackets") +lang.missingOperand = Error("Missing operand") +lang.noSuchFile = Error("No such file") +lang.outOfData = function(line) { + return Error("Out of DATA"+(line !== undefined ? (" in "+line) : "")) +} +lang.nextWithoutFor = function(line, varname) { + return Error("NEXT "+((varname !== undefined) ? ("'"+varname+"'") : "")+"without FOR in "+line) +} +lang.syntaxfehler = function(line, reason) { + return Error("Syntax error" + ((line !== undefined) ? (" in "+line) : "") + ((reason !== undefined) ? (": "+reason) : "")) +} +lang.illegalType = function(line, obj) { + return Error("Type mismatch" + ((obj !== undefined) ? ` "${obj} (typeof ${typeof obj})"` : "") + ((line !== undefined) ? (" in "+line) : "")) + } +lang.refError = function(line, obj) { + serial.printerr(`${line} Unresolved reference:`) + serial.printerr(` object: ${obj}, typeof: ${typeof obj}`) + if (obj !== null && obj !== undefined) serial.printerr(` entries: ${Object.entries(obj)}`) + return Error("Unresolved reference" + ((obj !== undefined) ? ` "${obj}"` : "") + ((line !== undefined) ? (" in "+line) : "")) +} +lang.nowhereToReturn = function(line) { return "RETURN without GOSUB in " + line } +lang.errorinline = function(line, stmt, errobj) { + return Error('Error'+((line !== undefined) ? (" in "+line) : "")+' on "'+stmt+'": '+errobj) +} +lang.parserError = function(line, errorobj) { + return Error("Parser error in " + line + ": " + errorobj) +} +lang.outOfMem = function(line) { + return Error("Out of memory in " + line) +} +lang.dupDef = function(line, varname) { + return Error("Duplicate definition"+((varname !== undefined) ? (" on "+varname) : "")+" in "+line) +} +lang.asgnOnConst = function(line, constname) { + return Error('Trying to modify constant "'+constname+'" in '+line) +} +lang.subscrOutOfRng = function(line, object, index, maxlen) { + return Error("Subscript out of range"+(object !== undefined ? (' for "'+object+'"') : '')+(index !== undefined ? (` (index: ${index}, len: ${maxlen})`) : "")+(line !== undefined ? (" in "+line) : "")) +} +lang.aG = " arguments were given" +lang.ord = function(n) { + if (n % 10 == 1 && n % 100 != 11) return n+"st" + if (n % 10 == 2 && n % 100 != 12) return n+"nd" + if (n % 10 == 3 && n % 100 != 13) return n+"rd" + return n+"th" +} +Object.freeze(lang) +let fs = {} +fs._close = function(portNo) { + com.sendMessage(portNo, "CLOSE") +} +fs._flush = function(portNo) { + com.sendMessage(portNo, "FLUSH") +} +fs.open = function(path, operationMode) { + var port = _BIOS.FIRST_BOOTABLE_PORT + fs._flush(port[0]); fs._close(port[0]) + var mode = operationMode.toUpperCase() + if (mode != "R" && mode != "W" && mode != "A") { + throw Error("Unknown file opening mode: " + mode) + } + com.sendMessage(port[0], "OPEN"+mode+'"'+BASIC_HOME_PATH+path+'",'+port[1]) + let response = com.getStatusCode(port[0]) + return (response == 0) +} +fs.readAll = function() { + var port = _BIOS.FIRST_BOOTABLE_PORT + com.sendMessage(port[0], "READ") + var response = com.getStatusCode(port[0]) + if (135 == response) { + throw Error("File not opened") + } + if (response < 0 || response >= 128) { + throw Error("Reading a file failed with "+response) + } + return com.pullMessage(port[0]) +} +fs.write = function(string) { + var port = _BIOS.FIRST_BOOTABLE_PORT + com.sendMessage(port[0], "WRITE"+string.length) + var response = com.getStatusCode(port[0]) + if (135 == response) { + throw Error("File not opened") + } + if (response < 0 || response >= 128) { + throw Error("Writing a file failed with "+response) + } + com.sendMessage(port[0], string) + fs._flush(port[0]); fs._close(port[0]) +} +Object.freeze(fs) +let getUsedMemSize = function() { + var varsMemSize = 0 + Object.entries(bS.vars).forEach((pair, i) => { + var object = pair[1] + if (Array.isArray(object)) { + varsMemSize += object.length * 8 + } + else if (!isNaN(object)) varsMemSize += 8 + else if (typeof object === "string" || object instanceof String) varsMemSize += object.length + else varsMemSize += 1 + }) + return varsMemSize + cmdbufMemFootPrint +} +let reLineNum = /^[0-9]+ / +let reNumber = /([0-9]*[.][0-9]+[eE]*[\-+0-9]*[fF]*|[0-9]+[.eEfF][0-9+\-]*[fF]?)|([0-9]+(\_[0-9])*)|(0[Xx][0-9A-Fa-f_]+)|(0[Bb][01_]+)/ +let reNum = /[0-9]+/ +let tbasexit = false +const termWidth = con.getmaxyx()[1] +const termHeight = con.getmaxyx()[0] +const greetText = (termWidth >= 70) ? `Terran BASIC ${THEVERSION} `+String.fromCharCode(179)+" Scratchpad Memory: "+vmemsize+" bytes" : `Terran BASIC ${THEVERSION}` +const greetLeftPad = (termWidth - greetText.length - 6) >> 1 +const greetRightPad = termWidth - greetLeftPad - greetText.length - 6 +con.clear() +con.color_pair(253,255) +print(' ');con.addch(17) +con.color_pair(0,253) +con.move(1,4) +print(" ".repeat(greetLeftPad)+greetText+" ".repeat(greetRightPad)) +con.color_pair(253,255) +con.addch(16);con.curs_right();print(' ') +con.move(3,1) +con.color_pair(253,255) +println(prompt) +let BasicVar = function(literal, type) { + this.bvLiteral = literal + this.bvType = type +} +let astToString = function(ast, depth, isFinalLeaf) { + let l__ = "| " + let recDepth = depth || 0 + if (!isAST(ast)) return "" + let hastStr = ast.astHash + let sb = "" + let marker = ("lit" == ast.astType) ? "i" : + ("op" == ast.astType) ? "+" : + ("string" == ast.astType) ? "@" : + ("num" == ast.astType) ? "$" : + ("array" == ast.astType) ? "[" : + ("defun_args" === ast.astType) ? "d" : "f" + sb += l__.repeat(recDepth)+`${marker} ${ast.astLnum}: "${ast.astValue}" (astType:${ast.astType}); leaves: ${ast.astLeaves.length}; hash:"${hastStr}"\n` + for (var k = 0; k < ast.astLeaves.length; k++) { + sb += astToString(ast.astLeaves[k], recDepth + 1, k == ast.astLeaves.length - 1) + if (ast.astSeps[k] !== undefined) + sb += l__.repeat(recDepth)+` sep:${ast.astSeps[k]}\n` + } + sb += l__.repeat(recDepth)+"`"+"-".repeat(22)+'\n' + return sb +} +let monadToString = function(monad, depth) { + let recDepth = depth || 0 + let l__ = " " + let sb = ` M"${monad.mHash}"(${monad.mType}): ` + sb += (monad.mVal === undefined) ? "(undefined)" : (isAST(monad.mVal)) ? `f"${monad.mVal.astHash}"` : (isMonad(monad.mVal)) ? `M"${monad.mVal.mHash}"` : monad.mVal + return sb +} +let arrayToString = function(a) { + let acc = "" + for (let k = 0; k < a.length; k++) { + if (k > 0) acc += "," + acc += (Array.isArray(a[k])) ? arrayToString(a[k]) : a[k] + } + return "{"+acc+"}" +} +let theLambdaBoundVars = function() { + let sb = "" + lambdaBoundVars.forEach((it,i) => { + if (i > 0) sb += ' |' + sb += ` ${i} [` + it.forEach((it,i) => { + if (i > 0) sb += ',' + sb += `${it[0]}:${it[1]}` + }) + sb += ']' + }) + return sb +} +let makeBase32Hash = ()=>[1,2,3,4,5].map(i=>"YBNDRFG8EJKMCPQXOTLVWIS2A345H769"[Math.random()*32|0]).join('') +let BasicAST = function() { + this.astLnum = 0 + this.astLeaves = [] + this.astSeps = [] + this.astValue = undefined + this.astType = "null" + this.astHash = makeBase32Hash() +} +let isAST = (object) => (object === undefined) ? false : object.astLeaves !== undefined && object.astHash !== undefined +let isRunnable = (object) => isAST(object) || object.mType == "funseq" +let BasicFunSeq = function(f) { + if (!Array.isArray(f) || !isAST(f[0])) throw new BASICerror("Not an array of functions") + this.mHash = makeBase32Hash() + this.mType = "funseq" + this.mVal = f +} +let BasicListMonad = function(m) { + this.mHash = makeBase32Hash() + this.mType = "list" + this.mVal = [m] +} +let BasicMemoMonad = function(m) { + this.mHash = makeBase32Hash() + this.mType = "value" + this.mVal = m + this.seq = undefined +} +let isMonad = (o) => (o === undefined) ? false : (o.mType !== undefined) +let literalTypes = ["string", "num", "bool", "array", "generator", "usrdefun", "monad"] +let resolve = function(variable) { + if (variable === undefined) return undefined + if (variable.troType === undefined) { + if (isNumable(variable)) return tonum(variable) + if (Array.isArray(variable)) return variable + if (isGenerator(variable) || isAST(variable) || isMonad(variable)) return variable + if (typeof variable == "object") + throw Error(`BasicIntpError: trying to resolve unknown object '${variable}' with entries ${Object.entries(variable)}`) + return variable + } + else if (variable.troType === "internal_arrindexing_lazy") + return eval("variable.troValue.arrFull"+variable.troValue.arrKey) + else if (literalTypes.includes(variable.troType) || variable.troType.startsWith("internal_")) + return variable.troValue + else if (variable.troType == "lit") { + if (isNumable(variable.troValue)) { + return tonum(variable.troValue) + } + else if (bS.builtin[variable.troValue] !== undefined) { + return bS.wrapBuiltinToUsrdefun(variable.troValue) + } + else { + let basicVar = bS.vars[variable.troValue] + if (basicVar === undefined) throw lang.refError(undefined, variable.troValue) + if (basicVar.bvLiteral === "") return "" + return (basicVar !== undefined) ? basicVar.bvLiteral : undefined + } + } + else if (variable.troType == "null") + return undefined + else + throw Error("BasicIntpError: unknown variable/object with type "+variable.troType+", with value "+variable.troValue) +} +let findHighestIndex = function(exprTree) { + let highestIndex = [-1,-1] + let rec = function(exprTree) { + bF._recurseApplyAST(exprTree, it => { + if (it.astType == "defun_args") { + let recIndex = it.astValue[0] + let ordIndex = it.astValue[1] + if (recIndex > highestIndex[0]) { + highestIndex = [recIndex, 0] + } + if (recIndex == highestIndex[0] && ordIndex > highestIndex[1]) { + highestIndex[1] = ordIndex + } + } + else if (isAST(it.astValue)) { + rec(it.astValue) + } + }) + };rec(exprTree) + return highestIndex +} +let indexDec = function(node, recIndex) { + if (node.astType == "defun_args" && node.astValue[0] === recIndex) { + let newNode = cloneObject(node) + newNode.astValue[1] -= 1 + return newNode + } + else return node +} +let curryDefun = function(inputTree, inputValue) { + let exprTree = cloneObject(inputTree) + let value = cloneObject(inputValue) + let highestIndex = findHighestIndex(exprTree)[0] + if (DBGON) { + serial.println("[curryDefun] highest index to curry: "+highestIndex) + } + let substitution = new BasicAST() + if (isAST(value)) { + substitution = value + } + else { + substitution.astLnum = "??" + substitution.astType = JStoBASICtype(value) + substitution.astValue = value + } + + bF._recurseApplyAST(exprTree, it => { + return (it.astType == "defun_args" && it.astValue[0] === highestIndex && it.astValue[1] === 0) ? substitution : indexDec(it, highestIndex) + }) + return exprTree +} +let getMonadEvalFun = (monad) => function(lnum, stmtnum, args, sep) { + if (!isMonad(monad)) throw lang.badFunctionCallFormat(lnum, "not a monad") + if (DBGON) { + serial.println("[BASIC.MONADEVAL] monad:") + serial.println(monadToString(monad)) + } + if (monad.mType == "funseq") { + let arg = args[0] + monad.mVal.forEach(f => { + arg = bS.getDefunThunk(f)(lnum, stmtnum, [arg]) + }) + return arg + } + else { + return monad.mVal + } +} +let listMonConcat = function(parentm, childm) { + parentm.mVal = parentm.mVal.concat(childm.mVal) + return parentm +} +let countArgs = function(defunTree) { + let cnt = -1 + bF._recurseApplyAST(defunTree, it => { + if (it.astType == "defun_args" && it.astValue > cnt) + cnt = it.astValue + }) + return cnt+1 +} +let argCheckErr = function(lnum, o) { + if (o === undefined) throw lang.refError(lnum, "(variable is undefined)") + if (o.troType == "null") throw lang.refError(lnum, o) + if (o.troType == "lit" && bS.builtin[o.troValue] !== undefined) return + if (o.troType == "lit" && bS.vars[o.troValue] === undefined) throw lang.refError(lnum, o.troValue) +} +let oneArg = function(lnum, stmtnum, args, action) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0]) + return action(rsvArg0) +} +let oneArgNul = function(lnum, stmtnum, args, action) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var rsvArg0 = resolve(args[0]) + return action(rsvArg0) +} +let oneArgNum = function(lnum, stmtnum, args, action) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0], 1) + if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, args[0]) + return action(rsvArg0) +} +let twoArg = function(lnum, stmtnum, args, action) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0]) + argCheckErr(lnum, args[1]) + var rsvArg1 = resolve(args[1]) + return action(rsvArg0, rsvArg1) +} +let twoArgNul = function(lnum, stmtnum, args, action) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var rsvArg0 = resolve(args[0]) + var rsvArg1 = resolve(args[1]) + return action(rsvArg0, rsvArg1) +} +let twoArgNum = function(lnum, stmtnum, args, action) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0], 1) + if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, "LH:"+Object.entries(args[0])) + argCheckErr(lnum, args[1]) + var rsvArg1 = resolve(args[1], 1) + if (!isNumable(rsvArg1)) throw lang.illegalType(lnum, "RH:"+Object.entries(args[1])) + return action(rsvArg0, rsvArg1) +} +let threeArg = function(lnum, stmtnum, args, action) { + if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0]) + argCheckErr(lnum, args[1]) + var rsvArg1 = resolve(args[1]) + argCheckErr(lnum, args[2]) + var rsvArg2 = resolve(args[2]) + return action(rsvArg0, rsvArg1, rsvArg2) +} +let threeArgNum = function(lnum, stmtnum, args, action) { + if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG) + argCheckErr(lnum, args[0]) + var rsvArg0 = resolve(args[0], 1) + if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, "1H:"+Object.entries(args[0])) + argCheckErr(lnum, args[1]) + var rsvArg1 = resolve(args[1], 1) + if (!isNumable(rsvArg1)) throw lang.illegalType(lnum, "2H:"+Object.entries(args[1])) + argCheckErr(lnum, args[2]) + var rsvArg2 = resolve(args[2], 1) + if (!isNumable(rsvArg2)) throw lang.illegalType(lnum, "3H:"+Object.entries(args[2])) + return action(rsvArg0, rsvArg1, rsvArg2) +} +let varArg = function(lnum, stmtnum, args, action) { + var rsvArg = args.map((it) => { + argCheckErr(lnum, it) + var r = resolve(it) + return r + }) + return action(rsvArg) +} +let varArgNum = function(lnum, stmtnum, args, action) { + var rsvArg = args.map((it) => { + argCheckErr(lnum, it) + var r = resolve(it) + if (isNaN(r)) throw lang.illegalType(lnum, r) + return r + }) + return action(rsvArg) +} +let makeIdFun = () => { + return JSON.parse(`{"astLnum":"**","astLeaves":[],"astSeps":[],"astValue":[0,0],"astType":"defun_args","astHash":"IDFUN"}`) +} +let _basicConsts = { + "NIL": new BasicVar([], "array"), + "PI": new BasicVar(Math.PI, "num"), + "TAU": new BasicVar(Math.PI * 2.0, "num"), + "EULER": new BasicVar(Math.E, "num"), + "ID": new BasicVar(makeIdFun(), "usrdefun"), + "UNDEFINED": new BasicVar(undefined, "null"), + "TRUE": new BasicVar(true, "bool"), + "FALSE": new BasicVar(false, "bool") +} +Object.freeze(_basicConsts) +let initBvars = function() { + return cloneObject(_basicConsts) +} +let ForGen = function(s,e,t) { + this.start = s + this.end = e + this.step = t || 1 + this.current = this.start + this.stepsgn = (this.step > 0) ? 1 : -1 +} +let isGenerator = (o) => o.start !== undefined && o.end !== undefined && o.step !== undefined && o.stepsgn !== undefined +let genToArray = (gen) => { + let a = [] + let cur = gen.start + while (cur*gen.stepsgn + gen.step*gen.stepsgn <= (gen.end + gen.step)*gen.stepsgn) { + a.push(cur) + cur += gen.step + } + return a +} +let genHasHext = (o) => o.current*o.stepsgn + o.step*o.stepsgn <= (o.end + o.step)*o.stepsgn +let genGetNext = (gen, mutated) => { + if (mutated !== undefined) gen.current = tonum(mutated) + gen.current += gen.step + return genHasHext(gen) ? gen.current : undefined +} +let genToString = (gen) => `Generator: ${gen.start} to ${gen.end}`+((gen.step !== 1) ? ` step ${gen.step}` : '') +let genReset = (gen) => { gen.current = gen.start } +let bS = {} +bS.gosubStack = [] +bS.forLnums = {} +bS.forStack = [] +bS.vars = initBvars() +bS.rnd = 0 +bS.getDimSize = function(array, dim) { + var dims = [] + while (true) { + dims.push(array.length) + if (Array.isArray(array[0])) + array = array[0] + else + break + } + return dims[dim] +} +bS.getArrayIndexFun = function(lnum, stmtnum, arrayName, array) { + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + return function(lnum, stmtnum, args, seps) { + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + return varArgNum(lnum, stmtnum, args, (dims) => { + if (TRACEON) serial.println("ar dims: "+dims) + let dimcnt = 1 + let oldIndexingStr = "" + let indexingstr = "" + dims.forEach(d => { + oldIndexingStr = indexingstr + indexingstr += `[${d-INDEX_BASE}]` + var testingArr = eval(`array${indexingstr}`) + if (testingArr === undefined) + throw lang.subscrOutOfRng(lnum, `${arrayName}${oldIndexingStr} (${lang.ord(dimcnt)} dim)`, d-INDEX_BASE, bS.getDimSize(array, dimcnt-1)) + dimcnt += 1 + }) + if (TRACEON) + serial.println("ar indexedValue = "+`array${indexingstr}`) + return {arrFull: array, arrName: arrayName, arrKey: indexingstr} + }) + } +} +bS.getDefunThunk = function(exprTree, norename) { + if (!isRunnable(exprTree)) throw new BASICerror("not a syntax tree") + if (isMonad(exprTree)) return getMonadEvalFun(exprTree) + let tree = cloneObject(exprTree) + return function(lnum, stmtnum, args, seps) { + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + if (!norename) { + let argsMap = args.map(it => { + let rit = resolve(it) + return [JStoBASICtype(rit), rit] + }) + lambdaBoundVars.unshift(argsMap) + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] unthunking: ") + serial.println(astToString(tree)) + serial.println("[BASIC.getDefunThunk.invoke] thunk args:") + serial.println(argsMap) + serial.println("[BASIC.getDefunThunk.invoke] lambda bound vars:") + serial.println(theLambdaBoundVars()) + } + bF._recurseApplyAST(tree, (it) => { + if ("defun_args" == it.astType) { + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] thunk renaming arg-tree branch:") + serial.println(astToString(it)) + } + let recIndex = it.astValue[0] + let argIndex = it.astValue[1] + let theArg = lambdaBoundVars[recIndex][argIndex] + if (theArg !== undefined) { + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] thunk renaming-theArg: "+theArg) + serial.println(`${Object.entries(theArg)}`) + } + if (theArg[0] === "null") { + throw new BASICerror(`Bound variable is ${theArg}; lambdaBoundVars: ${theLambdaBoundVars()}`) + } + it.astValue = theArg[1] + it.astType = theArg[0] + } + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] thunk successfully renamed arg-tree branch:") + serial.println(astToString(it)) + } + } + }) + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] resulting thunk tree:") + serial.println(astToString(tree)) + } + } + else { + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] no rename, resulting thunk tree:") + serial.println(astToString(tree)) + } + } + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] evaluating tree:") + } + let ret = resolve(bF._executeSyntaxTree(lnum, stmtnum, tree, 0)) + if (!norename) { + lambdaBoundVars.shift() + } + return ret + } +} +bS.wrapBuiltinToUsrdefun = function(funcname) { + let argCount = bS.builtin[funcname].argc + if (argCount === undefined) throw new BASICerror(`${funcname} cannot be wrapped into usrdefun`) + let leaves = [] + for (let k = 0; k < argCount; k++) { + let l = new BasicAST() + l.astLnum = "**" + l.astValue = [0,k] + l.astType = "defun_args" + leaves.push(l) + } + let tree = new BasicAST() + tree.astLnum = "**" + tree.astValue = funcname + tree.astType = "function" + tree.astLeaves = leaves + return tree +} +bS.addAsBasicVar = function(lnum, troValue, rh) { + if (troValue.arrFull !== undefined) { + let arr = eval("troValue.arrFull"+troValue.arrKey) + if (Array.isArray(arr)) throw lang.subscrOutOfRng(lnum, arr) + eval("troValue.arrFull"+troValue.arrKey+"=rh") + return {asgnVarName: troValue.arrName, asgnValue: rh} + } + else { + let varname = troValue.toUpperCase() + if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname) + if (bS.builtin[varname]) throw lang.asgnOnConst(lnum, varname) + let type = JStoBASICtype(rh) + bS.vars[varname] = new BasicVar(rh, type) + return {asgnVarName: varname, asgnValue: rh} + } +} +bS.builtin = { +"=" : {argc:2, f:function(lnum, stmtnum, args) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var troValue = args[0].troValue + var rh = resolve(args[1]) + if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue) + if (isNumable(rh)) rh = tonum(rh) + + return bS.addAsBasicVar(lnum, troValue, rh) +}}, +"IN" : {argc:2, f:function(lnum, stmtnum, args) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var troValue = args[0].troValue + var rh = resolve(args[1]) + if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue) + if (troValue.arrFull !== undefined) { + throw lang.syntaxfehler(lnum) + } + else { + var varname = troValue.toUpperCase() + var type = JStoBASICtype(rh) + if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname) + return {asgnVarName: varname, asgnValue: rh} + } +}}, +"==" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNul(lnum, stmtnum, args, (lh,rh) => lh == rh) +}}, +"<>" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh) +}}, +"><" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh) +}}, +"<=" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh) +}}, +"=<" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh) +}}, +">=" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh) +}}, +"=>" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh) +}}, +"<" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh < rh) +}}, +">" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh > rh) +}}, +"<<" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh << rh) +}}, +">>" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >>> rh) +}}, +"UNARYMINUS" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => -lh) +}}, +"UNARYPLUS" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => +lh) +}}, +"UNARYLOGICNOT" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => !(lh)) +}}, +"UNARYBNOT" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => ~(lh)) +}}, +"BAND" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh & rh) +}}, +"BOR" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh | rh) +}}, +"BXOR" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh ^ rh) +}}, +"!" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => { + if (Array.isArray(rh)) { + return [lh].concat(rh) + } + else if (rh.mType === "list") { + rh.mVal = [lh].concat(rh.mVal) + return rh + } + else throw lang.illegalType(lnum, rh) + }) +}}, +"~" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => { + if (Array.isArray(lh)) { + return lh.concat([rh]) + } + else if (lh.mType === "list") { + lh.mVal = [lh.mVal].concat([rh]) + return lh + } + else throw lang.illegalType(lnum, lh) + }) +}}, +"#" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => { + if (Array.isArray(lh) && Array.isArray(rh)) { + return lh.concat(rh) + } + else if (lh.mType == "list" && rh.mType == "list") { + let newMval = lh.mVal.concat(rh.mVal) + return new BasicListMonad(newMval) + } + else + throw lang.illegalType(lnum) + }) +}}, +"+" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => (!isNaN(lh) && !isNaN(rh)) ? (tonum(lh) + tonum(rh)) : (lh + rh)) +}}, +"-" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh - rh) +}}, +"*" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh * rh) +}}, +"/" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => { + if (rh == 0) throw lang.divByZero + return lh / rh + }) +}}, +"\\" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => { + if (rh == 0) throw lang.divByZero + return (lh / rh)|0 + }) +}}, +"MOD" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => { + if (rh == 0) throw lang.divByZero + return lh % rh + }) +}}, +"^" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (lh,rh) => { + let r = Math.pow(lh, rh) + if (isNaN(r)) throw lang.badFunctionCallFormat(lnum) + if (!isFinite(r)) throw lang.divByZero + return r + }) +}}, +"TO" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (from, to) => new ForGen(from, to, 1)) +}}, +"STEP" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (gen, step) => { + if (!isGenerator(gen)) throw lang.illegalType(lnum, gen) + return new ForGen(gen.start, gen.end, step) + }) +}}, +"DIM" : {f:function(lnum, stmtnum, args) { + return varArgNum(lnum, stmtnum, args, (revdims) => { + let dims = revdims.reverse() + let arraydec = "Array(dims[0]).fill(0)" + for (let k = 1; k < dims.length; k++) { + arraydec = `Array(dims[${k}]).fill().map(_=>${arraydec})` + } + return eval(arraydec) + }) +}}, +"ARRAY CONSTRUCTOR" : {f:function(lnum, stmtnum, args) { + return args.map(v => resolve(v)) +}}, +"PRINT" : {argc:1, f:function(lnum, stmtnum, args, seps) { + if (args.length == 0) + println() + else { + for (var llll = 0; llll < args.length; llll++) { + if (llll >= 1) { + if (seps[llll - 1] == ",") print("\t") + } + var rsvArg = resolve(args[llll]) + let printstr = "" + if (Array.isArray(rsvArg)) + printstr = arrayToString(rsvArg) + else if (rsvArg === undefined || rsvArg === "") + printstr = "" + else if (rsvArg.toString !== undefined) + printstr = rsvArg.toString() + else + printstr = rsvArg + print(printstr) + if (TRACEON) serial.println("[BASIC.PRINT] "+printstr) + } + } + if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println() +}}, +"EMIT" : {argc:1, f:function(lnum, stmtnum, args, seps) { + if (args.length == 0) + println() + else { + for (var llll = 0; llll < args.length; llll++) { + if (llll >= 1) { + if (seps[llll - 1] == ",") print("\t") + } + var rsvArg = resolve(args[llll]) + if (rsvArg === undefined && args[llll] !== undefined && args[llll].troType != "null") throw lang.refError(lnum, args[llll].troValue) + let printstr = "" + if (rsvArg === undefined) + print("") + else if (isNumable(rsvArg)) { + let c = con.getyx() + con.addch(tonum(rsvArg)) + con.move(c[0],c[1]+1) + } + else if (rsvArg.toString !== undefined) + print(rsvArg.toString()) + else + printstr = (rsvArg) + if (TRACEON) serial.println("[BASIC.EMIT] "+printstr) + } + } + if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println() +}}, +"POKE" : {argc:2, f:function(lnum, stmtnum, args) { + twoArgNum(lnum, stmtnum, args, (lh,rh) => sys.poke(lh, rh)) +}}, +"PEEK" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => sys.peek(lh)) +}}, +"GOTO" : {argc:1, f:function(lnum, stmtnum, args) { + let line = gotoLabels[args[0].troValue] + if (line === undefined) line = resolve(args[0]) + if (line < 0) throw lang.syntaxfehler(lnum, line) + return new JumpObj(line, 0, lnum, line) +}}, +"GOSUB" : {argc:1, f:function(lnum, stmtnum, args) { + let line = gotoLabels[args[0].troValue] + if (line === undefined) line = resolve(args[0]) + if (line < 0) throw lang.syntaxfehler(lnum, line) + bS.gosubStack.push([lnum, stmtnum + 1]) + return new JumpObj(line, 0, lnum, line) +}}, +"RETURN" : {f:function(lnum, stmtnum, args) { + var r = bS.gosubStack.pop() + if (r === undefined) throw lang.nowhereToReturn(lnum) + return new JumpObj(r[0], r[1], lnum, r) +}}, +"CLEAR" : {argc:0, f:function(lnum, stmtnum, args) { + bS.vars = initBvars() +}}, +"PLOT" : {argc:3, f:function(lnum, stmtnum, args) { + threeArgNum(lnum, stmtnum, args, (xpos, ypos, color) => graphics.plotPixel(xpos, ypos, color)) +}}, +"AND" : {argc:2, f:function(lnum, stmtnum, args) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var rsvArg = args.map((it) => resolve(it)) + rsvArg.forEach((v) => { + if (v === undefined) throw lang.refError(lnum, v) + if (typeof v !== "boolean") throw lang.illegalType(lnum, v) + }) + var argum = rsvArg.map((it) => { + if (it === undefined) throw lang.refError(lnum, it) + return it + }) + return argum[0] && argum[1] +}}, +"OR" : {argc:2, f:function(lnum, stmtnum, args) { + if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG) + var rsvArg = args.map((it) => resolve(it)) + rsvArg.forEach((v) => { + if (v === undefined) throw lang.refError(lnum, v.value) + if (typeof v !== "boolean") throw lang.illegalType(lnum, v) + }) + var argum = rsvArg.map((it) => { + if (it === undefined) throw lang.refError(lnum, it) + return it + }) + return argum[0] || argum[1] +}}, +"RND" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => { + if (!(args.length > 0 && args[0].troValue === 0)) + bS.rnd = Math.random() + return bS.rnd + }) +}}, +"ROUND" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => Math.round(lh)) +}}, +"FLOOR" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh)) +}}, +"INT" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh)) +}}, +"CEIL" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => Math.ceil(lh)) +}}, +"FIX" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => (lh|0)) +}}, +"CHR" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => String.fromCharCode(lh)) +}}, +"TEST" : {argc:1, f:function(lnum, stmtnum, args) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + return !!resolve(args[0]) +}}, +"FOREACH" : {f:function(lnum, stmtnum, args) { + var asgnObj = resolve(args[0]) + if (asgnObj === undefined) throw lang.syntaxfehler(lnum) + if (!Array.isArray(asgnObj.asgnValue)) throw lang.illegalType(lnum, asgnObj) + var varname = asgnObj.asgnVarName + bS.vars[varname] = new BasicVar(asgnObj.asgnValue[0], JStoBASICtype(asgnObj.asgnValue.shift())) + bS.vars["for var "+varname] = new BasicVar(asgnObj.asgnValue, "array") + bS.forLnums[varname] = [lnum, stmtnum] + bS.forStack.push(varname) +}}, +"FOR" : {f:function(lnum, stmtnum, args) { + var asgnObj = resolve(args[0]) + if (asgnObj === undefined) throw lang.syntaxfehler(lnum) + if (!isGenerator(asgnObj.asgnValue)) throw lang.illegalType(lnum, typeof asgnObj) + var varname = asgnObj.asgnVarName + var generator = asgnObj.asgnValue + bS.vars[varname] = new BasicVar(generator.start, "num") + bS.vars["for var "+varname] = new BasicVar(generator, "generator") + bS.forLnums[varname] = [lnum, stmtnum] + bS.forStack.push(varname) +}}, +"NEXT" : {f:function(lnum, stmtnum, args) { + if (args.length == 0 || (args.length == 1 && args.troType == "null")) { + var forVarname = bS.forStack.pop() + if (forVarname === undefined) { + throw lang.nextWithoutFor(lnum) + } + if (TRACEON) serial.println("[BASIC.FOR] looping "+forVarname) + var forVar = bS.vars["for var "+forVarname].bvLiteral + if (isGenerator(forVar)) + bS.vars[forVarname].bvLiteral = genGetNext(forVar, bS.vars[forVarname].bvLiteral) + else + bS.vars[forVarname].bvLiteral = forVar.shift() + if ((bS.vars[forVarname].bvLiteral !== undefined)) { + bS.forStack.push(forVarname) + let forLnum = bS.forLnums[forVarname] + return new JumpObj(forLnum[0], forLnum[1]+1, lnum, [forLnum[0], forLnum[1]+1]) + } + else { + if (isGenerator(forVar)) + bS.vars[forVarname].bvLiteral = forVar.current + else + bS.vars[forVarname] === undefined + return new JumpObj(lnum, stmtnum + 1, lnum, [lnum, stmtnum + 1]) + } + } + throw lang.syntaxfehler(lnum, "extra arguments for NEXT") +}}, +/* +10 input;"what is your name";a$ +Β£ Line 10 (function) +| leaves: 3 +| value: input (type: string) +Β£ Line 0 (null) +| leaves: 0 +| value: undefined (type: undefined) +`----------------- +| +| ΒΆ Line 10 (string) +| | leaves: 0 +| | value: what is your name (type: string) +| `----------------- +| +| i Line 10 (literal) +| | leaves: 0 +| | value: A$ (type: string) +| `----------------- +`----------------- +10 input "what is your name";a$ +Β£ Line 10 (function) +| leaves: 2 +| value: input (type: string) +| ΒΆ Line 10 (string) +| | leaves: 0 +| | value: what is your name (type: string) +| `----------------- +| +| i Line 10 (literal) +| | leaves: 0 +| | value: A$ (type: string) +| `----------------- +`----------------- +*/ +"INPUT" : {argc:1, f:function(lnum, stmtnum, args) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + let troValue = args[0].troValue + print("? "); var rh = sys.read().trim() + if (!isNaN(rh)) rh = tonum(rh) + return bS.addAsBasicVar(lnum, troValue, rh) +}}, +"CIN" : {argc:0, f:function(lnum, stmtnum, args) { + return sys.read().trim() +}}, +"END" : {argc:0, f:function(lnum, stmtnum, args) { + serial.println("Program terminated in "+lnum) + return new JumpObj(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1, lnum, undefined) +}}, +"SPC" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => " ".repeat(lh)) +}}, +"LEFT" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (str, len) => str.substring(0, len)) +}}, +"MID" : {argc:3, f:function(lnum, stmtnum, args) { + return threeArg(lnum, stmtnum, args, (str, start, len) => str.substring(start-INDEX_BASE, start-INDEX_BASE+len)) +}}, +"RIGHT" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (str, len) => str.substring(str.length - len, str.length)) +}}, +"SGN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => (it > 0) ? 1 : (it < 0) ? -1 : 0) +}}, +"ABS" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.abs(it)) +}}, +"SIN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.sin(it)) +}}, +"COS" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.cos(it)) +}}, +"TAN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.tan(it)) +}}, +"EXP" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.exp(it)) +}}, +"ASN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.asin(it)) +}}, +"ACO" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.acos(it)) +}}, +"ATN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.atan(it)) +}}, +"SQR" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.sqrt(it)) +}}, +"CBR" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.cbrt(it)) +}}, +"SINH" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.sinh(it)) +}}, +"COSH" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.cosh(it)) +}}, +"TANH" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.tanh(it)) +}}, +"LOG" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (it) => Math.log(it)) +}}, +"RESTORE" : {argc:0, f:function(lnum, stmtnum, args) { + DATA_CURSOR = 0 +}}, +"READ" : {argc:1, f:function(lnum, stmtnum, args) { + if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG) + let troValue = args[0].troValue + let rh = DATA_CONSTS[DATA_CURSOR++] + if (rh === undefined) throw lang.outOfData(lnum) + return bS.addAsBasicVar(lnum, troValue, rh) +}}, +"DGET" : {argc:0, f:function(lnum, stmtnum, args) { + let r = DATA_CONSTS[DATA_CURSOR++] + if (r === undefined) throw lang.outOfData(lnum) + return r +}}, +"OPTIONBASE" : {f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (lh) => { + if (lh != 0 && lh != 1) throw lang.syntaxfehler(line) + INDEX_BASE = lh|0 + }) +}}, +"DATA" : {f:function(lnum, stmtnum, args) { + if (prescan) { + args.forEach(it => DATA_CONSTS.push(resolve(it))) + } +}}, +"MAP" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fn, functor) => { + if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn)) + if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : "")) + if (isGenerator(functor)) functor = genToArray(functor) + return functor.map(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it])) + }) +}}, +"FOLD" : {argc:3, f:function(lnum, stmtnum, args) { + return threeArg(lnum, stmtnum, args, (fn, init, functor) => { + if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn)) + if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, `not a mappable type '${Object.entries(args[2])}': `+functor+((typeof functor == "object") ? Object.entries(functor) : "")) + if (isGenerator(functor)) functor = genToArray(functor) + let akku = init + functor.forEach(it => { + akku = bS.getDefunThunk(fn)(lnum, stmtnum, [akku, it]) + }) + return akku + }) +}}, +"FILTER" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fn, functor) => { + if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn)) + if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, `not a mappable type '${Object.entries(args[1])}': `+functor+((typeof functor == "object") ? Object.entries(functor) : (typeof functor))) + if (isGenerator(functor)) functor = genToArray(functor) + return functor.filter(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it])) + }) +}}, +"DO" : {f:function(lnum, stmtnum, args) { + return args[args.length - 1] +}}, +"LABEL" : {f:function(lnum, stmtnum, args) { + if (prescan) { + let labelname = args[0].troValue + if (labelname === undefined) throw lang.syntaxfehler(lnum, "empty LABEL") + gotoLabels[labelname] = lnum + } +}}, +"ON" : {f:function(lnum, stmtnum, args) { + if (args[2] === undefined) throw lang.syntaxfehler(lnum) + let jmpFun = args.shift() + let testvalue = resolve(args.shift())-INDEX_BASE + let jmpTarget = args[testvalue] + if (jmpFun !== "GOTO" && jmpFun !== "GOSUB") + throw lang.badFunctionCallFormat(lnum, `Not a jump statement: ${jmpFun}`) + if (jmpTarget === undefined) + return undefined + return bS.builtin[jmpFun].f(lnum, stmtnum, [jmpTarget]) +}}, +"MIN" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => (lh > rh) ? rh : lh) +}}, +"MAX" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (lh,rh) => (lh < rh) ? rh : lh) +}}, +"GETKEYSDOWN" : {argc:0, f:function(lnum, stmtnum, args) { + let keys = [] + sys.poke(-40, 255) + for (let k = -41; k >= -48; k--) { + keys.push(sys.peek(k)) + } + return keys +}}, +"~<" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fn, value) => { + if (!isAST(fn)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function: got "+JStoBASICtype(fn)) + if (DBGON) { + serial.println("[BASIC.BUILTIN.CURRY] currying this function tree...") + serial.println(astToString(fn)) + serial.println("[BASIC.BUILTIN.CURRY] with this value: "+value) + serial.println(Object.entries(value)) + } + let curriedTree = curryDefun(fn, value) + if (DBGON) { + serial.println("[BASIC.BUILTIN.CURRY] Here's your curried tree:") + serial.println(astToString(curriedTree)) + } + return curriedTree + }) +}}, +"TYPEOF" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNul(lnum, stmtnum, args, bv => { + if (bv === undefined) return "undefined" + if (bv.bvType === undefined || !(bv instanceof BasicVar)) { + let typestr = JStoBASICtype(bv) + if (typestr == "monad") + return bv.mType+"-"+typestr + else return typestr + } + return bv.bvType + }) +}}, +"LEN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined) throw lang.illegalType() + return lh.length + }) +}}, +"HEAD" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined || lh.length < 1) throw lang.illegalType() + return lh[0] + }) +}}, +"TAIL" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined || lh.length < 1) throw lang.illegalType() + return lh.slice(1, lh.length) + }) +}}, +"INIT" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined || lh.length < 1) throw lang.illegalType() + return lh.slice(0, lh.length - 1) + }) +}}, +"LAST" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined || lh.length < 1) throw lang.illegalType() + return lh[lh.length - 1] + }) +}}, +"CLS" : {argc:0, f:function(lnum, stmtnum, args) { + con.clear() +}}, +"CLPX" : {argc:0, f:function(lnum, stmtnum, args) { + graphics.clearPixels(255) +}}, +"$" : {argc:2, f:function(lnum, stmtnum, args) { + let fn = resolve(args[0]) + let value = resolve(args[1]) + if (DBGON) { + serial.println("[BASIC.BUILTIN.APPLY] applying this function tree... "+fn) + serial.println(astToString(fn)) + serial.println("[BASIC.BUILTIN.APPLY] with this value: "+value) + if (value !== undefined) + serial.println(Object.entries(value)) + } + if (fn.mType == "funseq") { + return getMonadEvalFun(fn)(lnum, stmtnum, [value]) + } + else { + let valueTree = new BasicAST() + valueTree.astLnum = lnum + valueTree.astType = JStoBASICtype(value) + valueTree.astValue = value + let newTree = new BasicAST() + newTree.astLnum = lnum + newTree.astValue = fn + newTree.astType = "usrdefun" + newTree.astLeaves = [valueTree] + if (DBGON) { + serial.println("[BASIC.BUILTIN.APPLY] Here's your applied tree:") + serial.println(astToString(newTree)) + } + return bF._executeSyntaxTree(lnum, stmtnum, newTree, 0) + } +}}, +"&" : {argc:2, f:function(lnum, stmtnum, args) { + return bS.builtin["$"].f(lnum, stmtnum, [args[1], args[0]].concat(args.slice(2))) +}}, +"REDUCE" : {noprod:1, argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, bv => { + if (isAST(bv)) { + if (DBGON) { + serial.println("[BASIC.BUILTIN.REDUCE] reducing:") + serial.println(astToString(bv)) + } + let reduced = bF._uncapAST(bv, it => { + return it + }) + if (DBGON) { + serial.println("[BASIC.BUILTIN.REDUCE] reduced: "+reduced) + serial.println(astToString(reduced)) + } + let newTree = new BasicAST() + newTree.astLnum = lnum + newTree.astType = JStoBASICtype(reduced) + newTree.astValue = reduced + return newTree + } + else { + return bv + } + }) +}}, +">>=" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (ma, a_to_mb) => { + if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma)) + if (!isRunnable(a_to_mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a usrdefun: got "+JStoBASICtype(a_to_mb)) + if (DBGON) { + serial.println("[BASIC.BIND] binder:") + serial.println(monadToString(ma)) + serial.println("[BASIC.BIND] bindee:") + serial.println(astToString(a_to_mb)) + } + let a = ma.mVal + let mb = bS.getDefunThunk(a_to_mb)(lnum, stmtnum, [a]) + if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand function did not return a monad") + if (DBGON) { + serial.println("[BASIC.BIND] bound monad:") + serial.println(monadToString(mb)) + } + return mb + }) +}}, +">>~" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (ma, mb) => { + if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma)) + if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a monad: got "+JStoBASICtype(mb)) + if (DBGON) { + serial.println("[BASIC.BIND] binder:") + serial.println(monadToString(ma)) + serial.println("[BASIC.BIND] bindee:") + serial.println(monadToString(mb)) + } + let a = ma.mVal + let b = mb.mVal + return mb + }) +}}, +"." : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fa, fb) => { + if (!isRunnable(fa)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fa)) + if (!isRunnable(fb)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fb)) + let ma = (isAST(fa)) ? [fa] : fa.mVal + let mb = (isAST(fb)) ? [fb] : fb.mVal + let mc = mb.concat(ma) + return new BasicFunSeq(mc) + }) +}}, +"MLIST" : {noprod:1, argc:1, f:function(lnum, stmtnum, args) { + return oneArgNul(lnum, stmtnum, args, fn => { + return new BasicListMonad([fn]) + }) +}}, +"MRET" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArgNul(lnum, stmtnum, args, fn => { + return new BasicMemoMonad(fn) + }) +}}, +"MJOIN" : {argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, m => { + if (!isMonad(m)) throw lang.illegalType(lnum, m) + return m.mVal + }) +}}, +"GOTOYX" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (y, x) => { + con.move(y + (1-INDEX_BASE),x + (1-INDEX_BASE)) + }) +}}, +"TEXTFORE" : {argc:2, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, col => { + print(String.fromCharCode(27,91)+"38;5;"+(col|0)+"m") + }) +}}, +"TEXTBACK" : {argc:2, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, col => { + print(String.fromCharCode(27,91)+"48;5;"+(col|0)+"m") + }) +}}, +"<*>" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fns, functor) => { + if (!Array.isArray(fns) || !isRunnable(fns[0])) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fns)) + if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : "")) + if (isGenerator(functor)) functor = genToArray(functor) + let ret = [] + fns.forEach(fn => ret = ret.concat(functor.map(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it])))) + return ret + }) +}}, +"<$>" : {argc:2, f:function(lnum, stmtnum, args) { + return bS.builtin.MAP.f(lnum, stmtnum, args) +}}, +"<~>" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (fns, functor) => { + if (!isRunnable(fns) && !(Array.isArray(fns) && isRunnable(fns[0]))) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fns)) + if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : "")) + if (isGenerator(functor)) functor = genToArray(functor) + if (!Array.isArray(fns)) fns = [fns] + let ret = [] + fns.forEach(fn => ret = ret.concat(functor.map(it => bS.builtin["~<"].f(lnum, stmtnum, [fn, it])))) + return ret + }) +}}, +"CPUT" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArg(lnum, stmtnum, args, (devnum, msg) => { + if (!isNumable(devnum)) throw lang.illegalType(lnum, "LH:"+Object.entries(devnum)) + com.sendMessage(devnum, msg) + return com.getStatusCode(devnum) + }) +}}, +"CGET" : {argc:2, f:function(lnum, stmtnum, args) { + return twoArgNum(lnum, stmtnum, args, (devnum, ptr) => { + let msg = com.pullMessage(devnum) + let len = msg.length|0 + for (let i = 0; i < len; i++) { + sys.poke(ptr + i, msg.charCodeAt(i)) + } + return len + }) +}}, +"CSTA" : {argc:2, f:function(lnum, stmtnum, args) { + return oneArgNum(lnum, stmtnum, args, (devnum) => { + return com.getStatusCode(devnum) + }) +}}, +"OPTIONDEBUG" : {f:function(lnum, stmtnum, args) { + oneArgNum(lnum, stmtnum, args, (lh) => { + if (lh != 0 && lh != 1) throw lang.syntaxfehler(line) + DBGON = (1 == lh|0) + }) +}}, +"OPTIONTRACE" : {f:function(lnum, stmtnum, args) { + oneArgNum(lnum, stmtnum, args, (lh) => { + if (lh != 0 && lh != 1) throw lang.syntaxfehler(line) + TRACEON = (1 == lh|0) + }) +}}, +"PRINTMONAD" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, (it) => { + println(monadToString(it)) + }) +}}, +"RESOLVE" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, (it) => { + if (isAST(it)) { + println(lnum+" RESOLVE PRINTTREE") + println(astToString(it)) + if (typeof it.astValue == "object") { + if (isAST(it.astValue)) { + println(lnum+" RESOLVE PRINTTREE ASTVALUE PRINTTREE") + println(astToString(it.astValue)) + } + else { + println(lnum+" RESOLVE PRINTTREE ASTVALUE") + println(it.astValue) + } + } + } + else + println(it) + }) +}}, +"RESOLVEVAR" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + return oneArg(lnum, stmtnum, args, (it) => { + let v = bS.vars[args[0].troValue] + if (v === undefined) println("Undefined variable: "+args[0].troValue) + else println(`type: ${v.bvType}, value: ${v.bvLiteral}`) + }) +}}, +"UNRESOLVE" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + println(args[0]) +}}, +"UNRESOLVE0" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + println(Object.entries(args[0])) +}}, +"TOJSON" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) { + println(JSON.stringify(resolve(args[0]))) +}}, +} +Object.freeze(bS.builtin) +let bF = {} +bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1,"$":1,".":1,"@":1,"\\":1,"%":1,"|":1,"`":1} +bF._uos = {"+":1,"-":1,"NOT":1,"BNOT":1,"^":1,"@":1,"`":1} +bF._isNum = function(code) { + return (code >= 0x30 && code <= 0x39) || code == 0x5F +} +bF._isNum2 = function(code) { + return (code >= 0x30 && code <= 0x39) || code == 0x5F || (code >= 0x41 && code <= 0x46) || (code >= 0x61 && code <= 0x66) +} +bF._isNumSep = function(code) { + return code == 0x2E || code == 0x42 || code == 0x58 || code == 0x62 || code == 0x78 +} +bF._is1o = function(code) { + return bF._1os[String.fromCharCode(code)] +} +bF._isUnary = function(code) { + return bF._uos[String.fromCharCode(code)] +} +bF._isParenOpen = function(code) { + return (code == 0x28 || code == 0x5B || code == 0x7B) || (code == '(' || code == '[' || code == '{') +} +bF._isParenClose = function(code) { + return (code == 0x29 || code == 0x5D || code == 0x7D) || (code == ')' || code == ']' || code == '}') +} +bF._isMatchingParen = function(open, close) { + return (open == '(' && close == ')' || open == '[' && close == ']' || open == '{' && close == '}') +} +bF._isParen = function(code) { + return bF._isParenOpen(code) || bF._isParenClose(code) +} +bF._isSep = function(code) { + return code == 0x2C || code == 0x3B +} +bF._opPrc = { + "`":10, + "^":20, + "*":30,"/":30,"\\":30, + "MOD":40, + "+":50,"-":50, + "NOT":60,"BNOT":60, + "<<":70,">>":70, + "<":80,">":80,"<=":80,"=<":80,">=":80,"=>":80, + "==":90,"<>":90,"><":90, + "MIN":100,"MAX":100, + "BAND":200, + "BXOR":201, + "BOR":202, + "AND":300, + "OR":301, + "TO":400,"STEP":401, + "!":500, + "~":501, + "#":502, + ".": 600, + "$": 600, + "&": 600, + "~<": 601, + "<$>": 602, + "<*>": 602, + "<~>": 602, + "@":700, + "~>": 1000, + ">>~": 1000, + ">>=": 1000, + "=":9999,"IN":9999 +} +bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"~>":1,"$":1,".":1,">>=":1,">>~":1,">!>":1,"@":1,"`":1,"<$>":1} +bF._tokenise = function(lnum, cmd) { + var _debugprintStateTransition = false + var k + var tokens = [] + var states = [] + var sb = "" + var mode = "lit" + if (_debugprintStateTransition) println("@@ TOKENISE @@") + if (_debugprintStateTransition) println("Ln "+lnum+" cmd "+cmd) + for (k = 0; k < cmd.length; k++) { + var char = cmd[k] + var charCode = cmd.charCodeAt(k) + if (_debugprintStateTransition) print("Char: "+char+"("+charCode+"), state: "+mode) + if ("lit" == mode) { + if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "qot" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "paren" + } + else if (" " == char) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "limbo" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "sep" + } + else if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "op" + } + else { + sb += char + } + } + else if ("num" == mode) { + if (bF._isNum(charCode)) { + sb += char + } + else if (bF._isNumSep(charCode)) { + sb += char + mode = "nsep" + } + else if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "qot" + } + else if (" " == char) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "limbo" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "paren" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "sep" + } + else if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "op" + } + else { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "lit" + } + } + else if ("nsep" == mode) { + if (bF._isNum2(charCode)) { + sb += char + mode = "n2" + } + else { + throw lang.syntaxfehler(lnum, lang.badNumberFormat) + } + } + else if ("n2" == mode) { + if (bF._isNum2(charCode)) { + sb += char + } + else if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push("num") + mode = "qot" + } + else if (" " == char) { + tokens.push(sb); sb = ""; states.push("num") + mode = "limbo" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push("num") + mode = "paren" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push("num") + mode = "sep" + } + else if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push("num") + mode = "op" + } + else { + tokens.push(sb); sb = "" + char; states.push("num") + mode = "lit" + } + } + else if ("op" == mode) { + if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "op" + } + else if (bF._isUnary(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + } + else if (bF._isNum(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "num" + } + else if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "qot" + } + else if (" " == char) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "limbo" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "paren" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "sep" + } + else { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "lit" + } + } + else if ("qot" == mode) { + if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "quote_end" + } + else { + sb += char + } + } + else if ("quote_end" == mode) { + if (" " == char) { + sb = "" + mode = "limbo" + } + else if (0x22 == charCode) { + sb = "" + char + mode = "qot" + } + else if (bF._isParen(charCode)) { + sb = "" + char + mode = "paren" + } + else if (bF._isSep(charCode)) { + sb = "" + char + mode = "sep" + } + else if (bF._isNum(charCode)) { + sb = "" + char + mode = "num" + } + else if (bF._is1o(charCode)) { + sb = "" + char + mode = "op" + } + else { + sb = "" + char + mode = "lit" + } + } + else if ("limbo" == mode) { + if (char == " ") { + } + else if (0x22 == charCode) { + mode = "qot" + } + else if (bF._isParen(charCode)) { + sb = "" + char + mode = "paren" + } + else if (bF._isSep(charCode)) { + sb = "" + char + mode = "sep" + } + else if (bF._isNum(charCode)) { + sb = "" + char + mode = "num" + } + else if (bF._is1o(charCode)) { + sb = "" + char + mode = "op" + } + else { + sb = "" + char + mode = "lit" + } + } + else if ("paren" == mode) { + if (char == " ") { + tokens.push(sb); sb = ""; states.push(mode) + mode = "limbo" + } + else if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "qot" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "paren" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "sep" + } + else if (bF._isNum(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "num" + } + else if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "op" + } + else { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "lit" + } + } + else if ("sep" == mode) { + if (char == " ") { + tokens.push(sb); sb = ""; states.push(mode) + mode = "limbo" + } + else if (0x22 == charCode) { + tokens.push(sb); sb = ""; states.push(mode) + mode = "qot" + } + else if (bF._isParen(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "paren" + } + else if (bF._isSep(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "sep" + } + else if (bF._isNum(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "num" + } + else if (bF._is1o(charCode)) { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "op" + } + else { + tokens.push(sb); sb = "" + char; states.push(mode) + mode = "lit" + } + } + else { + throw Error("Unknown parser state: " + mode) + } + if (_debugprintStateTransition) println("->"+mode) + } + if (sb.length > 0) { + tokens.push(sb); states.push(mode) + } + if (tokens[0].length == 0) { + tokens = tokens.slice(1, tokens.length) + states = states.slice(1, states.length) + } + for (k = 0; k < states.length; k++) { + if (states[k] == "o2" || states[k] == "o3") states[k] = "op" + else if (states[k] == "n2" || states[k] == "nsep") states[k] = "num" + } + if (tokens.length != states.length) { + throw new BASICerror("size of tokens and states does not match (line: "+lnum+")\n"+ + tokens+"\n"+states) + } + return { "tokens": tokens, "states": states } +} +bF._parserElaboration = function(lnum, ltokens, lstates) { + let _debugprintElaboration = (!PROD) && true + if (_debugprintElaboration) serial.println("@@ ELABORATION @@") + let tokens = cloneObject(ltokens) + let states = cloneObject(lstates) + let k = 0 + while (k < states.length) { + if (states[k] == "num" && !reNumber.test(tokens[k])) + states[k] = "lit" + else if (states[k] == "lit" && bF._opPrc[tokens[k].toUpperCase()] !== undefined) + states[k] = "op" + else if ((tokens[k].toUpperCase() == "TRUE" || tokens[k].toUpperCase() == "FALSE") && states[k] == "paren") + states[k] = "bool" + if (states[k] == "num") { + if (tokens[k].toUpperCase().startsWith("0B")) { + tokens[k] = parseInt(tokens[k].substring(2, tokens[k].length), 2) + "" + } + } + k += 1 + } + k = 0; let l = states.length + while (k < l) { + let lookahead012 = tokens[k]+tokens[k+1]+tokens[k+2] + let lookahead01 = tokens[k]+tokens[k+1] + if (k < states.length - 3 && states[k] == "op" && states[k+1] == "op" && states[k+2] == "op" && bF._opPrc[lookahead012]) { + if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Trigraph (${lookahead012}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`) + tokens[k] = lookahead012 + let oldtkn = cloneObject(tokens) + let oldsts = cloneObject(states) + tokens = oldtkn.slice(0, k+1).concat(oldtkn.slice(k+3, oldtkn.length)) + states = oldsts.slice(0, k+1).concat(oldsts.slice(k+3, oldsts.length)) + l -= 2 + } + else if (k < states.length - 2 && states[k] == "op" && states[k+1] == "op" && bF._opPrc[lookahead01]) { + if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Digraph (${lookahead01}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`) + tokens[k] = lookahead01 + let oldtkn = cloneObject(tokens) + let oldsts = cloneObject(states) + tokens = oldtkn.slice(0, k+1).concat(oldtkn.slice(k+2, oldtkn.length)) + states = oldsts.slice(0, k+1).concat(oldsts.slice(k+2, oldsts.length)) + l -= 1 + } + else if (tokens[k] == ":" && states[k] == "op") + states[k] = "seq" + k += 1 + } + return {"tokens":tokens, "states":states} +} +bF._recurseApplyAST = function(tree, action) { + if (!isAST(tree)) throw new BASICerror(`tree is not a AST (${tree})`) + if (tree.astLeaves !== undefined && tree.astLeaves[0] === undefined) { + return action(tree) || tree + } + else { + let newLeaves = tree.astLeaves.map(it => bF._recurseApplyAST(it, action)) + let newTree = action(tree) + if (newTree !== undefined) { + tree.astLnum = newTree.astLnum + tree.astValue = newTree.astValue + tree.astSeps = newTree.astSeps + tree.astType = newTree.astType + for (let k = 0; k < tree.astLeaves.length; k++) { + if (newLeaves[k] !== undefined) tree.astLeaves[k] = newLeaves[k] + } + } + } +} +bF._uncapAST = function(tree, action) { + let expr = cloneObject(tree) + bF._recurseApplyAST(expr, it => { + if (isAST(it.astValue)) { + let capTree = bF._uncapAST(it.astValue, action) + it.astLnum = capTree.astLnum + it.astValue = capTree.astValue + it.astSeps = capTree.astSeps + it.astType = capTree.astType + it.astLeaves = capTree.astLeaves + } + return action(it) + }) + action(expr) + return expr +} +bF._EquationIllegalTokens = ["IF","THEN","ELSE","DEFUN","ON"] +bF.isSemanticLiteral = function(token, state) { + return undefined == token || "]" == token || ")" == token || "}" == token || + "qot" == state || "num" == state || "bool" == state || "lit" == state +} +bF.parserDoDebugPrint = (!PROD) && true +bF.parserPrintdbg = any => { if (bF.parserDoDebugPrint) serial.println(any) } +bF.parserPrintdbg2 = function(icon, lnum, tokens, states, recDepth) { + if (bF.parserDoDebugPrint) { + let treeHead = "| ".repeat(recDepth) + bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${tokens.join(' ')}`) + bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${states.join(' ')}`) + } +} +bF.parserPrintdbgline = function(icon, msg, lnum, recDepth) { + if (bF.parserDoDebugPrint) { + let treeHead = "| ".repeat(recDepth) + bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${msg}`) + } +} +bF._parseTokens = function(lnum, tokens, states) { + if (tokens.length !== states.length) throw Error("unmatched tokens and states length") + bF.parserPrintdbg2('Line ', lnum, tokens, states, 0) + if (tokens.length !== states.length) throw lang.syntaxfehler(lnum) + if (tokens[0].toUpperCase() == "REM" && states[0] != "qot") return + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let seps = [] + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + if (parenDepth == 0 && tokens[k] == ":" && states[k] == "seq") + seps.push(k) + } + let startPos = [0].concat(seps.map(k => k+1)) + let stmtPos = startPos.map((s,i) => {return{start:s, end:(seps[i] || tokens.length)}}) + return stmtPos.map((x,i) => { + if (stmtPos.length > 1) + bF.parserPrintdbgline('Line ', 'Statement #'+(i+1), lnum, 0) + if (x.end - x.start <= 0) throw new ParserError("Malformed Line") + let tree = bF._parseStmt(lnum, + tokens.slice(x.start, x.end), + states.slice(x.start, x.end), + 1 + ) + bF.parserPrintdbgline('Tree in ', '\n'+astToString(tree), lnum, 0) + return tree + }) +} +bF._parseStmt = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2('$', lnum, tokens, states, recDepth) + + if (tokens.length == 1 && states.length == 1) { + bF.parserPrintdbgline('$', "Single Word Function Call", lnum, recDepth) + return bF._parseLit(lnum, tokens, states, recDepth + 1, true) + } + let headTkn = tokens[0].toUpperCase() + let headSta = states[0] + let treeHead = new BasicAST() + treeHead.astLnum = lnum + + if (headTkn == "REM" && headSta != "qot") return + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let onGoPos = -1 + let sepsZero = [] + let sepsOne = [] + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + if (parenDepth == 0 && states[k] == "sep") + sepsZero.push(k) + if (parenDepth == 1 && states[k] == "sep") + sepsOne.push(k) + if (parenDepth == 0) { + let tok = tokens[k].toUpperCase() + if (-1 == onGoPos && ("GOTO" == tok || "GOSUB" == tok) && "lit" == states[k]) + onGoPos = k + } + } + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + + try { + bF.parserPrintdbgline('$', "Trying IF Statement...", lnum, recDepth) + return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false) + } + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('$', 'It was NOT!', lnum, recDepth) + } + + if ("DEFUN" == headTkn && "lit" == headSta && + parenStart == 2 && tokens[parenEnd + 1] == "=" && states[parenEnd + 1] == "op" + ) { + bF.parserPrintdbgline('$', 'DEFUN Stmt', lnum, recDepth) + treeHead.astValue = "DEFUN" + treeHead.astType = "function" + if (tokens[1] == "(") { + treeHead.astLeaves[0] = new BasicAST() + treeHead.astLeaves[0].astLnum = lnum + treeHead.astLeaves[0].astType = "lit" + } + else { + bF.parserPrintdbgline('$', 'DEFUN Stmt Function Name:', lnum, recDepth) + treeHead.astLeaves[0] = bF._parseIdent(lnum, [tokens[1]], [states[1]], recDepth + 1) + } + bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments -- ', lnum, recDepth) + let defunArgDeclSeps = sepsOne.filter((i) => i < parenEnd + 1).map(i => i-1).concat([parenEnd - 1]) + bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments comma position: '+defunArgDeclSeps, lnum, recDepth) + treeHead.astLeaves[0].astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1)) + let parseFunction = bF._parseExpr + treeHead.astLeaves[1] = parseFunction(lnum, + tokens.slice(parenEnd + 2, tokens.length), + states.slice(parenEnd + 2, states.length), + recDepth + 1 + ) + return treeHead + } + + if ("ON" == headTkn && "lit" == headSta) { + bF.parserPrintdbgline('$', 'ON Stmt', lnum, recDepth) + if (onGoPos == -1) throw ParserError("Malformed ON Statement") + treeHead.astValue = "ON" + treeHead.astType = "function" + let testvalue = bF._parseExpr(lnum, + tokens.slice(1, onGoPos), + states.slice(1, onGoPos), + recDepth + 1, + true + ) + let functionname = bF._parseExpr(lnum, + [tokens[onGoPos]], + [states[onGoPos]], + recDepth + 1, + true + ) + let onArgSeps = sepsZero.filter(i => (i > onGoPos)) + let onArgStartPos = [onGoPos + 1].concat(onArgSeps.map(k => k + 1)) + let onArgPos = onArgStartPos.map((s,i) => {return{start:s, end: (onArgSeps[i] || tokens.length)}}) + treeHead.astLeaves = [testvalue, functionname].concat(onArgPos.map((x,i) => { + bF.parserPrintdbgline('$', 'ON GOTO/GOSUB Arguments #'+(i+1), lnum, recDepth) + if (x.end - x.start <= 0) throw new ParserError("Malformed ON arguments") + return bF._parseExpr(lnum, + tokens.slice(x.start, x.end), + states.slice(x.start, x.end), + recDepth + 1, + true + ) + })) + return treeHead + } + + if (parenStart == 0 && parenEnd == tokens.length - 1) { + bF.parserPrintdbgline('$', '( Stmt )', lnum, recDepth) + return bF._parseStmt(lnum, + tokens.slice(parenStart + 1, parenEnd), + states.slice(parenStart + 1, parenEnd), + recDepth + 1 + ) + } + + try { + bF.parserPrintdbgline('$', 'Trying Expression Call...', lnum, recDepth) + return bF._parseExpr(lnum, tokens, states, recDepth + 1) + } + catch (e) { + bF.parserPrintdbgline('$', 'Error!', lnum, recDepth) + throw new ParserError("Statement cannot be parsed in "+lnum+": "+e.stack) + } + throw new ParserError("Statement cannot be parsed in "+lnum) +} +bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { + bF.parserPrintdbg2('e', lnum, tokens, states, recDepth) + + if (tokens[0] === undefined && states[0] === undefined) { + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = undefined + treeHead.astType = "null" + return treeHead + } + let headTkn = tokens[0].toUpperCase() + let headSta = states[0] + + if (!bF._EquationIllegalTokens.includes(headTkn) && tokens.length == 1) { + bF.parserPrintdbgline('e', 'Literal Call', lnum, recDepth) + return bF._parseLit(lnum, tokens, states, recDepth + 1) + } + + let topmostOp + let topmostOpPrc = 0 + let operatorPos = -1 + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let curlyDepth = 0 + let curlyStart = -1 + let curlyEnd = -1 + let uptkn = "" + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == "{" && states[k] == "paren") { + curlyDepth += 1 + if (curlyStart == -1 && curlyDepth == 1) curlyStart = k + } + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + else if (tokens[k] == "}" && states[k] == "paren") { + if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k + curlyDepth -= 1 + } + if (parenDepth == 0 && curlyDepth == 0) { + let uptkn = tokens[k].toUpperCase() + if (states[k] == "op" && bF.isSemanticLiteral(tokens[k-1], states[k-1]) && + ((bF._opPrc[uptkn] > topmostOpPrc) || + (!bF._opRh[uptkn] && bF._opPrc[uptkn] == topmostOpPrc)) + ) { + topmostOp = uptkn + topmostOpPrc = bF._opPrc[uptkn] + operatorPos = k + } + } + } + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + + try { + bF.parserPrintdbgline('e', "Trying Tuple...", lnum, recDepth) + return bF._parseTuple(lnum, tokens, states, recDepth + 1, false) + } + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + if (curlyStart == 0 && curlyEnd == tokens.length - 1) { + bF.parserPrintdbgline('e', "Array", lnum, recDepth) + return bF._parseArrayLiteral(lnum, tokens, states, recDepth + 1) + } + + if (parenStart == 0 && parenEnd == tokens.length - 1) { + bF.parserPrintdbgline('e', '( [Expr] )', lnum, recDepth) + return bF._parseExpr(lnum, + tokens.slice(parenStart + 1, parenEnd), + states.slice(parenStart + 1, parenEnd), + recDepth + 1 + ) + } + + try { + bF.parserPrintdbgline('e', "Trying IF Expression...", lnum, recDepth) + return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false) + } + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + try { + bF.parserPrintdbgline('e', "Trying FOR Expression...", lnum, recDepth) + return bF._parseForLoop(lnum, tokens, states, recDepth + 1) + } + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + if (topmostOp !== undefined) { + bF.parserPrintdbgline('e', 'Operators', lnum, recDepth) + if (ifMode && topmostOp == "=") throw lang.syntaxfehler(lnum, "'=' used on IF, did you mean '=='?") + if (ifMode && topmostOp == ":") throw lang.syntaxfehler(lnum, "':' used on IF") + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = topmostOp + treeHead.astType = "op" + if (operatorPos > 0) { + let subtknL = tokens.slice(0, operatorPos) + let substaL = states.slice(0, operatorPos) + let subtknR = tokens.slice(operatorPos + 1, tokens.length) + let substaR = states.slice(operatorPos + 1, tokens.length) + treeHead.astLeaves[0] = bF._parseExpr(lnum, subtknL, substaL, recDepth + 1) + treeHead.astLeaves[1] = bF._parseExpr(lnum, subtknR, substaR, recDepth + 1) + } + else { + if (topmostOp === "-") treeHead.astValue = "UNARYMINUS" + else if (topmostOp === "+") treeHead.astValue = "UNARYPLUS" + else if (topmostOp === "NOT") treeHead.astValue = "UNARYLOGICNOT" + else if (topmostOp === "BNOT") treeHead.astValue = "UNARYBNOT" + else if (topmostOp === "@") treeHead.astValue = "MRET" + else if (topmostOp === "`") treeHead.astValue = "MJOIN" + else throw new ParserError(`Unknown unary op '${topmostOp}'`) + treeHead.astLeaves[0] = bF._parseExpr(lnum, + tokens.slice(operatorPos + 1, tokens.length), + states.slice(operatorPos + 1, states.length), + recDepth + 1 + ) + } + return treeHead + } + + if (bS.builtin[headTkn] && headSta == "lit" && !bF._opPrc[headTkn] && + states[1] != "paren" && tokens[1] != "(" + ) { + bF.parserPrintdbgline('e', 'Builtin Function Call w/o Paren', lnum, recDepth) + return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1) + } + + if (topmostOp === undefined) { + try { + bF.parserPrintdbgline('e', "Trying Function Call...", lnum, recDepth) + return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1) + } + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + } + throw new ParserError(`Expression "${tokens.join(" ")}" cannot be parsed in ${lnum}`) +} +bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2('{', lnum, tokens, states, recDepth) + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let curlyDepth = 0 + let curlyStart = -1 + let curlyEnd = -1 + let argSeps = [] + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == "{" && states[k] == "paren") { + curlyDepth += 1 + if (curlyStart == -1 && curlyDepth == 1) curlyStart = k + } + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + else if (tokens[k] == "}" && states[k] == "paren") { + if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k + curlyDepth -= 1 + } + if (parenDepth == 0 && curlyDepth == 1 && tokens[k] == "," && states[k] == "sep") { + argSeps.push(k) + } + } + if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + if (curlyStart == -1) throw new ParserError("not an array") + bF.parserPrintdbgline('{', `curlyStart=${curlyStart}, curlyEnd=${curlyEnd}, argSeps=${argSeps}`, lnum, recDepth) + let argStartPos = [1].concat(argSeps.map(k => k+1)) + let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || curlyEnd)}}) + bF.parserPrintdbgline("{", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth) + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = "ARRAY CONSTRUCTOR" + treeHead.astType = "function" + if (curlyStart == 0 && curlyEnd == 1) { + treeHead.astLeaves = [] + } + else { + treeHead.astLeaves = argPos.map((x,i) => { + bF.parserPrintdbgline("{", 'Array Element #'+(i+1), lnum, recDepth) + if (x.end - x.start < 0) throw new lang.syntaxfehler(lnum) + return bF._parseExpr(lnum, + tokens.slice(x.start, x.end), + states.slice(x.start, x.end), + recDepth + 1 + )} + ) + } + return treeHead +} +bF._parseIfMode = function(lnum, tokens, states, recDepth, exprMode) { + bF.parserPrintdbg2('/', lnum, tokens, states, recDepth) + let headTkn = tokens[0].toUpperCase() + let headSta = states[0] + let parseFunction = (exprMode) ? bF._parseExpr : bF._parseStmt + let thenPos = -1 + let elsePos = -1 + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + if (parenDepth == 0) { + if (-1 == thenPos && "THEN" == tokens[k].toUpperCase() && "lit" == states[k]) + thenPos = k + else if (-1 == elsePos && "ELSE" == tokens[k].toUpperCase() && "lit" == states[k]) + elsePos = k + } + } + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + let treeHead = new BasicAST() + treeHead.astLnum = lnum + if ("IF" == headTkn && "lit" == headSta) { + if (thenPos == -1) throw lang.syntaxfehler(lnum, "IF without THEN") + treeHead.astValue = "IF" + treeHead.astType = "function" + treeHead.astLeaves[0] = bF._parseExpr(lnum, + tokens.slice(1, thenPos), + states.slice(1, thenPos), + recDepth + 1, + true + ) + treeHead.astLeaves[1] = parseFunction(lnum, + tokens.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length), + states.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length), + recDepth + 1 + ) + if (elsePos != -1) + treeHead.astLeaves[2] = parseFunction(lnum, + tokens.slice(elsePos + 1, tokens.length), + states.slice(elsePos + 1, tokens.length), + recDepth + 1 + ) + return treeHead + } + throw new ParserError("not an IF "+(exprMode) ? "expression" : "statement") +} +bF._parseForLoop = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2('\\', lnum, tokens, states, recDepth) + let headTkn = tokens[0].toUpperCase() + let headSta = states[0] + let treeHead = new BasicAST() + treeHead.astLnum = lnum + if (("FOR" == headTkn || "FOREACH" == headTkn) && "lit" == headSta) { + treeHead.astValue = headTkn + treeHead.astType = "function" + treeHead.astLeaves[0] = bF._parseExpr(lnum, + tokens.slice(1), + states.slice(1), + recDepth + 1 + ) + return treeHead + } + throw new ParserError("not an FOR/FOREACH expression") +} +bF._parseTuple = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2(']', lnum, tokens, states, recDepth) + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let argSeps = [] + for (let k = 0; k < tokens.length; k++) { + if (tokens[k] == "[" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (tokens[k] == "]" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + if (parenDepth == 1 && parenEnd == -1 && states[k] == "sep") + argSeps.push(k) + if (parenStart != -1 && parenEnd != -1) + break + } + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + if (parenStart != 0 || parenEnd != tokens.length - 1) + throw new ParserError("not a Tuple expression") + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = undefined + treeHead.astType = "closure_args" + bF.parserPrintdbgline(']', 'Tuple arguments -- ', lnum, recDepth) + let defunArgDeclSeps = argSeps.map(i => i-1).concat([parenEnd - 1]) + bF.parserPrintdbgline(']', 'Tuple comma position: '+defunArgDeclSeps, lnum, recDepth) + treeHead.astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1)) + return treeHead +} +bF._parseFunctionCall = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2("F", lnum, tokens, states, recDepth) + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let _argsepsOnLevelZero = [] + let _argsepsOnLevelOne = [] + let currentParenMode = [] + let depthsOfRoundParen = [] + for (let k = 0; k < tokens.length; k++) { + if (bF._isParenOpen(tokens[k]) && states[k] == "paren") { + parenDepth += 1; currentParenMode.unshift(tokens[k]) + if (currentParenMode[0] == '(') depthsOfRoundParen.push(parenDepth) + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + else if (bF._isParenClose(tokens[k]) && states[k] == "paren") { + if (!bF._isMatchingParen(currentParenMode[0], tokens[k])) + throw lang.syntaxfehler(lnum, `Opening paren: ${currentParenMode[0]}, closing paren: ${tokens[k]}`) + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + if (currentParenMode[0] == '(') depthsOfRoundParen.pop() + parenDepth -= 1; currentParenMode.shift() + } + if (parenDepth == 0 && states[k] == "sep" && currentParenMode[0] === undefined) + _argsepsOnLevelZero.push(k) + if (parenDepth == depthsOfRoundParen[0] && states[k] == "sep" && currentParenMode[0] == "(") + _argsepsOnLevelOne.push(k) + } + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + let parenUsed = (parenStart == 1) + bF.parserPrintdbgline("F", `parenStart: ${parenStart}, parenEnd: ${parenEnd}`, lnum, recDepth) + + bF.parserPrintdbgline("F", `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth) + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = bF._parseIdent(lnum, [tokens[0]], [states[0]], recDepth + 1).astValue + let argSeps = parenUsed ? _argsepsOnLevelOne : _argsepsOnLevelZero + bF.parserPrintdbgline("F", "argSeps = "+argSeps, lnum, recDepth) + let argStartPos = [1 + (parenUsed)].concat(argSeps.map(k => k+1)) + bF.parserPrintdbgline("F", "argStartPos = "+argStartPos, lnum, recDepth) + let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || (parenUsed ? parenEnd : tokens.length) )}}) + bF.parserPrintdbgline("F", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth) + + treeHead.astLeaves = argPos.map((x,i) => { + bF.parserPrintdbgline("F", `Function Arguments #${i+1} of ${argPos.length}`, lnum, recDepth) + if (x.end - x.start < 0) throw new ParserError("not a function call because it's malformed") + return bF._parseExpr(lnum, + tokens.slice(x.start, x.end), + states.slice(x.start, x.end), + recDepth + 1 + )} + ) + treeHead.astType = "function" + treeHead.astSeps = argSeps.map(i => tokens[i]) + bF.parserPrintdbgline("F", "astSeps = "+treeHead.astSeps, lnum, recDepth) + return treeHead +} +bF._parseIdent = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2('i', lnum, tokens, states, recDepth) + if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array") + if (tokens.length != 1 || states[0] != "lit") throw new ParserError(`illegal tokens '${tokens}' with states '${states}' in ${lnum}`) + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = tokens[0].toUpperCase() + treeHead.astType = "lit" + return treeHead +} +bF._parseLit = function(lnum, tokens, states, recDepth, functionMode) { + bF.parserPrintdbg2('i', lnum, tokens, states, recDepth) + if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array") + if (tokens.length != 1) throw new ParserError("parseLit 1") + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = ("qot" == states[0]) ? tokens[0] : tokens[0].toUpperCase() + treeHead.astType = ("qot" == states[0]) ? "string" : + ("num" == states[0]) ? "num" : + (functionMode) ? "function" : "lit" + return treeHead +} +bF._findDeBruijnIndex = function(varname, offset) { + let recurseIndex = -1 + let orderlyIndex = -1 + for (recurseIndex = 0; recurseIndex < lambdaBoundVars.length; recurseIndex++) { + orderlyIndex = lambdaBoundVars[recurseIndex].findIndex(it => it == varname) + if (orderlyIndex != -1) + return [recurseIndex + (offset || 0), orderlyIndex] + } + throw new ParserError("Unbound variable: "+varname) +} +bF._pruneTree = function(lnum, tree, recDepth) { + if (tree === undefined) return + if (DBGON) { + serial.println("[Parser.PRUNE] pruning following subtree, lambdaBoundVars = "+Object.entries(lambdaBoundVars)) + serial.println(astToString(tree)) + if (isAST(tree) && isAST(tree.astValue)) { + serial.println("[Parser.PRUNE] unpacking astValue:") + serial.println(astToString(tree.astValue)) + } + } + let defunName = undefined + if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") { + let nameTree = tree.astLeaves[0] + if (tree.astValue == "DEFUN") { + defunName = nameTree.astValue + if (DBGON) { + serial.println("[Parser.PRUNE.~>] met DEFUN, function name: "+defunName) + } + } + let vars = nameTree.astLeaves.map((it, i) => { + if (it.astType !== "lit") throw new ParserError("Malformed bound variable for function definition; tree:\n"+astToString(nameTree)) + return it.astValue + }) + lambdaBoundVars.unshift(vars) + if (DBGON) { + serial.println("[Parser.PRUNE.~>] added new bound variables: "+Object.entries(lambdaBoundVars)) + } + } + else if (tree.astValue == "UNARYMINUS" && tree.astType == "op" && + tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" + ) { + tree.astValue = -(tree.astLeaves[0].astValue) + tree.astType = "num" + tree.astLeaves = [] + } + else if (tree.astValue == "UNARYPLUS" && tree.astType == "op" && + tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" + ) { + tree.astValue = +(tree.astLeaves[0].astValue) + tree.astType = "num" + tree.astLeaves = [] + } + if (tree.astLeaves[0] != undefined) { + tree.astLeaves.forEach(it => bF._pruneTree(lnum, it, recDepth + 1)) + } + if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") { + if (tree.astLeaves.length !== 2) throw lang.syntaxfehler(lnum, tree.astLeaves.length+lang.aG) + let nameTree = tree.astLeaves[0] + let exprTree = tree.astLeaves[1] + if (DBGON) { + serial.println("[Parser.PRUNE.~>] closure bound variables: "+Object.entries(lambdaBoundVars)) + } + bF._recurseApplyAST(exprTree, (it) => { + if (it.astType == "lit" || it.astType == "function") { + try { + let dbi = bF._findDeBruijnIndex(it.astValue) + if (DBGON) { + serial.println(`index for ${it.astValue}: ${dbi}`) + } + it.astValue = dbi + it.astType = "defun_args" + } + catch (_) {} + } + }) + tree.astType = "usrdefun" + tree.astValue = exprTree + tree.astLeaves = [] + lambdaBoundVars.shift() + } + + if (defunName) { + let nameTree = new BasicAST() + nameTree.astLnum = tree.astLnum + nameTree.astType = "lit" + nameTree.astValue = defunName + let newTree = new BasicAST() + newTree.astLnum = tree.astLnum + newTree.astType = "op" + newTree.astValue = "=" + newTree.astLeaves = [nameTree, tree] + tree = newTree + if (DBGON) { + serial.println(`[Parser.PRUNE] has DEFUN, function name: ${defunName}`) + } + } + if (DBGON) { + serial.println("[Parser.PRUNE] pruned subtree:") + serial.println(astToString(tree)) + if (isAST(tree) && isAST(tree.astValue)) { + serial.println("[Parser.PRUNE] unpacking astValue:") + serial.println(astToString(tree.astValue)) + } + serial.println("======================================================\n") + } + return tree +} +let JStoBASICtype = function(object) { + if (typeof object === "boolean") return "bool" + else if (object === undefined) return "null" + else if (object.arrName !== undefined) return "internal_arrindexing_lazy" + else if (object.asgnVarName !== undefined) return "internal_assignment_object" + else if (isGenerator(object)) return "generator" + else if (isAST(object)) return "usrdefun" + else if (isMonad(object)) return "monad" + else if (Array.isArray(object)) return "array" + else if (isNumable(object)) return "num" + else if (typeof object === "string" || object instanceof String) return "string" + else throw Error("BasicIntpError: un-translatable object with typeof "+(typeof object)+",\ntoString = "+object+",\nentries = "+Object.entries(object)) +} +let SyntaxTreeReturnObj = function(type, value, nextLine) { + if (nextLine === undefined || !Array.isArray(nextLine)) + throw Error("TODO change format of troNextLine to [linenumber, stmtnumber]") + this.troType = type + this.troValue = value + this.troNextLine = nextLine +} +let JumpObj = function(targetLnum, targetStmtNum, fromLnum, rawValue) { + this.jmpNext = [targetLnum, targetStmtNum] + this.jmpFrom = fromLnum + this.jmpReturningValue = rawValue +} +bF._makeRunnableFunctionFromExprTree = function(lnum, stmtnum, expression, args, recDepth, _debugExec, recWedge) { + let defunArgs = args.map(it => { + let rit = resolve(it) + return [JStoBASICtype(rit), rit] + }) + lambdaBoundVars.unshift(defunArgs) + if (_debugExec) { + serial.println(recWedge+"usrdefun dereference") + serial.println(recWedge+"usrdefun dereference function: ") + serial.println(astToString(expression)) + serial.println(recWedge+"usrdefun dereference bound vars: "+theLambdaBoundVars()) + } + let bindVar = function(tree, recDepth) { + bF._recurseApplyAST(tree, it => { + if (_debugExec) { + serial.println(recWedge+`usrdefun${recDepth} trying to bind some variables to:`) + serial.println(astToString(it)) + } + if (it.astType == "defun_args") { + let recIndex = it.astValue[0] - recDepth + let varIndex = it.astValue[1] + if (_debugExec) { + serial.println(recWedge+`usrdefun${recDepth} bindvar d(${recIndex},${varIndex})`) + } + let theVariable = undefined + try { + theVariable = lambdaBoundVars[recIndex][varIndex] + } + catch (e0) {} + if (theVariable !== undefined) { + it.astValue = theVariable[1] + it.astType = theVariable[0] + } + if (_debugExec) { + serial.println(recWedge+`usrdefun${recDepth} the bindvar: ${theVariable}`) + serial.println(recWedge+`usrdefun${recDepth} modified tree:`) + serial.println(astToString(it)) + } + } + else if (it.astType == "usrdefun") { + bindVar(it.astValue, recDepth + 1) + } + }) + };bindVar(expression, 0) + if (_debugExec) { + serial.println(recWedge+"usrdefun dereference final tree:") + serial.println(astToString(expression)) + } + return bS.getDefunThunk(expression, true) +} +bF._troNOP = function(lnum, stmtnum) { return new SyntaxTreeReturnObj("null", undefined, [lnum, stmtnum+1]) } +bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { + if (syntaxTree == undefined) return bF._troNOP(lnum, stmtnum) + if (syntaxTree.astLeaves === undefined && syntaxTree.astValue === undefined) { + throw new BASICerror("not a syntax tree") + } + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + let _debugExec = (!PROD) && true + let _debugPrintCurrentLine = (!PROD) && true + let recWedge = ">".repeat(recDepth+1) + " " + let tearLine = "\n =====ExecSyntaxTree===== "+("<".repeat(recDepth+1))+"\n" + if (_debugExec || _debugPrintCurrentLine) serial.println(recWedge+`@@ EXECUTE ${lnum}:${stmtnum} @@`) + if (_debugPrintCurrentLine) { + serial.println("Syntax Tree in "+lnum+":") + serial.println(astToString(syntaxTree)) + } + let callingUsrdefun = (syntaxTree.astType == "usrdefun" && syntaxTree.astLeaves[0] !== undefined) + if (syntaxTree.astValue == undefined && syntaxTree.mVal == undefined) { + if (syntaxTree.astLeaves.length > 1) throw Error("WTF") + return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth) + } + else if (syntaxTree.astType == "array" && syntaxTree.astLeaves[0] !== undefined) { + let indexer = bS.getArrayIndexFun(lnum, stmtnum, "substituted array", syntaxTree.astValue) + let args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)) + let retVal = indexer(lnum, stmtnum, args) + if (_debugExec) serial.println(recWedge+`indexing substituted array(${Object.entries(args)}) = ${Object.entries(retVal)}`) + return new SyntaxTreeReturnObj( + JStoBASICtype(retVal), + retVal, + [lnum, stmtnum + 1] + ) + } + else if (syntaxTree.astType == "op" && syntaxTree.astValue == "~>") { + throw new BASICerror("Untended closure") + } + else if (syntaxTree.astType == "function" && syntaxTree.astValue == "DEFUN") { + throw new BASICerror("Untended DEFUN") + } + else if (syntaxTree.astType == "function" || syntaxTree.astType == "op" || callingUsrdefun) { + if (_debugExec) serial.println(recWedge+"function|operator") + if (_debugExec) serial.println(recWedge+astToString(syntaxTree)) + let callerHash = syntaxTree.astHash + let funcName = (typeof syntaxTree.astValue.toUpperCase == "function") ? syntaxTree.astValue.toUpperCase() : "(usrdefun)" + let lambdaBoundVarsAppended = (callingUsrdefun) + let func = (callingUsrdefun) + ? bF._makeRunnableFunctionFromExprTree( + lnum, stmtnum, + cloneObject(syntaxTree.astValue), + syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)), + recDepth, _debugExec, recWedge + ) + : (bS.builtin[funcName] === undefined) + ? undefined + : (!DBGON && bS.builtin[funcName].debugonly) ? "NO_DBG4U" : (PROD && bS.builtin[funcName].noprod) ? "NO_PRODREADY" : bS.builtin[funcName].f + if (func === "NO_DBG4U") throw lang.syntaxfehler(lnum) + if (func === "NO_PRODREADY") throw lang.syntaxfehler(lnum) + if ("IF" == funcName) { + if (syntaxTree.astLeaves.length != 2 && syntaxTree.astLeaves.length != 3) throw lang.syntaxfehler(lnum) + var testedval = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1) + if (_debugExec) { + serial.println(recWedge+"testedval:") + serial.println(recWedge+"type="+testedval.troValue.astType) + serial.println(recWedge+"value="+testedval.troValue.astValue) + serial.println(recWedge+"nextLine="+testedval.troValue.astNextLine) + } + try { + var iftest = bS.builtin["TEST"].f(lnum, stmtnum, [testedval]) + let r = (!iftest && syntaxTree.astLeaves[2] !== undefined) ? + bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[2], recDepth + 1) + : (iftest) ? + bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[1], recDepth + 1) + : bF._troNOP(lnum, stmtnum) + if (_debugExec) serial.println(tearLine) + return r + } + catch (e) { + serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`) + throw lang.errorinline(lnum, "TEST", e) + } + } + else if ("ON" == funcName) { + if (syntaxTree.astLeaves.length < 3) throw lang.badFunctionCallFormat(lnum) + let testValue = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1) + let functionName = syntaxTree.astLeaves[1].astValue + let arrays = [] + for (let k = 2; k < syntaxTree.astLeaves.length; k++) + arrays.push(bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[k], recDepth + 1)) + try { + let r = bS.builtin["ON"].f(lnum, stmtnum, [functionName, testValue].concat(arrays)) + let r2 = new SyntaxTreeReturnObj(JStoBASICtype(r.jmpReturningValue), r.jmpReturningValue, r.jmpNext) + if (_debugExec) serial.println(tearLine) + return r2 + } + catch (e) { + serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`) + throw lang.errorinline(lnum, "ON error", e) + } + } + else { + let args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)) + if (_debugExec) { + serial.println(recWedge+`fn caller: "${callerHash}"`) + serial.println(recWedge+`fn call name: "${funcName}"`) + serial.println(recWedge+"fn call args: "+(args.map(it => (it == undefined) ? it : (it.troType+" "+it.troValue)).join(", "))) + } + if (func === undefined) { + var someVar = bS.vars[funcName] + if (someVar !== undefined && DBGON) { + serial.println(recWedge+`variable dereference of '${funcName}' : ${someVar.bvLiteral} (bvType: ${someVar.bvType})`) + if (typeof someVar.bvLiteral == "object") + serial.println(recWedge+"variable as an object : "+Object.entries(someVar.bvLiteral)) + } + if (someVar === undefined) { + throw lang.syntaxfehler(lnum, funcName + " is undefined") + } + else if ("array" == someVar.bvType) { + func = bS.getArrayIndexFun(lnum, stmtnum, funcName, someVar.bvLiteral) + } + else if ("usrdefun" == someVar.bvType) { + let expression = cloneObject(someVar.bvLiteral) + lambdaBoundVarsAppended = true + func = bF._makeRunnableFunctionFromExprTree(lnum, stmtnum, expression, args, recDepth, _debugExec, recWedge) + } + else if ("monad" == someVar.bvType) { + func = getMonadEvalFun(someVar.bvLiteral) + } + else { + throw lang.syntaxfehler(lnum, funcName + " is not a function or an array") + } + } + if (func === undefined) { + serial.printerr(lnum+` ${funcName} is undefined`) + throw lang.syntaxfehler(lnum, funcName + " is undefined") + } + let funcCallResult = func(lnum, stmtnum, args, syntaxTree.astSeps) + if (funcCallResult instanceof SyntaxTreeReturnObj) return funcCallResult + let retVal = (funcCallResult instanceof JumpObj) ? funcCallResult.jmpReturningValue : funcCallResult + let theRealRet = new SyntaxTreeReturnObj( + JStoBASICtype(retVal), + retVal, + (funcCallResult instanceof JumpObj) ? funcCallResult.jmpNext : [lnum, stmtnum + 1] + ) + if (lambdaBoundVarsAppended) lambdaBoundVars.shift() + if (_debugExec) serial.println(tearLine) + return theRealRet + } + } + else if (syntaxTree.astType == "defun_args") { + if (_debugExec) { + serial.println(recWedge+"defun_args lambda bound vars: "+(lambdaBoundVars === undefined) ? undefined : theLambdaBoundVars()) + serial.println(recWedge+"defun_args defun args: "+syntaxTree.astValue) + } + let recIndex = syntaxTree.astValue[0] + let varIndex = syntaxTree.astValue[1] + let theVar = lambdaBoundVars[recIndex, varIndex] + if (_debugExec) { + serial.println(recWedge+"defun_args thevar: "+(theVar === undefined) ? undefined : Object.entries(theVar)) + serial.println(tearLine) + } + return theVar + } + else if (syntaxTree.astType == "num") { + if (_debugExec) serial.println(recWedge+"num "+(tonum(syntaxTree.astValue))) + let r = new SyntaxTreeReturnObj(syntaxTree.astType, tonum(syntaxTree.astValue), [lnum, stmtnum + 1]) + if (_debugExec) serial.println(tearLine) + return r + } + else if (syntaxTree.astType == "lit" || literalTypes.includes(syntaxTree.astType)) { + if (_debugExec) { + serial.println(recWedge+"literal with astType: "+syntaxTree.astType+", astValue: "+syntaxTree.astValue) + if (isAST(syntaxTree.astValue)) { + serial.println(recWedge+"astValue is a tree, unpacking: \n"+astToString(syntaxTree.astValue)) + } + } + let r = new SyntaxTreeReturnObj(syntaxTree.astType, syntaxTree.astValue, [lnum, stmtnum + 1]) + if (_debugExec) serial.println(tearLine) + return r + } + else if (syntaxTree.astType == "null") { + if (_debugExec) serial.println(recWedge+"null") + let r = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1) + if (_debugExec) serial.println(tearLine) + return r + } + else { + serial.println(recWedge+"Parsing error in "+lnum) + serial.println(recWedge+astToString(syntaxTree)) + throw Error("Parsing error") + } +} +bF._interpretLine = function(lnum, cmd) { + let _debugprintHighestLevel = false + if (cmd.toUpperCase().startsWith("REM")) { + if (_debugprintHighestLevel) serial.println(lnum+" "+cmd) + return undefined + } + let tokenisedObject = bF._tokenise(lnum, cmd) + let tokens = tokenisedObject.tokens + let states = tokenisedObject.states + let newtoks = bF._parserElaboration(lnum, tokens, states) + tokens = newtoks.tokens + states = newtoks.states + let syntaxTrees = bF._parseTokens(lnum, tokens, states).map(it => { + if (lambdaBoundVars.length != 0) + throw new BASICerror("lambdaBoundVars not empty") + return bF._pruneTree(lnum, it, 0) + }) + if (_debugprintHighestLevel) { + syntaxTrees.forEach((t,i) => { + serial.println("\nParsed Statement #"+(i+1)) + serial.println(astToString(t)) + }) + } + return syntaxTrees +} +bF._executeAndGet = function(lnum, stmtnum, syntaxTree) { + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + try { + if (lambdaBoundVars.length != 0) throw new BASICerror() + var execResult = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree, 0) + if (DBGON) serial.println(`Line ${lnum} TRO: ${Object.entries(execResult)}`) + return execResult.troNextLine + } + catch (e) { + serial.printerr(`ERROR on ${lnum}:${stmtnum} -- PARSE TREE:\n${astToString(syntaxTree)}\nERROR CONTENTS:\n${e}\n${e.stack || "Stack trace undefined"}`) + throw e + } +} +bF._basicList = function(v, i, arr) { + if (i < 10) print(" ") + if (i < 100) print(" ") + print(i) + print(" ") + println(v) +} +bF.list = function(args) { + if (args.length == 1) { + cmdbuf.forEach(bF._basicList) + } + else if (args.length == 2) { + if (cmdbuf[args[1]] !== undefined) + bF._basicList(cmdbuf[args[1]], args[1], undefined) + } + else { + var lastIndex = (args[2] === ".") ? cmdbuf.length - 1 : (args[2] | 0) + var i = 0 + for (i = args[1]; i <= lastIndex; i++) { + var cmd = cmdbuf[i] + if (cmd !== undefined) { + bF._basicList(cmd, i, cmdbuf) + } + } + } +} +bF.system = function(args) { + tbasexit = true +} +bF.new = function(args) { + if (args) cmdbuf = [] + bS.vars = initBvars() + gotoLabels = {} + lambdaBoundVars = [] + DATA_CONSTS = [] + DATA_CURSOR = 0 + INDEX_BASE = 0 +} +bF.renum = function(args) { + var newcmdbuf = [] + var linenumRelation = [[]] + var cnt = 10 + for (var k = 0; k < cmdbuf.length; k++) { + if (cmdbuf[k] !== undefined) { + newcmdbuf[cnt] = cmdbuf[k].trim() + linenumRelation[k] = cnt + cnt += 10 + } + } + for (k = 0; k < newcmdbuf.length; k++) { + if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("goto ")) { + newcmdbuf[k] = "GOTO " + linenumRelation[newcmdbuf[k].match(reNum)[0]] + } + else if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("gosub ")) { + newcmdbuf[k] = "GOSUB " + linenumRelation[newcmdbuf[k].match(reNum)[0]] + } + else if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("breakto ")) { + newcmdbuf[k] = "BREAKTO " + linenumRelation[newcmdbuf[k].match(reNum)[0]] + } + } + cmdbuf = newcmdbuf.slice() + cmdbufMemFootPrint = 0 + cmdbuf.forEach((v, i, arr) => + cmdbufMemFootPrint += ("" + i).length + 1 + v.length + ) +} +bF.fre = function(args) { + println(vmemsize - getUsedMemSize()) +} +bF.tron = function(args) { + TRACEON = true +} +bF.troff = function(args) { + TRACEON = false +} +bF.delete = function(args) { + if (args.length != 2 && args.length != 3) throw lang.syntaxfehler() + let start = 0; let end = 0 + if (args.length == 2) { + if (!isNumable(args[1])) throw lang.badFunctionCallFormat() + start = args[1]|0 + end = args[1]|0 + } + else { + if (!isNumable(args[1]) && !isNumable(args[2])) throw lang.badFunctionCallFormat() + start = args[1]|0 + end = args[2]|0 + } + let newcmdbuf = [] + cmdbuf.forEach((v,i) => {if (i < start || i > end) newcmdbuf[i]=v}) + cmdbuf = newcmdbuf +} +bF.cls = function(args) { + con.clear() +} +bF.prescanStmts = ["DATA","LABEL"] +bF.run = function(args) { + bF.new(false) + let programTrees = [] + prescan = true + cmdbuf.forEach((linestr, linenum) => { + let trees = bF._interpretLine(linenum, linestr.trim()) + programTrees[linenum] = trees + if (trees !== undefined) { + trees.forEach((t, i) => { + if (t !== undefined && bF.prescanStmts.includes(t.astValue)) { + bF._executeAndGet(linenum, i, t) + } + }) + } + }) + prescan = false + if (!PROD && DBGON) { + serial.println("[BASIC] final DATA: "+DATA_CONSTS) + } + let lnum = 1 + let stmtnum = 0 + let oldnum = 1 + let tree = undefined + do { + if (programTrees[lnum] !== undefined) { + if (TRACEON) { + serial.println("[BASIC] Line "+lnum) + } + oldnum = lnum + tree = (programTrees[lnum] !== undefined) ? programTrees[lnum][stmtnum] : undefined + if (tree !== undefined) { + let nextObj = bF._executeAndGet(lnum, stmtnum, tree) + lnum = nextObj[0] + stmtnum = nextObj[1] + } + else { + lnum += 1 + stmtnum = 0 + } + } + else { + lnum += 1 + } + if (lnum < 0) throw lang.badNumberFormat + if (con.hitterminate()) { + println("Break in "+oldnum) + break + } + } while (lnum < cmdbuf.length) + con.resetkeybuf() +} +bF.save = function(args) { + if (args[1] === undefined) throw lang.missingOperand + if (!args[1].toUpperCase().endsWith(".BAS")) + args[1] += ".bas" + fs.open(args[1], "W") + var sb = "" + cmdbuf.forEach((v, i) => sb += i+" "+v+"\n") + fs.write(sb) +} +bF.load = function(args) { + if (args[1] === undefined) throw lang.missingOperand + var fileOpened = fs.open(args[1], "R") + if (replUsrConfirmed || cmdbuf.length == 0) { + if (!fileOpened) { + fileOpened = fs.open(args[1]+".BAS", "R") + } + if (!fileOpened) { + fileOpened = fs.open(args[1]+".bas", "R") + } + if (!fileOpened) { + throw lang.noSuchFile + return + } + var prg = fs.readAll() + bF.new(true) + prg.split('\n').forEach((line) => { + var i = line.indexOf(" ") + var lnum = line.slice(0, i) + if (isNaN(lnum)) throw lang.illegalType() + cmdbuf[lnum] = line.slice(i + 1, line.length) + }) + } + else { + replCmdBuf = ["load"].concat(args) + println("Unsaved program will be lost, are you sure? (type 'yes' to confirm)") + } +} +bF.yes = function() { + if (replCmdBuf.length > 0) { + replUsrConfirmed = true + bF[replCmdBuf[0].toLowerCase()](replCmdBuf.slice(1, replCmdBuf.length)) + replCmdBuf = [] + replUsrConfirmed = false + } + else { + throw lang.syntaxfehler("interactive", "nothing to confirm!") + } +} +bF.catalog = function(args) { + if (args[1] === undefined) args[1] = "\\" + var pathOpened = fs.open(args[1], 'R') + if (!pathOpened) { + throw lang.noSuchFile + return + } + var port = _BIOS.FIRST_BOOTABLE_PORT[0] + com.sendMessage(port, "LIST") + println(com.pullMessage(port)) +} +Object.freeze(bF) +while (!tbasexit) { + var line = sys.read().trim() + cmdbufMemFootPrint += line.length + if (reLineNum.test(line)) { + var i = line.indexOf(" ") + cmdbuf[line.slice(0, i)] = line.slice(i + 1, line.length) + } + else if (line.length > 0) { + cmdbufMemFootPrint -= line.length + var cmd = line.split(" ") + if (bF[cmd[0].toLowerCase()] === undefined) { + serial.printerr("Unknown command: "+cmd[0].toLowerCase()) + println(lang.syntaxfehler()) + } + else { + try { + bF[cmd[0].toLowerCase()](cmd) + } + catch (e) { + serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`) + println(`${e}`) + } + } + println(prompt) + } +} diff --git a/buildapp/appimagetool-x86_64.AppImage b/buildapp/appimagetool-x86_64.AppImage new file mode 100755 index 0000000..25365df Binary files /dev/null and b/buildapp/appimagetool-x86_64.AppImage differ diff --git a/buildapp/build_app_linux_arm.sh b/buildapp/build_app_linux_arm.sh new file mode 100755 index 0000000..f885d28 --- /dev/null +++ b/buildapp/build_app_linux_arm.sh @@ -0,0 +1,28 @@ +#!/bin/bash +cd "${0%/*}" +APPIMAGETOOL="appimagetool-x86_64.AppImage" +SRCFILES="tbaslinux_arm" +DESTDIR="TerranBASIC_linux.arm" +RUNTIME="runtime-linux-arm" + +# Cleanup +rm -rf $DESTDIR || true +mkdir $DESTDIR + +# Prepare an application +cp icns.png $DESTDIR/icns.png +cp $SRCFILES/TerranBASIC.desktop $DESTDIR/ +cp $SRCFILES/AppRun $DESTDIR/AppRun +chmod +x $DESTDIR/AppRun + +# Copy over a Java runtime +cp -r "../out/$RUNTIME" $DESTDIR/ + +# Copy over all the assets and a jarfile +cp -r "../out/TerranBASIC.jar" $DESTDIR/ + +# Pack everything to AppImage +"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; } +chmod +x "out/$DESTDIR.AppImage" +rm -rf $DESTDIR || true +echo "Build successful: $DESTDIR" diff --git a/buildapp/build_app_linux_x86.sh b/buildapp/build_app_linux_x86.sh new file mode 100755 index 0000000..027ec26 --- /dev/null +++ b/buildapp/build_app_linux_x86.sh @@ -0,0 +1,28 @@ +#!/bin/bash +cd "${0%/*}" +APPIMAGETOOL="appimagetool-x86_64.AppImage" +SRCFILES="tbaslinux_x86" +DESTDIR="TerranBASIC_linux.x86" +RUNTIME="runtime-linux-x86" + +# Cleanup +rm -rf $DESTDIR || true +mkdir $DESTDIR + +# Prepare an application +cp icns.png $DESTDIR/icns.png +cp $SRCFILES/TerranBASIC.desktop $DESTDIR/ +cp $SRCFILES/AppRun $DESTDIR/AppRun +chmod +x $DESTDIR/AppRun + +# Copy over a Java runtime +cp -r "../out/$RUNTIME" $DESTDIR/ + +# Copy over all the assets and a jarfile +cp -r "../out/TerranBASIC.jar" $DESTDIR/ + +# Pack everything to AppImage +"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; } +chmod +x "out/$DESTDIR.AppImage" +rm -rf $DESTDIR || true +echo "Build successful: $DESTDIR" diff --git a/buildapp/build_app_mac_arm.sh b/buildapp/build_app_mac_arm.sh new file mode 100755 index 0000000..d572ed6 --- /dev/null +++ b/buildapp/build_app_mac_arm.sh @@ -0,0 +1,24 @@ +#!/bin/bash +cd "${0%/*}" +SRCFILES="tbasmac_arm" +DESTDIR="out/TerranBASIC_macOS.arm.app" +RUNTIME="runtime-osx-arm" +# Cleanup +rm -rf $DESTDIR || true +mkdir $DESTDIR +mkdir $DESTDIR/Contents +mkdir $DESTDIR/Contents/MacOS + +# Prepare an application +cp icns.png $DESTDIR/.icns +cp $SRCFILES/Info.plist $DESTDIR/Contents/ +cp $SRCFILES/TerranBASIC.sh $DESTDIR/Contents/MacOS/ +chmod +x $DESTDIR/Contents/MacOS/TerranBASIC.sh + +# Copy over a Java runtime +cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/ + +# Copy over all the assets and a jarfile +cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/ + +echo "Build successful: $DESTDIR" diff --git a/buildapp/build_app_mac_x86.sh b/buildapp/build_app_mac_x86.sh new file mode 100755 index 0000000..71164d0 --- /dev/null +++ b/buildapp/build_app_mac_x86.sh @@ -0,0 +1,24 @@ +#!/bin/bash +cd "${0%/*}" +SRCFILES="tbasmac_x86" +DESTDIR="out/TerranBASIC_macOS.x86.app" +RUNTIME="runtime-osx-x86" +# Cleanup +rm -rf $DESTDIR || true +mkdir $DESTDIR +mkdir $DESTDIR/Contents +mkdir $DESTDIR/Contents/MacOS + +# Prepare an application +cp icns.png $DESTDIR/.icns +cp $SRCFILES/Info.plist $DESTDIR/Contents/ +cp $SRCFILES/TerranBASIC.sh $DESTDIR/Contents/MacOS/ +chmod +x $DESTDIR/Contents/MacOS/TerranBASIC.sh + +# Copy over a Java runtime +cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/ + +# Copy over all the assets and a jarfile +cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/ + +echo "Build successful: $DESTDIR" diff --git a/buildapp/build_app_windows_x86.sh b/buildapp/build_app_windows_x86.sh new file mode 100755 index 0000000..167e66f --- /dev/null +++ b/buildapp/build_app_windows_x86.sh @@ -0,0 +1,23 @@ +#!/bin/bash +cd "${0%/*}" +SRCFILES="tbaswindows_x86" +DESTDIR="TerranBASIC_windows.x86.exe" +RUNTIME="runtime-windows-x86" + +# Cleanup +rm -rf $DESTDIR || true +mkdir $DESTDIR + +# Prepare an application +cp $SRCFILES/TerranBASIC.bat $DESTDIR/ + +# Copy over a Java runtime +cp -r "../out/$RUNTIME" $DESTDIR/ + +# Copy over all the assets and a jarfile +cp -r "../out/TerranBASIC.jar" $DESTDIR/ + +# Temporary solution: zip everything +zip -r -9 -l "out/$DESTDIR.zip" $DESTDIR +rm -rf $DESTDIR || true +echo "Build successful: $DESTDIR" diff --git a/buildapp/icns.png b/buildapp/icns.png new file mode 100644 index 0000000..3da53b3 Binary files /dev/null and b/buildapp/icns.png differ diff --git a/buildapp/instructions.md b/buildapp/instructions.md new file mode 100644 index 0000000..f13a095 --- /dev/null +++ b/buildapp/instructions.md @@ -0,0 +1,61 @@ +### Preparation + +Download and unzip the JDKs to ~/Documents/openjdk/* for the appropriate operating systems first! JDKs can be downloaded on https://jdk.java.net/archive/. + +The filenames must be: + +| Target OS/Arch | filename | +|---------------------|--------------------| +| Linux AMD64 | jdk-17.0.1-x86 | +| Linux Aarch64 | jdk-17.0.1-arm | +| Windows AMD64 | jdk-17.0.1-windows | +| macOS Apple Silicon | jdk-17.0.1.jdk-arm | +| macOS Intel | jdk-17.0.1.jdk-x86 | + +Then, on the terminal, run following commands: + +``` +jlink --module-path ~/Documents/openjdk/jdk-17.0.1-x86/jmods:mods --add-modules java.base,java.desktop,java.logging,java.scripting,jdk.unsupported --output ~/Documents/Terrarum/out/runtime-linux-x86 --no-header-files --no-man-pages --strip-debug --compress=2 +jlink --module-path ~/Documents/openjdk/jdk-17.0.1-arm/jmods:mods --add-modules java.base,java.desktop,java.logging,java.scripting,jdk.unsupported --output ~/Documents/Terrarum/out/runtime-linux-arm --no-header-files --no-man-pages --strip-debug --compress=2 +jlink --module-path ~/Documents/openjdk/jdk-17.0.1-windows/jmods:mods --add-modules java.base,java.desktop,java.logging,java.scripting,jdk.unsupported --output ~/Documents/Terrarum/out/runtime-windows-x86 --no-header-files --no-man-pages --strip-debug --compress=2 +jlink --module-path ~/Documents/openjdk/jdk-17.0.1.jdk-arm/Contents/Home/jmods:mods --add-modules java.base,java.desktop,java.logging,java.scripting,jdk.unsupported --output ~/Documents/Terrarum/out/runtime-osx-arm --no-header-files --no-man-pages --strip-debug --compress=2 +jlink --module-path ~/Documents/openjdk/jdk-17.0.1.jdk-x86/Contents/Home/jmods:mods --add-modules java.base,java.desktop,java.logging,java.scripting,jdk.unsupported --output ~/Documents/Terrarum/out/runtime-osx-x86 --no-header-files --no-man-pages --strip-debug --compress=2 +``` + +(note: matching the building machine with the target OS is highly recommended -- Use Linux for building linux x86/arm; Mac for building macOS x86/arm; Windows for building Windows Java Runtime) + +This process assumes that the game does NOT use the Java 9+ modules and every single required libraries are fat-jar'd (their contents extracted right into the Jar) + +### Packaging + +Create an output directory if there is none (project root/buildapp/out) + +Before running the packaging script make sure: + +1. The required runtime must exist on `(project root)/out/runtime--` directory +2. The build scripts are on a subdirectory of the project directory + +To build, **cd into the "(project root)/buildapp/", then execute the appropriate script**. + +The packaged application can be found on `(project root)/buildapp/out/` + +#### OSX .app Packaging + +``` +Terrarum.*.app ++.icns /* 512x512 PNG */ ++Contents +`Info.plist + +MacOS + `start_game_mac_*.sh * permission: +x */ +``` + +`assets/TerrarumBuild.jar` is the artifact built using the TerrarumBuild. + +`start_game_*` files are on the root directory of the project; use them to build executable apps. + +Hide the `.jar` within the subdirectory; users will think this file is the main executable and will try to execute it using whatever JVM they may (or may not) have. + +### Notes to Terrarum Programmers + +By self-containing everything in one file, it is not possible to modify the base game easily. Modloading scheme must be extended to load from mutable directory such as `%APPDATA%/Terrarum/mods`. diff --git a/buildapp/out/Apps built can be found here b/buildapp/out/Apps built can be found here new file mode 100644 index 0000000..3c02d05 --- /dev/null +++ b/buildapp/out/Apps built can be found here @@ -0,0 +1,7 @@ +You should be able to find following files here: + +TerranBASIC_macOS.x86.app (a directory) +TerranBASIC_macOS.arm.app (a directory) +TerranBASIC_linux.x86.AppImage (an ELF executable) +TerranBASIC_linux.arm.AppImage (an ELF executable) +TerranBASIC_windows.x86.exe (a Windows executable) diff --git a/buildapp/tbaslinux_arm/AppRun b/buildapp/tbaslinux_arm/AppRun new file mode 100755 index 0000000..7b90fa1 --- /dev/null +++ b/buildapp/tbaslinux_arm/AppRun @@ -0,0 +1,3 @@ +#!/bin/bash +cd "${0%/*}" +./runtime-linux-arm/bin/java -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar diff --git a/buildapp/tbaslinux_arm/TerranBASIC.desktop b/buildapp/tbaslinux_arm/TerranBASIC.desktop new file mode 100644 index 0000000..29985d2 --- /dev/null +++ b/buildapp/tbaslinux_arm/TerranBASIC.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Name=TerranBASIC +Exec=AppRun +Icon=icns +Type=Application +Categories=Game; diff --git a/buildapp/tbaslinux_x86/AppRun b/buildapp/tbaslinux_x86/AppRun new file mode 100755 index 0000000..b155968 --- /dev/null +++ b/buildapp/tbaslinux_x86/AppRun @@ -0,0 +1,3 @@ +#!/bin/bash +cd "${0%/*}" +./runtime-linux-x86/bin/java -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar diff --git a/buildapp/tbaslinux_x86/TerranBASIC.desktop b/buildapp/tbaslinux_x86/TerranBASIC.desktop new file mode 100644 index 0000000..29985d2 --- /dev/null +++ b/buildapp/tbaslinux_x86/TerranBASIC.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Name=TerranBASIC +Exec=AppRun +Icon=icns +Type=Application +Categories=Game; diff --git a/buildapp/tbasmac_arm/Info.plist b/buildapp/tbasmac_arm/Info.plist new file mode 100644 index 0000000..865063d --- /dev/null +++ b/buildapp/tbasmac_arm/Info.plist @@ -0,0 +1,7 @@ + + + +CFBundleExecutableTerranBASIC.sh +CFBundleDisplayNameTerranBASIC +CFBundleNameTerranBASIC + diff --git a/buildapp/tbasmac_arm/TerranBASIC.sh b/buildapp/tbasmac_arm/TerranBASIC.sh new file mode 100755 index 0000000..24c6430 --- /dev/null +++ b/buildapp/tbasmac_arm/TerranBASIC.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd "${0%/*}" +./runtime-osx-arm/bin/java -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar diff --git a/buildapp/tbasmac_x86/Info.plist b/buildapp/tbasmac_x86/Info.plist new file mode 100644 index 0000000..865063d --- /dev/null +++ b/buildapp/tbasmac_x86/Info.plist @@ -0,0 +1,7 @@ + + + +CFBundleExecutableTerranBASIC.sh +CFBundleDisplayNameTerranBASIC +CFBundleNameTerranBASIC + diff --git a/buildapp/tbasmac_x86/TerranBASIC.sh b/buildapp/tbasmac_x86/TerranBASIC.sh new file mode 100755 index 0000000..83673b7 --- /dev/null +++ b/buildapp/tbasmac_x86/TerranBASIC.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd "${0%/*}" +./runtime-osx-x86/bin/java -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar diff --git a/buildapp/tbaswindows_x86/TerranBASIC.bat b/buildapp/tbaswindows_x86/TerranBASIC.bat new file mode 100755 index 0000000..248b0f0 --- /dev/null +++ b/buildapp/tbaswindows_x86/TerranBASIC.bat @@ -0,0 +1,2 @@ +cd /D "%~dp0" +.\runtime-windows-x86\bin\java -Xms128M -Xmx2G -jar .\TerranBASIC.jar diff --git a/assets/JS_INIT.js b/tsvm_core/src/net/torvald/tsvm/JS_INIT.js similarity index 100% rename from assets/JS_INIT.js rename to tsvm_core/src/net/torvald/tsvm/JS_INIT.js diff --git a/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt b/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt index 687c1d0..88a8c98 100644 --- a/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt +++ b/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt @@ -78,9 +78,8 @@ object VMRunnerFactory { bind.putMember("audio", AudioJSR223Delegate(vm)) bind.putMember("parallel", ringOneParallel) - val fr = FileReader("$assetsRoot/JS_INIT.js") - val prg = fr.readText() - fr.close() + val fr = this::class.java.classLoader.getResourceAsStream("net/torvald/tsvm/JS_INIT.js") + val prg = fr.readAllBytes().decodeToString() context.eval("js", sanitiseJS(prg)) } diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt index e8454d7..9267b95 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/GraphicsAdapter.kt @@ -1,6 +1,7 @@ package net.torvald.tsvm.peripheral import com.badlogic.gdx.Gdx +import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.g2d.Gdx2DPixmap import com.badlogic.gdx.graphics.g2d.SpriteBatch @@ -77,7 +78,15 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi val channel = it % 4 rgba.shr((3 - channel) * 8).and(255) / 255f } - protected fun getOriginalChrrom() = Pixmap(Gdx2DPixmap(Gdx.files.internal("$assetsRoot/"+config.chrRomPath).read(), Gdx2DPixmap.GDX2D_FORMAT_RGBA8888)) + protected fun getOriginalChrrom(): Pixmap { + fun getFileHandle(): FileHandle = + if (config.chrRomPath.isEmpty()) + Gdx.files.classpath("net/torvald/tsvm/rom/FontROM7x14.png") + else + Gdx.files.internal("$assetsRoot/"+config.chrRomPath) + + return Pixmap(Gdx2DPixmap(getFileHandle().read(), Gdx2DPixmap.GDX2D_FORMAT_RGBA8888)) + } protected lateinit var chrrom: Pixmap protected var chrrom0 = Texture(1,1,Pixmap.Format.RGBA8888) protected val faketex: Texture @@ -1919,16 +1928,16 @@ void main() { val DEFAULT_CONFIG_COLOR_CRT = AdapterConfig( "crt_color", - 560, 448, 80, 32, 253, 255, 256.kB(), "FontROM7x14.png", 0.32f, TEXT_TILING_SHADER_COLOUR + 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.32f, TEXT_TILING_SHADER_COLOUR ) val DEFAULT_CONFIG_PMLCD = AdapterConfig( "pmlcd_inverted", - 560, 448, 80, 32, 253, 255, 256.kB(), "FontROM7x14.png", 0.64f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD + 560, 448, 80, 32, 253, 255, 256.kB(), "", 0.64f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD ) val DEFAULT_CONFIG_FOR_TESTING = AdapterConfig( "crt_color", - 560, 448, 80, 32, 253, 255, 256.kB(), "FontROM7x14.png", 0f, TEXT_TILING_SHADER_COLOUR + 560, 448, 80, 32, 253, 255, 256.kB(), "", 0f, TEXT_TILING_SHADER_COLOUR ) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt index c4b4fa9..54b1bc5 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt @@ -1,15 +1,26 @@ package net.torvald.tsvm.peripheral +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.files.FileHandle import net.torvald.tsvm.CompressorDelegate import net.torvald.tsvm.CompressorDelegate.Companion.GZIP_HEADER import net.torvald.tsvm.VM import java.io.File -open class VMProgramRom(path: String) { +open class VMProgramRom { private val contents: ByteArray - init { - val bytes = File(path).readBytes() + constructor(file: File) { + val bytes = file.readBytes() + contents = bytes.sliceArray(0 until minOf(65536, bytes.size)) + } + + constructor(raw: String) { + val bytes = raw.encodeToByteArray() + contents = bytes.sliceArray(0 until minOf(65536, bytes.size)) + } + + constructor(bytes: ByteArray) { contents = bytes.sliceArray(0 until minOf(65536, bytes.size)) } @@ -24,15 +35,17 @@ open class VMProgramRom(path: String) { fun get(addr: Int): Byte = contents[addr] } -object GenericBios : VMProgramRom("./assets/bios/bios1.bin") -object OEMBios : VMProgramRom("./assets/bios/TBMBIOS.js") -object QuickBios : VMProgramRom("./assets/bios/quick.js") -object BasicBios : VMProgramRom("./assets/bios/basicbios.js") -object TandemBios : VMProgramRom("./assets/bios/tandemport.js") -object TsvmBios : VMProgramRom("./assets/bios/tsvmbios.js") -object BasicRom : VMProgramRom("./assets/bios/basic.bin") -object TBASRelBios : VMProgramRom("./assets/bios/tbasdist.js") -object WPBios : VMProgramRom("./assets/bios/wp.js") -object OpenBios : VMProgramRom("./assets/bios/openbios.js") -object PipBios : VMProgramRom("./assets/bios/pipboot.rom") -object PipROM : VMProgramRom("./assets/bios/pipcode.bas") \ No newline at end of file +object GenericBios : VMProgramRom(File("./assets/bios/bios1.bin")) +object OEMBios : VMProgramRom(File("./assets/bios/TBMBIOS.js")) +object QuickBios : VMProgramRom(File("./assets/bios/quick.js")) +object BasicBios : VMProgramRom(File("./assets/bios/basicbios.js")) +object TandemBios : VMProgramRom(File("./assets/bios/tandemport.js")) +object TsvmBios : VMProgramRom(File("./assets/bios/tsvmbios.js")) +object BasicRom : VMProgramRom(File("./assets/bios/basic.bin")) +object WPBios : VMProgramRom(File("./assets/bios/wp.js")) +object OpenBios : VMProgramRom(File("./assets/bios/openbios.js")) +object PipBios : VMProgramRom(File("./assets/bios/pipboot.rom")) +object PipROM : VMProgramRom(File("./assets/bios/pipcode.bas")) +object TerranBASICreleaseBios : VMProgramRom(java.util.Base64.getDecoder().decode("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")) + + diff --git a/tsvm_core/src/net/torvald/tsvm/rom/FontROM7x14.png b/tsvm_core/src/net/torvald/tsvm/rom/FontROM7x14.png new file mode 100644 index 0000000..f864a62 Binary files /dev/null and b/tsvm_core/src/net/torvald/tsvm/rom/FontROM7x14.png differ