From 55113ff11fb55f62955ccc6c9e91aba3a0e74c32 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 1 Dec 2021 16:15:42 +0900 Subject: [PATCH] the pip(needs proper name) program loader and the example program --- .../regex => bios/basic_minify_regex.txt} | 0 assets/bios/pip_basic_loader_source.js | 4095 +++++++++++++++++ assets/bios/pipboot.js | 7 - assets/bios/pipboot.rom | Bin 0 -> 31077 bytes assets/bios/pipcode.bas | 5 +- src/net/torvald/tsvm/peripheral/ExtDisp.kt | 2 +- .../torvald/tsvm/peripheral/VMProgramRom.kt | 2 +- 7 files changed, 4101 insertions(+), 10 deletions(-) rename assets/{disk0/tbas/regex => bios/basic_minify_regex.txt} (100%) create mode 100644 assets/bios/pip_basic_loader_source.js delete mode 100644 assets/bios/pipboot.js create mode 100644 assets/bios/pipboot.rom diff --git a/assets/disk0/tbas/regex b/assets/bios/basic_minify_regex.txt similarity index 100% rename from assets/disk0/tbas/regex rename to assets/bios/basic_minify_regex.txt diff --git a/assets/bios/pip_basic_loader_source.js b/assets/bios/pip_basic_loader_source.js new file mode 100644 index 0000000..79c9e33 --- /dev/null +++ b/assets/bios/pip_basic_loader_source.js @@ -0,0 +1,4095 @@ +// Created by CuriousTorvald on 2020-05-19 +// Version 1.0 Release Date 2020-12-28 +// Version 1.1 Release Date 2021-01-28 +// Version 1.2 Release Date 2021-05-05 + +/* +Copyright (c) 2020-2021 CuriousTorvald + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +const THEVERSION = "1.2" + +const PROD = true +let INDEX_BASE = 0 +let TRACEON = (!PROD) && true +let DBGON = (!PROD) && true +let DATA_CURSOR = 0 +let DATA_CONSTS = [] + +let vmemsize = system.maxmem() + +let cmdbuf = [] // index: line number +let gotoLabels = {} +let cmdbufMemFootPrint = 0 +let prompt = "Ok" +let prescan = false +let replCmdBuf = [] // used to store "load filename" and issues it when user confirmed potential data loss +let replUsrConfirmed = false + +// lambdaBoundVars is used in two different mode: +// - PARSER will just store a symbol as a string literal +// - EXECUTOR will store the actual info of the bound vars in this format: [astType, astValue] +let lambdaBoundVars = [] // format: [[a,b],[c]] for "[c]~>[a,b]~>expr" + +/* if an object can be FOR REAL cast to number */ +function isNumable(s) { + // array? + if (Array.isArray(s)) return false + // undefined? + if (s === undefined) return false + // null string? + if (typeof s.trim == "function" && s.trim().length == 0) return false + // else? + return !isNaN(s) // NOTE: isNaN('') == false +} +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) + +// implement your own con object here +// requirements: reset_graphics(), getch(), curs_set(int), hitterminate(), resetkeybuf(), addch(int) + +let getUsedMemSize = function() { + var varsMemSize = 0 + + Object.entries(bS.vars).forEach((pair, i) => { + var object = pair[1] + + if (Array.isArray(object)) { + // TODO test 1-D array + 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 // + array's dimsize * 8 + variables' sizeof literal + functions' expression length +} + +let reLineNum = /^[0-9]+ / +//var reFloat = /^([\-+]?[0-9]*[.][0-9]+[eE]*[\-+0-9]*[fF]*|[\-+]?[0-9]+[.eEfF][0-9+\-]*[fF]?)$/ +//var reDec = /^([\-+]?[0-9_]+)$/ +//var reHex = /^(0[Xx][0-9A-Fa-f_]+)$/ +//var reBin = /^(0[Bb][01_]+)$/ + +// must match partial +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 + +// variable object constructor +/** variable object constructor + * @param literal Javascript object or primitive + * @type derived from JStoBASICtype + "usrdefun" + "internal_arrindexing_lazy" + "internal_assignment_object" + * @see bS.builtin["="] + */ +let BasicVar = function(literal, type) { + this.bvLiteral = literal + this.bvType = type +} +// Abstract Syntax Tree +// 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" : + ("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}): ` + //if (monad.mType == "value") { + sb += (monad.mVal === undefined) ? "(undefined)" : (isAST(monad.mVal)) ? `f"${monad.mVal.astHash}"` : (isMonad(monad.mVal)) ? `M"${monad.mVal.mHash}"` : monad.mVal + /*} + else if (monad.mType == "list") { + let elemToStr = function(e) { + return (e === undefined) ? "(undefined)" : + (isAST(e)) ? `f"${e.astHash}"` : + (isMonad(e)) ? `M"${e.mHash}"` : + e + } + + let m = monad.mVal + while (1) { + sb += elemToStr(m.p) + if (m.n === undefined) break + else { + sb += "," + m = m.n + } + } + } + else { + throw new BASICerror("unknown monad subtype: "+m.mType) + }*/ + 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]}` // type and value pair + }) + 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" // lit, op, string, num, array, function, null, defun_args (! NOT usrdefun !) + this.astHash = makeBase32Hash() +} +// I'm basically duck-typing here... +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 +} +/** A List Monad (a special case of Value-monad) + * This monad MUST follow the monad law! + * @param m a monadic value (Javascript array) + */ +let BasicListMonad = function(m) { + this.mHash = makeBase32Hash() + this.mType = "list" + this.mVal = [m] +} +/** A Memoisation Monad, aka the most generic monad + * This monad MUST follow the monad law! + * @param m a monadic value + */ +/* Test this monad with following program + * This program requires (>>=) to "play by the rules" +10 LSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE LSORT(FILTER([K]~>KK>=HEAD XS,TAIL XS)) +20 LREV=[XS]~>MAP([I]~>XS(I),LEN(XS)-1 TO 0 STEP -1) +30 LINC=[XS]~>MAP([I]~>I+1,XS) +40 L=7!9!4!5!2!3!1!8!6!NIL +100 MAGICKER=[XS]~>MRET(LSORT(XS))>>=([X]~>MRET(LREV(X))>>=([X]~>MRET(LINC(X)))) +110 MAGICK_L = MAGICKER(L) +120 PRINT MAGICK_L() + */ +/* Value-monad satisfies monad laws, test with following program +10 F=[X]~>X*2 : G=[X]~>X^3 : RETN=[X]~>MRET(X) + +100 PRINT:PRINT "First law: 'return a >>= k' equals to 'k a'" +110 K=[X]~>RETN(F(X)) : REM K is monad-returning function +120 A=42 +130 KM=RETN(A)>>=K +140 KO=K(A) +150 PRINT("KM is ";TYPEOF(KM);", ";MJOIN(KM)) +160 PRINT("KO is ";TYPEOF(KO);", ";MJOIN(KO)) + +200 PRINT:PRINT "Second law: 'm >>= return' equals to 'm'" +210 M=MRET(G(42)) +220 MM=M>>=RETN +230 MO=M +240 PRINT("MM is ";TYPEOF(MM);", ";MJOIN(MM)) +250 PRINT("MO is ";TYPEOF(MO);", ";MJOIN(MO)) + +300 PRINT:PRINT "Third law: 'm >>= (\x -> k x >>= h)' equals to '(m >>= k) >>= h'" +310 REM see line 110 for the definition of K +320 H=[X]~>RETN(G(X)) : REM H is monad-returning function +330 M=MRET(69) +340 M1=M>>=([X]~>K(X)>>=H) +350 M2=(M>>=K)>>=H +360 PRINT("M1 is ";TYPEOF(M1);", ";MJOIN(M1)) +370 PRINT("M2 is ";TYPEOF(M2);", ";MJOIN(M2)) + */ +let BasicMemoMonad = function(m) { + this.mHash = makeBase32Hash() + this.mType = "value" + this.mVal = m // a monadic value + this.seq = undefined // unused +} +// I'm basically duck-typing here... +let isMonad = (o) => (o === undefined) ? false : (o.mType !== undefined) + +let literalTypes = ["string", "num", "bool", "array", "generator", "usrdefun", "monad"] +/* +@param variable SyntaxTreeReturnObj, of which the 'troType' is defined in BasicAST. +@return a value, if the input type if string or number, its literal value will be returned. Otherwise will search the + BASIC variable table and return the literal value of the BasicVar; undefined will be returned if no such var exists. +*/ +let resolve = function(variable) { + if (variable === undefined) return undefined + // head error checking + if (variable.troType === undefined) { + // primitves types somehow injected from elsewhere (main culprit: MAP) + //throw Error(`BasicIntpError: trying to resolve unknown object '${variable}' with entries ${Object.entries(variable)}`) + + 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)) { // rarely we get a number as a variable name, notably on (&) + return tonum(variable.troValue) + } + // when program tries to call builtin function (e.g. SIN), return usrdefun-wrapped version + else if (bS.builtin[variable.troValue] !== undefined) { + return bS.wrapBuiltinToUsrdefun(variable.troValue) + } + // else, it's just a plain-old variable :p + 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 + // tail error checking + else + throw Error("BasicIntpError: unknown variable/object with type "+variable.troType+", with value "+variable.troValue) +} +let findHighestIndex = function(exprTree) { + let highestIndex = [-1,-1] + // look for the highest index of [a,b] + 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 + } + + // 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 => { + arg = bS.getDefunThunk(f)(lnum, stmtnum, [arg]) + }) + return arg + } + else { + // args are futile + 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 +} +// I'm basically duck-typing here... +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 = {} // BASIC status +bS.gosubStack = [] +bS.forLnums = {} // key: forVar, value: [lnum, stmtnum] +bS.forStack = [] // forVars only +bS.vars = initBvars() // contains instances of BasicVars +bS.rnd = 0 // stores mantissa (23 bits long) of single precision floating point number +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 = "+`/*ar1*/array${indexingstr}`) + + return {arrFull: array, arrName: arrayName, arrKey: indexingstr} + }) + } +} +/** + * @return a Javascript function that when called, evaluates the exprTree + */ +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)) + serial.println("[BASIC.getDefunThunk.invoke] thunk args:") + serial.println(argsMap) + serial.println("[BASIC.getDefunThunk.invoke] lambda bound vars:") + serial.println(theLambdaBoundVars()) + } + + // perform renaming + 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] // 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)) + } + } + else { + if (DBGON) { + serial.println("[BASIC.getDefunThunk.invoke] no rename, resulting thunk tree:") + 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 + * @params troValue Variable to assign into + * @params rh the value, resolved + */ +bS.addAsBasicVar = function(lnum, troValue, rh) { + if (troValue.arrFull !== undefined) { // assign to existing array + 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() + //println("input varname: "+varname) + if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname) + let type = JStoBASICtype(rh) + bS.vars[varname] = new BasicVar(rh, type) + return {asgnVarName: varname, asgnValue: rh} + } +} +bS.builtin = { +/* +@param lnum line number +@param args instance of the SyntaxTreeReturnObj + +if no args were given (e.g. "10 NEXT()"), args[0] will be: {troType: null, troValue: , troNextLine: 11} +if no arg text were given (e.g. "10 NEXT"), args will have zero length +*/ +"=" : {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) // if string we got can be cast to number, do it + + //println(lnum+" = lh: "+Object.entries(args[0])) + //println(lnum+" = rh raw: "+Object.entries(args[1])) + //println(lnum+" = rh resolved: "+rh) + //try { println(lnum+" = rh resolved entries: "+Object.entries(rh)) } + //catch (_) {} + + return bS.addAsBasicVar(lnum, troValue, rh) +}}, +"IN" : {argc:2, f:function(lnum, stmtnum, args) { // almost same as =, but don't actually make new variable. Used by FOR statement + 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) { // Haskell-style CONS + 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) { // array PUSH + 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) { // array CONCAT + 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) { // addition, string concat + 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) { // integer division, rounded towards zero + 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++) { + // parse separators. + // ; - concat + // , - tab + 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) + + //serial.println(`${lnum} PRINT ${lang.ord(llll)} arg: ${Object.entries(args[llll])}, resolved: ${rsvArg}`) + + 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++) { + // parse separators. + // ; - concat + // , - tab + 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) { + // search from gotoLabels first + let line = gotoLabels[args[0].troValue] + // if not found, use resolved variable + 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) { + // search from gotoLabels first + let line = gotoLabels[args[0].troValue] + // if not found, use resolved variable + if (line === undefined) line = resolve(args[0]) + if (line < 0) throw lang.syntaxfehler(lnum, line) + + bS.gosubStack.push([lnum, stmtnum + 1]) + //println(lnum+" GOSUB into "+lh) + 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) + //println(lnum+" RETURN to "+r) + 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()//(bS.rnd * 214013 + 2531011) % 16777216 // GW-BASIC does this + 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) { // synonymous to FLOOR + 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]) // string 'false' will be interpreted as truthy; this is completely intentional +}}, +"FOREACH" : {f:function(lnum, stmtnum, args) { // list comprehension model + var asgnObj = resolve(args[0]) + // type check + if (asgnObj === undefined) throw lang.syntaxfehler(lnum) + if (!Array.isArray(asgnObj.asgnValue)) throw lang.illegalType(lnum, asgnObj) + + var varname = asgnObj.asgnVarName + + // assign new variable + // the var itself will have head of the array, and the head itself will be removed from the array + bS.vars[varname] = new BasicVar(asgnObj.asgnValue[0], JStoBASICtype(asgnObj.asgnValue.shift())) + // stores entire array (sans head) into temporary storage + bS.vars["for var "+varname] = new BasicVar(asgnObj.asgnValue, "array") + // put the varname to forstack + bS.forLnums[varname] = [lnum, stmtnum] + bS.forStack.push(varname) +}}, +"FOR" : {f:function(lnum, stmtnum, args) { // generator model + var asgnObj = resolve(args[0]) + // type check + 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 + + // assign new variable + // the var itself will have head of the array, and the head itself will be removed from the array + bS.vars[varname] = new BasicVar(generator.start, "num") + // stores entire array (sans head) into temporary storage + bS.vars["for var "+varname] = new BasicVar(generator, "generator") + // put the varname to forstack + bS.forLnums[varname] = [lnum, stmtnum] + bS.forStack.push(varname) +}}, +"NEXT" : {f:function(lnum, stmtnum, args) { + // if no args were given + if (args.length == 0 || (args.length == 1 && args.troType == "null")) { + // go to most recent FOR + var forVarname = bS.forStack.pop() + //serial.println(lnum+" NEXT > forVarname = "+forVarname) + 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)) { + // feed popped value back, we're not done yet + bS.forStack.push(forVarname) + let forLnum = bS.forLnums[forVarname] + return new JumpObj(forLnum[0], forLnum[1]+1, lnum, [forLnum[0], forLnum[1]+1]) // goto the statement RIGHT AFTER the FOR-declaration + } + else { + if (isGenerator(forVar)) + bS.vars[forVarname].bvLiteral = forVar.current // true BASIC compatibility for generator + else + bS.vars[forVarname] === undefined // unregister the variable + + 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 out prompt text + print("? "); var rh = sys.read().trim() + + // if string we got can be cast to number, do it + // NOTE: empty string will be cast to 0, which corresponds to GW-BASIC + 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) // GOTO far-far-away +}}, +"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))) + } +}}, +/* Syopsis: MAP function, functor + */ +"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) : "")) + // generator? + if (isGenerator(functor)) functor = genToArray(functor) + + return functor.map(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it])) + }) +}}, +/* Synopsis: FOLD function, init_value, functor + * a function must accept two arguments, of which first argument will be an accumulator + */ +"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) : "")) + // generator? + if (isGenerator(functor)) functor = genToArray(functor) + + let akku = init + functor.forEach(it => { + akku = bS.getDefunThunk(fn)(lnum, stmtnum, [akku, it]) + }) + return akku + }) +}}, +/* Syopsis: FILTER function, functor + */ +"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))) + // generator? + if (isGenerator(functor)) functor = genToArray(functor) + + return functor.filter(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it])) + }) +}}, +/* GOTO and GOSUB won't work but that's probably the best...? */ +"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) { + //args: functionName (string), testvalue (SyntaxTreeReturnObj), arg0 (SyntaxTreeReturnObj), arg1 (SyntaxTreeReturnObj), ... + if (args[2] === undefined) throw lang.syntaxfehler(lnum) + + let jmpFun = args.shift() + let testvalue = resolve(args.shift())-INDEX_BASE + + // args must be resolved lazily because jump label is not resolvable + 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) { // CURRY operator + 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]) // 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)) + 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)) + /*if (tree.astType == "usrdefun") { + serial.println("[BASIC.BUILTIN.REDUCE] usrdefun unpack:") + 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 { + return bv + } + }) +}}, +/** type: m a -> (a -> m b) -> m b + * @param m a monad + * @param fnb a function that takes a monadic value from m and returns a new monad. IT'S ENTIRELY YOUR RESPONSIBILITY TO MAKE SURE THIS FUNCTION TO RETURN RIGHT KIND OF MONAD! + * @return another monad + */ +">>=" : {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 + }) +}}, +/** type: m a -> m b -> m b + * @param m a monad + * @param fnb a function that returns a new monad. IT'S ENTIRELY YOUR RESPONSIBILITY TO MAKE SURE THIS FUNCTION TO RETURN RIGHT KIND OF MONAD! + * @return another monad + */ +">>~" : {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 + }) +}}, +/** type: (b -> c) -> (a -> b) -> (a -> c) + * @param fa a function or a funseq-monad + * @param fb a function or a funseq-monad + * @return another monad + */ +"." : {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 + }) +}}, +/*"MEVAL" : {argc:1, f:function(lnum, stmtnum, args) { + return varArg(lnum, stmtnum, args, rgs => { + let m = rgs[0] + let args = rgs.slice(1, rgs.length) + return getMonadEvalFun(m)(lnum, stmtnum, args) + }) +}},*/ +"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") + }) +}}, +/** type: (list of function) <*> (a functor) + * Sequnetial 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)) + */ +"<*>" : {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) : "")) + // generator? + 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 + }) +}}, +/** type: (a function) <*> (a functor) + * 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) { + 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) : "")) + // generator? + 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 + }) +}}, +"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 = {} // BASIC functions +bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1,"$":1,".":1,"@":1,"\\":1,"%":1,"|":1,"`":1} +//bF._2os = {"<":1,"=":1,">":1,"~":1,"-":1,"$":1,"*":1,"!":1,"#":1} +//bF._3os = {"<":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._is2o = function(code) { + return bF._2os[String.fromCharCode(code)] +} +bF._is3o = function(code) { + return bF._3os[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 +} +// define operator precedence here... +// NOTE: do NOT put falsy value (e.g. 0) here!! +bF._opPrc = { + // function call in itself has highest precedence + "`":10, // MJOIN + "^":20, + "*":30,"/":30,"\\":20, + "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, // array CONS and PUSH + "#":502, // array concat + ".": 600, // compo operator + "$": 600, // apply operator + "&": 600, // pipe operator + "~<": 601, // curry operator + "<$>": 602, // infix map operator + "<*>": 602, // sequential application operator + "<~>": 602, // infix curry-map operator + "@":700, // MRET + "~>": 1000, // closure operator + ">>~": 1000, // monad sequnce operator + ">>=": 1000, // monad bind operator + "=":9999,"IN":9999 +} // when to ops have same index of prc but different in associativity, right associative op gets higher priority (at least for the current parser implementation) +bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"~>":1,"$":1,".":1,">>=":1,">>~":1,">!>":1,"@":1,"`":1,"<$>":1} // ~< and ~> cannot have same associativity +// these names appear on executeSyntaxTree as "exceptional terms" on parsing (regular function calls are not "exceptional terms") +bF._tokenise = function(lnum, cmd) { + var _debugprintStateTransition = false + var k + var tokens = [] + var states = [] + var sb = "" + var mode = "lit" // lit, qot, paren, sep, op, num; operator2, numbersep, number2, limbo, escape, quote_end + + // NOTE: malformed numbers (e.g. "_b3", "_", "__") must be re-marked as literal or syntax error in the second pass + + if (_debugprintStateTransition) println("@@ TOKENISE @@") + if (_debugprintStateTransition) println("Ln "+lnum+" cmd "+cmd) + + // TOKENISE + 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._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 { + 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 if (charCode == 0x5C) { // reverse solidus + tokens.push(sb); sb = "" + mode = "escape" + }*/ + else { + sb += char + } + } + /*else if ("escape" == mode) { + if (0x5C == charCode) // reverse solidus + sb += String.fromCharCode(0x5C) + else if ("n" == char) + sb += String.fromCharCode(0x0A) + else if ("t" == char) + sb += String.fromCharCode(0x09) + else if (0x22 == charCode) // " + sb += String.fromCharCode(0x22) + else if (0x27 == charCode) + sb += String.fromCharCode(0x27) + else if ("e" == char) + sb += String.fromCharCode(0x1B) + else if ("a" == char) + sb += String.fromCharCode(0x07) + else if ("b" == char) + sb += String.fromCharCode(0x08) + mode = "qot" // ESCAPE is only legal when used inside of quote + }*/ + 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 == " ") { + /* do nothing */ + } + 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) + } + + // filter off initial empty token if the statement does NOT start with literal (e.g. "-3+5") + if (tokens[0].length == 0) { + tokens = tokens.slice(1, tokens.length) + states = states.slice(1, states.length) + } + // clean up operator2 and number2 + 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 + + // 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])) + states[k] = "lit" + // turn back into an op if operator is errenously checked as a literal + else if (states[k] == "lit" && bF._opPrc[tokens[k].toUpperCase()] !== undefined) + states[k] = "op" + // 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")) { + 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] + + // 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) + 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 + } + // 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) + 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 + } + // turn (:) into a seq + else if (tokens[k] == ":" && states[k] == "op") + states[k] = "seq" + + 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 + tree.astSeps = newTree.astSeps + tree.astType = newTree.astType + // weave astLeaves + for (let k = 0; k < tree.astLeaves.length; k++) { + if (newLeaves[k] !== undefined) tree.astLeaves[k] = newLeaves[k] + } + } + } +} +/** + * Returns a copy of BasicAST where its 'capsulated' trees are fully uncapsulated. + */ +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 +} +/** EBNF notation: +(* quick reference to EBNF *) +(* { word } = word is repeated 0 or more times *) +(* [ word ] = word is optional (repeated 0 or 1 times) *) + +line = + linenumber , stmt , {":" , stmt} + | linenumber , "REM" , ? basically anything ? +linenumber = digits + +stmt = + "REM" , ? anything ? + | "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] + | "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr + | "ON" , expr_sans_asgn , ("GOTO" | "GOSUB") , expr_sans_asgn , {"," , expr_sans_asgn} + | "(" , 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 ? + | lit + | "{" , [expr , {"," , expr}] , "}" + | "(" , expr , ")" + | ident_tuple + | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr] + | ("FOR"|"FOREACH") , expr + | 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 = + 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 = 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" | "`" | "@" + +alph = letter | letter , alph +digits = digit | digit , digits +hexdigits = hexdigit | hexdigit , hexdigits +bindigits = bindigit | bindigit , bindigits +num = digits | digits , "." , [digits] | "." , digits + | ("0x"|"0X") , hexdigits + | ("0b"|"0B") , bindigits ; (* sorry, no e-notation! *) +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" +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" + +(* all possible token states: lit num op bool qot paren sep *) +(* below are schematic of trees generated after parsing the statements *) + +IF (type: function, value: IF) +1. cond +2. true +[3. false] + +FOR (type: function, value: FOR) +1. expr (normally (=) but not necessarily) + +DEFUN (type: function, value: DEFUN) +1. funcname (type: lit) + 1. arg0 (type: lit) + [2. arg1] + [3. argN...] +2. stmt + +ON (type: function, value: ON) +1. testvalue +2. functionname (type: lit) +3. arg0 +[4. arg1] +[5. argN...] + +FUNCTION_CALL (type: function, value: PRINT or something) +1. arg0 +2. arg1 +[3. argN...] + +LAMBDA (type: op, value: "~>") +1. undefined (type: closure_args, value: undefined) + 1. arg0 (type: lit) + [2. arg1] + [3. argN...] +2. stmt + +ARRAY CONSTRUCTOR (type: function, value: undefined) +1. 0th element of the array +2. 1st element of the array +[3. Nth element of the array...] + */ +// @returns BasicAST +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}`) + } +} + +// ## USAGE OF lambdaBoundVars IN PARSEMODE STARTS HERE ## + +/** + * @return ARRAY of BasicAST + */ +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 = [] + + // scan for parens and (:)s + for (let k = 0; k < tokens.length; k++) { + // increase paren depth and mark paren start position + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // decrease paren depth + 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)}}) // 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), + 1 + ) + + bF.parserPrintdbgline('Tree in ', '\n'+astToString(tree), lnum, 0) + + return tree + }) +} +/** Parses following EBNF rule: +stmt = + "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] + | "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr + | "ON" , expr_sans_asgn , ident , expr_sans_asgn , {"," , expr_sans_asgn} + | "(" , stmt , ")" + | expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call e.g. NEXT for FOR loop *) + * @return: BasicAST + */ +bF._parseStmt = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2('$', lnum, tokens, states, recDepth) + + /*************************************************************************/ + + // case for: single word (e.g. NEXT for FOR loop) + 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 + + /*************************************************************************/ + + // case for: "REM" , ? anything ? + if (headTkn == "REM" && headSta != "qot") return + + /*************************************************************************/ + + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let onGoPos = -1 + let sepsZero = [] + let sepsOne = [] + + // scan for parens that will be used for several rules + // also find nearest THEN and ELSE but also take parens into account + for (let k = 0; k < tokens.length; k++) { + // increase paren depth and mark paren start position + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // decrease paren depth + 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 + } + } + + // unmatched brackets, duh! + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + + /*************************************************************************/ + + // ## case for: + // "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] + try { + bF.parserPrintdbgline('$', "Trying IF Statement...", lnum, recDepth) + return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false) + } + // if ParserError is raised, continue applying other rules + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('$', 'It was NOT!', lnum, recDepth) + } + + /*************************************************************************/ + + // ## case for: + // | "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr + 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" + + // parse function name + if (tokens[1] == "(") { + // anonymous function + 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) + } + + // parse function arguments + 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)) + + // parse function body + 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 + } + + /*************************************************************************/ + + // ## case for: + // | "ON" , if_equation , ident , if_equation , {"," , if_equation} + 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" + + // parse testvalue + let testvalue = bF._parseExpr(lnum, + tokens.slice(1, onGoPos), + states.slice(1, onGoPos), + recDepth + 1, + true + ) + + // parse functionname + let functionname = bF._parseExpr(lnum, + [tokens[onGoPos]], + [states[onGoPos]], + recDepth + 1, + true + ) + + // parse arguments + // get list of comma but filter ones appear before GOTO/GOSUB + 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)}}) // use end of token position as separator position + + // recursively parse expressions + treeHead.astLeaves = [testvalue, functionname].concat(onArgPos.map((x,i) => { + bF.parserPrintdbgline('$', 'ON GOTO/GOSUB Arguments #'+(i+1), lnum, recDepth) + + // check for empty tokens + 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 + } + + /*************************************************************************/ + + // ## case for: + // | "(" , stmt , ")" + 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 + ) + } + + /*************************************************************************/ + + // ## case for: + // | expr + 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) +} // END of STMT +/** Parses following EBNF rule: +expr = (* this basically blocks some funny attemps such as using DEFUN as anon function because everything is global in BASIC *) + ? empty string ? + | lit + | "{" , [expr , {"," , expr}] , "}" + | "(" , expr , ")" + | ident_tuple + | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr] + | ("FOR"|"FOREACH") , expr + | expr , op , expr + | op_uni , expr + | kywd , expr - "(" + | function_call + + * @return: BasicAST + */ +bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) { + bF.parserPrintdbg2('e', lnum, tokens, states, recDepth) + + /*************************************************************************/ + + // ## special case for virtual dummy element (e.g. phantom element on "PRINT SPC(20);") + 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] + + /*************************************************************************/ + + // ## case for: + // lit + if (!bF._EquationIllegalTokens.includes(headTkn) && tokens.length == 1) { + bF.parserPrintdbgline('e', 'Literal Call', lnum, recDepth) + return bF._parseLit(lnum, tokens, states, recDepth + 1) + } + + /*************************************************************************/ + + // scan for operators with highest precedence, use rightmost one if multiple were found + let topmostOp + let topmostOpPrc = 0 + let operatorPos = -1 + + // find and mark position of parentheses + // properly deal with the nested function calls + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let curlyDepth = 0 + 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++) { + // increase paren depth and mark paren start position + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // increase curly depth and mark curly start position + else if (tokens[k] == "{" && states[k] == "paren") { + curlyDepth += 1 + if (curlyStart == -1 && curlyDepth == 1) curlyStart = k + } + // decrease paren depth + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + // decrease curly depth + else if (tokens[k] == "}" && states[k] == "paren") { + 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)) + ) { + topmostOp = uptkn + topmostOpPrc = bF._opPrc[uptkn] + operatorPos = k + } + } + } + + // unmatched brackets, duh! + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + + /*************************************************************************/ + + // ## case for: + // | ident_tuple + try { + bF.parserPrintdbgline('e', "Trying Tuple...", lnum, recDepth) + return bF._parseTuple(lnum, tokens, states, recDepth + 1, false) + } + // if ParserError is raised, continue applying other rules + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + /*************************************************************************/ + + // ## case for: + // | "{" , [expr , {"," , expr}] , "}" + if (curlyStart == 0 && curlyEnd == tokens.length - 1) { + bF.parserPrintdbgline('e', "Array", lnum, recDepth) + return bF._parseArrayLiteral(lnum, tokens, states, recDepth + 1) + } + + /*************************************************************************/ + + + // ## case for: + // | "(" , [expr] , ")" + 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 + ) + } + + /*************************************************************************/ + + // ## case for: + // | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr] + try { + bF.parserPrintdbgline('e', "Trying IF Expression...", lnum, recDepth) + return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false) + } + // if ParserError is raised, continue applying other rules + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + /*************************************************************************/ + + // ## case for: + // | ("FOR"|"FOREACH") , expr + try { + bF.parserPrintdbgline('e', "Trying FOR Expression...", lnum, recDepth) + return bF._parseForLoop(lnum, tokens, states, recDepth + 1) + } + // if ParserError is raised, continue applying other rules + catch (e) { + if (!(e instanceof ParserError)) throw e + bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth) + } + + /*************************************************************************/ + + // ## case for: + // | expr , op, expr + // | op_uni , expr + // if operator is found, split by the operator and recursively parse the LH and RH + 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") + + + // this is the AST we're going to build up and return + // (other IF clauses don't use this) + let treeHead = new BasicAST() + treeHead.astLnum = lnum + treeHead.astValue = topmostOp + treeHead.astType = "op" + + // BINARY_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 + } + + /*************************************************************************/ + + // ## case for: + // | kywd , expr - "(" + 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) + } + + /*************************************************************************/ + + // ## case for: + // | function_call + if (topmostOp === undefined) { // don't remove this IF statement! + try { + bF.parserPrintdbgline('e', "Trying Function Call...", lnum, recDepth) + return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1) + } + // if ParserError is raised, continue applying other rules + 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}`) +} // END of EXPR +/** Parses following EBNF rule: + "{" , [expr , {"," , expr}] , "}" + * @return: BasicAST + */ +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 = [] + + // scan for parens that will be used for several rules + // also find nearest THEN and ELSE but also take parens into account + for (let k = 0; k < tokens.length; k++) { + // increase paren depth and mark paren start position + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // increase curly depth and mark curly start position + else if (tokens[k] == "{" && states[k] == "paren") { + curlyDepth += 1 + if (curlyStart == -1 && curlyDepth == 1) curlyStart = k + } + // decrease paren depth + else if (tokens[k] == ")" && states[k] == "paren") { + if (parenEnd == -1 && parenDepth == 1) parenEnd = k + parenDepth -= 1 + } + // decrease curly depth + else if (tokens[k] == "}" && states[k] == "paren") { + if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k + curlyDepth -= 1 + } + + // commas + if (parenDepth == 0 && curlyDepth == 1 && tokens[k] == "," && states[k] == "sep") { + argSeps.push(k) + } + } + + // 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 = [] + } + else { + treeHead.astLeaves = argPos.map((x,i) => { + bF.parserPrintdbgline("{", 'Array Element #'+(i+1), lnum, 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), + recDepth + 1 + )} + ) + } + + return treeHead + +} +/** Parses following EBNF rule: + "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] + | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr] + if exprMode is true, only the latter will be used; former otherwise + * @return: BasicAST + */ +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 + + // scan for parens that will be used for several rules + // also find nearest THEN and ELSE but also take parens into account + for (let k = 0; k < tokens.length; k++) { + // increase paren depth and mark paren start position + if (tokens[k] == "(" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // decrease paren depth + 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 + } + } + + // unmatched brackets, duh! + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + + let treeHead = new BasicAST() + treeHead.astLnum = lnum + + // ## case for: + // "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt] + if ("IF" == headTkn && "lit" == headSta) { + + // "THEN" not found, raise error! + 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 // if_equation mode + ) + 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") +} // END of IF +/** Parses following EBNF rule: + ("FOR"|"FOREACH") , expr + * @return: BasicAST + */ +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 + + // ## 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), + 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] , "]" + * @return: BasicAST + */ +bF._parseTuple = function(lnum, tokens, states, recDepth) { + bF.parserPrintdbg2(']', lnum, tokens, states, recDepth) + + /*************************************************************************/ + + let parenDepth = 0 + let parenStart = -1 + let parenEnd = -1 + let argSeps = [] // argseps collected when parenDepth == 0 + + // 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++) { + // increase paren depth and mark paren start position + if (tokens[k] == "[" && states[k] == "paren") { + parenDepth += 1 + if (parenStart == -1 && parenDepth == 1) parenStart = k + } + // decrease paren depth + else if (tokens[k] == "]" && states[k] == "paren") { + 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) + // break if we've got all the values we nedd + if (parenStart != -1 && parenEnd != -1) + break + } + + // unmatched brackets, duh! + 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" + + // parse function arguments + 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 +} +/** Parses following EBNF rule: +function_call = + ident , "(" , [expr , {argsep , expr} , [argsep]] , ")" + | ident , expr , {argsep , expr} , [argsep] + * @return: BasicAST + */ +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 = [] // argseps collected when parenDepth == 0 + 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++) { + // increase paren depth and mark paren start position + 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 + } + // decrease paren depth + 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) + } + + // unmatched brackets, duh! + if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets) + let parenUsed = (parenStart == 1) + // && parenEnd == tokens.length - 1) + // if starting paren is found, just use it + // 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] + bF.parserPrintdbgline("F", `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth) + + let treeHead = new BasicAST() + treeHead.astLnum = lnum + + // set function name and also check for syntax by deliberately parsing the word + treeHead.astValue = bF._parseIdent(lnum, [tokens[0]], [states[0]], recDepth + 1).astValue // always UPPERCASE + + // 5 8 11 [end] + let argSeps = parenUsed ? _argsepsOnLevelOne : _argsepsOnLevelZero // choose which "sep tray" to use + bF.parserPrintdbgline("F", "argSeps = "+argSeps, lnum, recDepth) + // 1 6 9 12 + let argStartPos = [1 + (parenUsed)].concat(argSeps.map(k => k+1)) + bF.parserPrintdbgline("F", "argStartPos = "+argStartPos, lnum, recDepth) + // [1,5) [6,8) [9,11) [12,end) + let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || (parenUsed ? parenEnd : tokens.length) )}}) // use end of token position as separator position + bF.parserPrintdbgline("F", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth) + + // check for trailing separator + + + // recursively parse function arguments + treeHead.astLeaves = argPos.map((x,i) => { + bF.parserPrintdbgline("F", `Function Arguments #${i+1} of ${argPos.length}`, lnum, recDepth) + + // check for empty tokens + 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 +} +/** + * @return: BasicAST + */ +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 +} +/** + * @return: Array of [recurseIndex, orderlyIndex], where recurseIndex is in reverse and orderlyIndex is not + */ +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) +} +/** + * @return: BasicAST + */ +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)) + if (isAST(tree) && isAST(tree.astValue)) { + serial.println("[Parser.PRUNE] unpacking astValue:") + 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) + } + } + 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)) + } + } + // simplify UNARYMINUS(num) to -num + 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 = [] + } + + + // 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") { + // 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: + // F=(lambda) + 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 +} + +// ## USAGE OF lambdaBoundVars IN PARSEMODE ENDS HERE ## + +// @return is defined in BasicAST +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" + // buncha error msgs + 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) { + // register variables + 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()) + } + + // 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:`) + serial.println(astToString(it)) + } + } + // function in a function + 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) +} +/** + * @param lnum line number of BASIC + * @param syntaxTree BasicAST + * @param recDepth recursion depth used internally + * + * @return syntaxTreeReturnObject if recursion is escaped + */ +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) + // 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) + } + // array indexing in the tree (caused by indexing array within recursive DEFUN) + 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] + ) + } + // closure + // type: closure_args ~> (expr) + else if (syntaxTree.astType == "op" && syntaxTree.astValue == "~>") { + throw new BASICerror("Untended closure") // closure definition must be 'pruned' by the parser + } + else if (syntaxTree.astType == "function" && syntaxTree.astValue == "DEFUN") { + throw new BASICerror("Untended DEFUN") // DEFUN must be 'pruned' by the parser + } + 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)), // the args + 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(", "))) + } + + // func not in builtins (e.g. array access, user-defined function defuns) + 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) { + // dereference usrdefun + 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") + } + } + + // 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`) + 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] + ) + + // unregister variables + 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") + } +} // END OF bF._executeSyntaxTree +// @return ARRAY of BasicAST +bF._interpretLine = function(lnum, cmd) { + let _debugprintHighestLevel = false + + if (cmd.toUpperCase().startsWith("REM")) { + if (_debugprintHighestLevel) serial.println(lnum+" "+cmd) + return undefined + } + + // TOKENISE + let tokenisedObject = bF._tokenise(lnum, cmd) + let tokens = tokenisedObject.tokens + let states = tokenisedObject.states + + + // ELABORATION : distinguish numbers and operators from literals + 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) + 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 +} // end INTERPRETLINE +// @return [next line number, next statement number] +bF._executeAndGet = function(lnum, stmtnum, syntaxTree) { + if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`) + + // EXECUTE + 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) { // LIST function + 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) { // SYSTEM function + tbasexit = true +} +bF.new = function(args) { // NEW function + if (args) cmdbuf = [] + bS.vars = initBvars() + gotoLabels = {} + lambdaBoundVars = [] + DATA_CONSTS = [] + DATA_CURSOR = 0 + INDEX_BASE = 0 +} +bF.renum = function(args) { // RENUM function + 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 + } + } + // deal with goto/gosub line numbers + 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() // make shallow copy + + // recalculate memory footprint + 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() + + // stupid Javascript can't even Array.prototype.remove(int) + 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) { // RUN function + bF.new(false) + + let programTrees = [] + // pre-build the trees + prescan = true + cmdbuf.forEach((linestr, linenum) => { + let trees = bF._interpretLine(linenum, linestr.trim()) + programTrees[linenum] = trees + // do prescan job (data, label, etc) + 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) + } + + // actually execute the program + let lnum = 1 + let stmtnum = 0 + let oldnum = 1 + let tree = undefined + do { + if (programTrees[lnum] !== undefined) { + if (TRACEON) { + //print(`[${lnum}]`) + 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.load = function(filecontents) { // LOAD function + filecontents.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) + }) +} +Object.freeze(bF) + + +println("Loading program from ROM...") +sys.mapRom(1); +let ffffff = sys.romReadAll() +bF.load(ffffff) +println(ffffff) + +try { + bF.run() + // TODO turn EXE lamp on + // TODO turn ERR lamp off +} +catch (e) { + serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`) + println(`BASIC exit with error:`) + println(`${e}`) + // TODO turn EXE lamp off + // TODO turn ERR lamp on +} diff --git a/assets/bios/pipboot.js b/assets/bios/pipboot.js deleted file mode 100644 index 4765340..0000000 --- a/assets/bios/pipboot.js +++ /dev/null @@ -1,7 +0,0 @@ -println("Hello, Personal Information Processor!") - -while (1) { - for (let i = 0; i <= 160*140; i++) { - sys.poke(-1048576 - i, Math.round(Math.random()*15)); - } -} \ No newline at end of file diff --git a/assets/bios/pipboot.rom b/assets/bios/pipboot.rom new file mode 100644 index 0000000000000000000000000000000000000000..7c47c11abdf4911a99e11686fa1bbbecf051a3f9 GIT binary patch literal 31077 zcmV($K;yq3iwFpb9;aaf18`|@Ut(c%X=7h(Z((F*a$j?Ab#h~6E^2cC%v*a`8^@CW zU!S6x>y2iR5J=d0;NV1nFbaCGlCbd-0wbw~Cd@;b8G(sSKKuPu^}HpVc+Wj&*C!{M z?yjn?s;;iCs&2No#i1|FP)^18Q5-IP%UcE`&%ZbAsqkFU*lFx+?Y!Em|4`cA7Vo4V zSm>?ScEmv1(hQ_H0UO})v|k#1;)G~dj!Wgzr$E`>IXFf@ZF znM%_xS+1b5ct|{0;oMt>!k0nlTN9#D6_z`(ms8>)9=0vVie?~mRzgq$(PaP$h|j8U zys0&#U&&%EmSfur=2bDZi1c_FqBEe*iF64Ia%_9P2&8S7K*0jvENjXg!w~BWQfL^d zD4?E)x##31v4YZU>ARRxGS1Y4%6P`#8eIU!Q8ZMF_w|SFfY_=X2kJn`XI!@Tc#}*p3n0Y#jIiela?6v zk4NvDgO+Fy#o3_$u6@)x5?XVJwpJDI+oO~I`A7g_(Cm#qi2ku?_CAQycJHVvS{G-7 z)^I5LgHpSD)@irU*X|v5&X3x?x8eYJz5Yma+THdDL`Qu=6Go)ktsxQXwg!hMXf_Yp zo%ZNMwRGGb^@!$ie;}ITtT`C956?TzfjB!Kob`t-On(Hjy>{<-fLU7IR&P|ptmqT1 zcW8*=Nwd?TsY=ara6cga#9{yJ!=U~4WF$`doud{y4_e@?dC+O8sUXy0r`hgS#Zj}{ zeA{BIKBx>z1g5wX?@wCPMe{ZB-{Gj;?~xdX{oZJRW))HnMhWWs_OMkI%|Uxe3OOG1 zyVVk@6BvCa0=!;JQ6W`}+=>8%+ULVoLQovFnjO#@5;BPx1J_DobGrl+aD$Kr-a$m7 zx-T^N7cCya3Ll{_^p~<^%MetKS{K&`O-!*bcDRe=XfdX~LEwh?=9?7Z=-}=1W6e?X z`tW=(1h+9Y)z|M0M?>^oUX{4_-jPmV{VCBEJO-h3YL594eY&B>C(d-doH2rcr?lLu z{I~~q;7Z{xow4*8>edUrjyaZg0LV{I8O*M9jy*3t^DQ?_SXua ztd6g$my@e2>Ju8i{(8fmf4z|(7rsU(p|EDqHqI(`t+a4(g0y)D*c507>~EEU&~BJ5 zT|Qc%yWY|<$F|f1LwqU;{KMp?@0*W5aSMd?CN*nTz`p=Aus$JNd$cAI5+sl_*x@uq zggE>A>A-qCcgboc&8L_lR0X95HS{~6BD9!gjW(9**NqxX?KYefz|MN=*aA|KjYc;h zve_eXF$yQ#+7qg$TsDY~!d6=*^q?}@(jkW4h;S3(O{%+z?YXkgD%U+|FP`C-VZT>f zU@@i6pGr0y#eg!z6C93>a6z0Agx~Ugtg8HoeJ~A#YN>T#x8yG;FgU$dt06W?mWciZ zOPqhnK+??=Y7=t-R^%`=CwC*?oJgIFv6`Jzp*)ome&Fibhw}dcJ8D~CA1c>{uiDt0 z_Skrjd0SwFk#wzX+wvB>FfwhmYcwOl>Vqp2EI)_mMdJm`sdazw_&?J3Qj8<(-r`sT zq2r$z$xLz_S;Qeijbx(OOmy#zZlW&WdWPaibKeZs~%b31YAVDE>}{K`PRDJS4`^SS%FBRqico&z+_iUDdDD z=1ya9*BdS;^J6&v6tss^0iApt#BhjQ`?DkR^acH_*GnIPE}Q@zlv;)1#75SiKyE#w z7^5J$3p0MN;$^JL>XN`u(>b+%?q2I+1iHE&LFH#QcH)Ss7 zDtJU{@Np*Rw)DTwOoogpc#Ediv5v77on)+K9eip$MIBnoH0k78WfsTHdQKS}HpJ$o zSOx6r%r`=N@d+bOiNk;KDf*yZL%Yea|Br=*D~Y)G%)o)&W zBA6#}?ji{C?0Xm*2vJBek$X4BdL-&p$M2p{G9kxfQTFmeMFi-Oi*>>$AWpS4I$Apq zVjCan3=vkxPc7;pibgy`eo58|^G{ahf4cMzvXh*%DM)j6IOYPJY$<>{wY_!LYPXK>K$6SLc}X&?3QvRnjsc* zkvYp(cw&a1-7D%0R6sd#E9}(qliCa$X%}Sk!En~_T59y%T~DJK;&7z$|L`gPAG)Cs{Wzik@3?C zCb(Eenzvl=&C78x@%yCS0mmP!UIV+WsA+oNEv@a-dWL{8Q>AmcdD;omd|i-$MCQ+yuS6##oU?%x=|Ik5(glCOqPCdjZrLEi00f1L#kf6*grJPn0N9K z2|6{+=@e)Ls-j_#IY)+zEk9JHfkfxA%CV_9?@U2$}ROf6Lqf-cYtT#NGcpe(2tEJoD|jKx-gF{%tmRU3_%j1ntf zcCgoboYrptcDb|l!&OCW!Yg26 zZGO%GRxWF@g-&Wze%(?KKN&C536A8XNdEe&l7%>dt7C|r%ZrapskwD*Zp{i{2bLRy z9gG21kH%?RI#l4{;1jm%Q++9tT_ur(r~ebdz97qho!D*U&C7Y2*Pf;w#`2u3!^Z_ zTbJDAwVGMu*#apAJkoe+*`eiLYWvz17i&o$2WDVR-kF7XiUm=Tb?j@Xlu#SrcOoJ8 zlk#0=Fv?#D4I2T9ZH}QQ6B>w;suBD@^-uW9D-hCIgpVSmn<3Yms`@8Y@I2)ZvP_}7 z3Y9IwIoxP)44-ayq&bV(<7&qBHJzoOg*t)jakDclol%JnLahJ|o_!rN>vwvKRarq1eo9#6fs5Ex{MZBp-A<#_e3%Y~*#>yd! zT)-&wc(IJi4aHZa$tOfw5s?nqf7+w0DeB-F?~^9N>`N@5t>H(=AO;n`GCz@+K*Y!Fm2HWet6?2T^H1P`Wgq-^#w?3nE>}R0T38`8oup z37INDX5?N$kZZ!$oQ!n-7nDW+9_Ca;)$2uJ)q*FA(Vf~Nzol5YHMgjXG0wBQ93U^n zrxk|P1X@*F8DUOqZm$2SAYNvbQ_|tw_a1~RA2L@9y0&!hT*{_cc)E_KLzxPYjxvkr zA^=gqx-H92f9)xp-KXYck{*2QEU7o^ET40bq{ch(hHjIHNI@l0S0VQ?;h@+oa?Gi2 zYzg|!C@DV3KWP<^t7uPJeAzUYot4YLl|*mlSkI%Rd7Twji%A^`XDQ*-a+k&Da>1wE zP%Ys}T;8Nu;TMUnCtNMBF<+dUR(QE{^|Z$y^{b~FN_9A}H2LBbevY%v)Vz!Wf90$4 znv5!S>E@k8(+?_ z&nPb7Y&_S=2dTRC&-T-4x7y2*gve~`+-MZqYE(qlNS%o6s=`}TqdV0q-Hlb1ZB`RL zt6X%ZBQ6e!5O4CElsLQ>8%A!PNS}EH>xK%a+hs?LNgJkZKZ@yca<_$5r7cR8l3K0C z`eLt3YZ4{wWJr1@70>x_PJ*y~qEtK8TE2#*%H|OS^Ozft<{d0um-D3j|NgI@_jp#9h z^4Z;UtfG3i#jhSzuWdeTWO7bNm}&rP;cnED&RBk%t)euif%6Q;E9srKcWJbcN>JL8WyJ1=*lDW5N_dnx4=)P zjM{Or5j*wGn|*^S+}gs1wTvIt8^EP41Fclw5uIUwFxtPo7+(GLrhP0rt)6}{G=8kp z>!+URwL7BK8MYLde%$ViS_A#^6u_rHp0t`r;$m1GH30=AL;Oon9}(Cf%zv#zd$YfS zS89NdLF-+_Mz?vUU$*gaG1S{eHDYP2PEX%<#BkI)6I)0pc7fFH9Tt(=m3kH6rI#4q z|8C=ljh7p*HX0kd8}*IvH@+plN>udiHs7`nPg{c+eb5@|id*6cO4Bbdl78@}U##?E za_R;D^?FS3x&zCJY3m&fLAJ9&yEjSzbR%Bd%)Sw1T)~VU9wzn?R8=kXnI*@Z$NS9A z#byJ}?`_ohZ5ItN*xOHey)a5FHFL73_|c9nKcLqW4|}2L2T;UE{S@MZ5b;zuluIpubI_`+4Yo9=Z=`SXe>$p`D$J@LP?n)y9luqs@f5_0KHx2?LC6E@4CJrNLw_CwI`>3awhSEJd1W#s@;`(tx7z zkPRvD963iUWVsYS$5#hb58&q?LU&B8>||*JG2Fwkd1hqDsfbuzoZqna+HSbu_MQln z`}|lbaRnjD9Lg^~#k@b2)xjFwcCNmUNt8&BWb%1&@(?zGN%Y9iK-2y&T!XIS#yy3} z-o|?>v#!V#RH9JmQkAEFNIJUU%vgrdqAQnt!RY@;>4oZDPKH9QaIHmt;l^o&xWu;9 zwJaHq&T7Csrfj*gcIZ?-8hJ*tKC>46X-Tlc9fOwE4-5a%<>Twr4p{;M5z379^D)n)CrCWh@EzmHH3EjoO*9(;4LfSb8j ze<-$k&!4$fT;Z*FHL<>~3<@;Mfub_<&ZD{iv8==kokit9{lS4LkgZwnmS;~>HSaA- zUntDdQ7}&LL8HeTYb5%2d+)y2Hj*?9|L?01n!Jk@j00p2yCjftz$EMt%wu2<591B8 zEx?0qc`TWPO`Mx~@8P?E_sYJi>VvvlEm`K2%x?0VF}Bo)>h9|5>N=*RaY-$GsBG73 z#*vAvyyE1H&1_j!xgs*UrjF8-(@T(2mSL7A*Nwx=Dr(N$7$gfzZL!pG82i1x?KljC z|A0v+U6W1`1+~$r ze@zM%UJlVdLrNScGD}z}MI-B3loA{iF`!8Eg^FW=a5^UxFhvF!B<~bsG=N+!1SAv#I7J&XK%AykN9>Tn)mA3hv-J$a z(8^?KkhsR@8J3rrrb_o!@cGstyxrEZ6b&^h5??f9M0k^hcVsol-wV_tie@$>P;3~y z+lTVdv+4kZO(Y}aWmE)omW45xYKF+lnS@Qe#aG;UZ!#WVZEtE=`x^}dc}&w0rjh$|O|os%uNV9@Qg4zs*N zw2N7QkPIs^>mv;%dRK5&<#|*=GayZ|rrDx+8YgRY!Q-vELsYCW;he$}&OwoAe^%c# z_G{bqpKE(9*4Dbxm{CPDD-!UU`_*O)!*zD%fdlh__I)rbD%Ydf-w5^mS(@i!b>ZRw zD?wEC=mL1w@|kB&`U#Y5c{aVf5WQN44=aVavZ?2#yv$S!K)j#E>n&meWvu= z3WputYjWhH3pW7Px#;z~Qe?{?3(>3kv)Z9X%B%vjR#H)umlX$UI2pz@gjwm7Hj>Fz zcgRtGc{Ovt%3A3So6qtZI}!M~4x0R{>&H4*3T16PD7dsLjbzTZ@IC{V6wP{;7CKaS zRaoSH$Tw0VZA6Fr#J)=LXR}P+aGS$ z7+cK0wUOz=n155<|46u=*55CWXRYq|=Ho|$rrj==x+t)xqg0)KaJkshU3VWpb}O#5 zwszM~TJ#f5)8!Ak3&F3I$_jgc5JO$%ZcC5Rk_sO0ZtooLyQuwVki8{^ABA_I_;d)C zyT7~Vt|x-ppZ#$edJ|?y9tqRjgI%)>S?M3_a`Po#?5K8}QOFRwD(OoUWH(FEu%u$9~i$FBlSB@tDEN~{^!kp%>XGfW-@6jMqy zl8b{8`sbnv#cC;ejspLHL?B61dadZ3@H=T`lSwjHopqGi%w=OIjdBt@yO4vQb~>Oz zSRNA)8P5^ew0pvNFgyy$ehB+L$A}_wvywO`aS7q7Ab0@9lOs~ z@pIM*G)Xm3>zp zA($ZtGUOo2x1#Y^LA=i!hhG(EFo`h=uQG)sX=P7M(Gh~Lpct95hz6*xTEHAGA80Z_ zwsbGFN~#^hG+Axwzb2RaR3aZb1k2T(qS}l7hKZP-E|tpy2pY^P3)V!xMYUiwJ!O$& zM2AfbFQOrrF?89V!+>lbQt&;L>oPM)co|Qk5(<*TXws%gehe+)(OEdg!y}S)E`#fJ zG&KOaRACC1b>~DodPTjYAs+OS{NBQ0|GLCQb}0T7l&VZY8$00;Lo!D)P6q|>6@-w0 z920?AART9q9F$Mz#x;b6tH_nQ%n?;+b4T5=GUWaygundHEIYXo>#R?V7W;m zhAE+d&A1&&dB*M3RO$1&Q-1UT`lp z%W%i!v5LKvK8vF!YSF2BDP?g6kD1zGHT5d&GuB*xH-Q^^Z^R#tZO4Y%=iY_@&tw#D1vKl=lOy*&=Sc|Il zs%CMkRL%~0pqrCfqK)w-K7I5-?x*Ra5Bl(@gg89(R++7nHzmq*3Is?QYQ@pWl|rMS z$})OYq@d%=zGSZZ)F`WBSK5s{jcqiAL04EWxJ#!`9{c0f$4~52Y_lEwz%6#yg&QmU z!3kP7XwOf0?x^jhjEtD~A35ns=K{qqQ5v7d7e0?zjZ7ml>WCH-iVcHGe5vO~cN0^+ zYBQZfTiS7;hOOh*ec_s#Sj}X)h%!w!6X{TB8rRcoAaFR5!<5L}0}WOobjHH@RXXn^ zmrx$nAn96iHJakyu*;IXXq>hyN^Au&%+B5~wcieTR2>=+)er~wLQ0MK*P+!Zibm^c zF0!G=kKXtrRjOb!lVq!zFI~_KBq?T;P0nEKq7&WP2B+9kK^o%$vmSTE4IZCd8;l1;i2pT0oP>39@n&&#n6HF01Rj4`OTLmNVvR_9p{ z$3!O6f;M&ijTlZ&Pl@1Uw|&jUnp>=&7#1$%GEuem7--ssZDtD~7L3|nZ)$yNeK?=F zv_h5&noQ?*4Cul{rp5-B7SXK}(G$;HA%iT6s*;LH#}2%nSzHN=Lq-f5Yus>%ovIPtxKq`2^9qlQzMG^;;j89EGo*UC{iEYblFvz{LZ{9pTou+)Vcq2C6HXi{q zXI!6kVSp&Cf$}HD%J-bU6jV!EA3gfOTAw~Mv2C5bOA)TS9$>5*2zQ(oJ~+%%J`Y?!;`sbVTR-G7z3a`lRk-l*i7Jn*Kf@sOtWIxez>P$ z3`4$bl?NP==KIDkOOoX;%^c)h1LW<~u#5psOZsuBGRMeuz+JQhTyAozm{$$PhOBs= zb!3wv@wTIJaD^%6V1qEGD$VTX-veYl_K;@Z#DI9O1M{mr|iCJ2h^UeIqoMHF_Hd zCKbJn8nC;2&}-E@nZx{p66R|;`IlF2D8*Ps%MB0}Yp>EM2~aHKxT=m=SlqT&2(0hBk5O@#9c^?fG)C31j$MFVh-O4veD#CD9!|>bkqjGPmlG?)X_-A z@U|@D++f&-Q0Dm4Q~Omg$!aeymGq5=KrFd<1YF>*%eAw!)Be3>ln*vcb({}2 z&`o8i$NRPBZ*O+@j}MdHSLf_q`T%D3PF*kewN4NA9-z-9z0bYItKF^r#?ig^yMSNR zo?kvhub-4&U)J`w9|)oTmY~ziM)QF>{it;M>x1?BucX(7`8nFxesmf1`^!;$-47hh zOMin?_Ct=igV=U!sI`;+MN4?^tL&F0N4Lq z<3fhPph^F9gKGp?Z5|vSzP{th?q^PR|3Xa3$@rp`JK6o&ligP*o4&T}&1(?cXuA8x zbboWx=?2{1svX_^grl8*QItX19|6neU*iJM}j!)<%Je!nR=HBeX zKW39a-=3?|c=jy43TPIKF4qW*Pe>nC%>vKhxK|h(2WQmcddKH{D>9T4@*AAZ;EZMrvuq5W1dzN&DNt2Y`iJ=72KoLr_~qAaz$ni z{Auefr4c>Jpm6!OhUvAg!pDz}^4xWEJ2N0QTCT>zk#!;8@kwdXs98>fKD)L)$qb7{ zA+NY69Jeqd9B4W2Qo0GDi_vJtiAp$VKiN^)EM~*%gwhaEtH0U(O4GhPn)c;UwAqBk z25-j3ESp3?iSw-oqw%tWv?E&q>x1;&mB_o0sUfUjOOd zbq>^b5L_y%hgS{t!d19m^pImIqA0V`c^HR#ej5I z1;thRMyk?#3N~tsh^BZR^TlMRs!SSwWs&<_6*nxq@~jEY=m?}}SAjotXNZliw1D+E z9`e^n?j!aPZ!COwaZoMc>SV?OL$)y)@H`0Y`)Z4=_KlRq|4EBoHIiWb$1OSBzx5?= zsvG>C7$HOGs$t8fvmzNt<#6RmA#{#dhE^hZ`q&?K!$H}5@}$gPJa*Przg<~<3WwX; zcTZPWR^j^gf1K6tfB4~twbk$OsDAa!GV>(qh7?Mp7nRb9*Ptm)oY-s}KfolhF(D73 z@QXWp4Ruf6XV+)_5Yb9@J)eM+E3sg!hr{bZI3cGUg8r`H*{bjEJug|0UBWDrKGk88+!eSq%>_)>c&-c+ZU3 z&nkF{fqr7pt~W+D9!J@ReGD zG8dyLOK~t5g=2qw zO~w85K!XrNJDy62&#u_g`ek$T0|QHVZ@iuscBlq2+-847-3l z3yX^~gh0sji_FxLAWv>STopXAC8v>*4E;RBv6Da8IOt%gm>te^36x}UO*!srgO&0X ztDcezOmO_brfaRZOUY0Dwxkxz%X6#>Z-VzRrl<}l@lH54j9jLAG}2k3Z5VqCbInqG zWgSY(oG4k16^u+OwUV5`rOXuSta$pWLd{y+Vj*vN?BKG9RGVAH%ZaEuqbv4Y#(2=i zQO>Wdt1&F9x3d5!44@HZ!=`&%8xX4kXuk+}k5bVtFr?0P5St+DJI;npLK4fjvBX`r zrxk#+>_~+ykJq7NUwlW+j*#q~WEEh%CW?TG)FhGmn!B%FA33$1qk5Cx0M;#cgH9h6 zkZMBknE_X3;?W!nEaEBS88U{IU>9BFPywiRuiuNWN!2Qe>1=J2rDsD2Nij*r!8tI) z!B`MvcAm{zmXthNu1A)nI;Ek?zgoDkyT*PJ8-+&C{KSc}KY3hQU16zfpSkZaQP-+72>7GHqGMeF>6;>r3@9jUik zbtS2q%qOp|=nhYp&3{nOf&c&C|CfL!y8Gzp?aLlg!!rT>=wx=(OZw>xYLwYg?NOnY zEsw-nI5%C3wHa&ipEqR~(O`G~;P|s&)>(X+86Mxrwc&B?z(Eh+fH1`r(4nN*0=#fs z?^)s-jw;kKFqLHw+UgC&@iliy0$ypqaa3P-fHRG+Wo^zbi+U>+jF#TH0Ky66G#Z7& zF3GZl0$7<-?311-X40byE(^D5@>U1q@Ejw903r{1LrP`P z8=`Jda%vXxzE2BKeN+4O?O|=F{&si&sQ#+ntT-7j&}2ZI01DTLLfghs!#VTE%lNXk%X; zZT;7Shv_zWKT^7_9X`-#eSL9jTa5?mS6|)Q(L;?_U*5z2X+F>%`g0tso9N{O_1kG{ z{Q}K+{a{4nLZ=$9AB=2V=yc=tgAoq3xV~;X8i`d<96++_IB&I zdDv*8DYs~@w*8m5qL9NLGV^R0=s|RNq6}$isZ}gIRfu5}w}DP@apujg1lzCbM>kjl z;dWSL5Oa({b0TE+LI+2?jeX?z=J&9=k?jbJqeK=X{h9bc_K2c(!kUspy5Y@=vopsK z5R?(z4&lz}={Y%*j6y8AhOz`q(y5v-c}lRYCy$-O>u?nHqIKs@?LY<=t*~z>t;5?s zyt*e&`Yh?n?kOo##heX``l?nOm#k`~3=ZWfERl==dz~)t?vk#L#V)cvH6@tnJg@7# z#j9Clf~T_?_@fbv2TSr7cZt_uDhsnO{($r^EU>JLth?cxB_UczpC(V!&j_$zWNQ#4 z(6TorEu(y$O3=k$D7jqBTtGOH=6av>Vi7cP>Ls{0^?qoViVN5EOHAHyWv@)RIRD;kwB7m#-pQ1)FozNGlFn3ZlZ zLdks_=JuMjeeEEMtJUfY!sG46%^dAho@4y(Q+rw8yFFJ%-aP2SBdm8!&R=6NlY+%f zk5JV}It2!Cvi6GikZc&OP_U93Xtl9Fmw^c-?~*%hpHx#^byT5jl1vp}wgl~F5wcfu z-mPZ6!%WqhTi{x2KKm?rH~C{QVmV!D-PPn1bV-u~&`rjqNuDBRP7JEh*oHWfw&H%( z_y66)92r5!N4Km$CW8?}1wpS^Nojt?fjffMD02r5Q^M0q+#>l1$RmLH^a07zAEJAj zXA3HvM^;bwiF^^D(%b-D{8`)5#h=3<0JVFY@*S3>fHp3nBJ&1oyH!0)(FoZ^OR63A z?>$y-SwtKKiVJt_bw>uOd{g`NL3)2~?H%soKh=Lb+;04`f5SWZWpJ(J7Ll&0%ilsP zYu|n6rBjY9e@g*YHu2lv;oI`EO4+bIcB_=Grk9?VPOgmQc#y#7^9QEdI&L<9bHb5g zG*XoBVBR!Vt^>1=%*Al@gR^+~0uQHoc#Rup{&{%}Oxf;!l}6u^>DQPod6K9V)0|YR zh6j62HY1GzD&GCOUVOp2<~9RpH>r6lS=ut#6;{D&Sw~LvFv4EYJ>of4alFJqC<}RK zv-vt02aDXYg7<4p4V5#I3b^($aU503>4C3)JE%8yZZBKSh;QD$N@IOH<%!1Ia^)k} zs8DTR$q<-Y5S0aNO-W)XWQm?F5+iO07BfKKA?>SlqzPFT4l&B&>t<$j1@I^g4aM{@9h5irmjr8xGw>(6~1FGbMFF?GOx>0kWG5g1(b>31p#AsmXQ}( zrf!zQv*n~UWX=A-7LyBymc%c`FU;T-Y6k~e1e6C4h0Z$*Yfku=h^ z_8>Dy$^o@*&o6UCRQYAx4>#bKIjCNdB%g&6mGTBFBFq~XW&Q2uH*`fL0mRWJkKn{i z=981r-kE%e&bu`)F=E(0-eQ{RVK^FxT|D3J{G79K`s{iO>@{AY?=o+&zza4s%oyCA zbYOYs%&CizrOH2f%<^QZNUvgjR+{I6po(^YN{;-_WmX2~8JU3PZnX?x`uIssthQ1* zS3S&KfZ-$%-vJr?5nDCX^rZDgT+n#b*lyr?=r6wv%dG6^IPM%=``zy z2aWy1-Iu$2yGOslUU1&jeyTf%$IZHP^m_Nu**V_dLX#kT#RF|IU%8)l_qUzKj`OCm zU)x?Fc!dH8n9Yk|EC=!g^F3|clbmHs?}F0W0g!#9kN>&36j;=jgBi(#@eEdkW<=5%Z4!!gFb}Vj9^%x_Li(`F z2R>yZ(Vh?5Hp>9fY?>`;WlUy*P&ZstxsSWOE>~EwP2*(A=5D{7lu({w@LX%!F2q|7 z+PUNo2;9x-+!sg2KHVc3`=Th=?XbL1um5orYrgH1N3M`s4chwiqWi8)`>o?8yhdC3 zj)@hLE$6E{4wyUo6$O7Tr#5GMcFF90Q@t-sDA+Y;?O8@p{@v&#pRfTFGN&|?c|5>b z`vCxGd$}hEoq>pL{3c72^1?Ye^INbqXIUqvDBb3z+f+IWF{xUIp)SA1!!9djB?N;> zI1K(Q4~$$NZ}xV%1p7|z_?Zell)7%FWp9>zWhZCDmH}h4esph|##w;92?ikpObALP zSpRJ7-jjB1kaslAioi5TA;BO)AxXg8H}#)ux6nedj5>Dk(U>TeNQZ)VlGw>eopTK7KU`*DWCkf$(Nt4b2>;Pp& z;8XA-w{foVQe=M|#2H)I&)>Ye!?WId#D9+Pk2dc@!BT|=)A|*5@?^sI#H2RR{_{_1 za#R1GW4v$I_L%A5G6SB7O>#p|wIvO~Nb4HF$~Xa|NFwN=<}(2uf2N(UZ*jJ<$`*0} z&n?v8D>?WqHGqA*^^ACz@T)<|G%;KbN#cxm?+Q@E<>)DO6EdvvP_Cc&3fGsx!JS>aTsc5A$l;pGEcAz1+&K_qEC4We{dhyf~i(YbWKxcrbf;0o7X_3tCNxS-n z;sE~4bMibivXa@95=PdZBq}o%i4-3nOEWqVKNF8svo$>mtgL;?j^IZhgbDSuA}rR} zGaf4x-G|h?_l?6$?ulX>SQ%U&f>0Mgxk zQaXC10I(o|6m2}6vyCYYeZp(01KMvK(chQi%fFP7oXV<{7!u9(#Bz4RZeVO~)9-)KU7a4Fq~iT|J0+#zwL5K@qI)MbzFpO8eEYp;l=$8#@%{In3f*Ba zngL(Z`&wOx=G!#`y1(nczc=3h@ORnsYB-};j?E`FxfT^~L0eBAv)0y%w6->@HP+VC zB5ggL(H7h&S!>5b3|T+B!AUXT4*YR2Y>a|oA)I21W-Q8gFO5ZcsbCB|Su8KA1r|^A zpC1-I9h$9v7zJ~H^A7`@Z3CQb0~}B9zoq}2D&V~F0Sjp^ z*{E@wu))Y~|h>>&{GwUr9{4)p)&3Y}c|2M;It z>P=(YUH?}6f@9&k6*?Eb6OZUn_^-Zp0T2Iyg*VyX&FA!&L$|w+V}r-&g-qYri8ecw@Wse#-1JK9eQT+hFiWdtw4WiB`{IObLZts|U1LI4P9pAz6;cHgr8!WS? zlu`0uBY)t0zrxlD2L}pVAEz0L$s-e?(;AxZ12AanP|otSz4#*ukg=(JP`^ zk5Y)P(gYsvK(pvZx+ofl808T0;9tc>B*Skd;uzmupwA7iPQ+5cL>TG;ZA;#xOmqbM z72|SrduL|>h5m<4>PJ!7>A~b&_2TP_LsmA*Lxd1@kbDax_2`9TObt->V+>CoQNjh1 zw~B-UlwW07b_z8xf*O;Ez$6mGWgkPbW8ck0Gll1sFxPj$BfF!C=wb z2pInJNgp$XsALdY<#{bE)!CH9;bk!F=|?QPZgd7+HQ()9x}?x`;}AnV9F6^9)T6F8 zh*~E}g)ZecYF!B;fX^sUg!!zUBxc9o13E?=x8I9hmc1;lIDdxlF~YM7?n1$+0=QAZ z!0FE`Z!){3rv_A0rfJ+0o)bd97!Q;Md zKXohchd$oAo)WnOcIx<&`J(a2`Dj1HWNuD?sV@=BDIG@uG_4EpM^O?KBYRSn%l3~y zI!BG4>ifHgb?3()U2neHdm{Atl8Z|UKX75?&_&BhFb53eu!3P6o@04#7y!SQmQ-mG zu2ScMa?(Jllgn1>%@$ijY(xC4#pO%GEq~@1$}V!NC|%L2+-q5;hV8*QsX(7BE)W^5jq<^hKG>l{-7NjbL01#IerO%za; zJjk9rHef?x{zJirY0pY|E=C4ytKoxz7LI1Fr^Sn??JO}&DZ~#u+#!d$mxn&q}zyK+^D~QqeDtaQ($d!yWdIJisNe-sh!tGl2Vy#BK&;_fPF+ zqSajFE-XUqzO2(Cfs525_X^r9e(*E${$F{x40N;99*F&(}}`dUMa{hLl} zCXDTyw)l6wyn4Rj($o*PY6o>RcSrjzhnxeMCTtQ>;?bxF6~Z&7btu_(okG7P-N@T8 zGmgwzM4oc2nSvU=;6Q37(LRGl8;T6 z=fAZ?5n`Me-GAsz68qvcvRCECl2$W7t+|vXAT0xxIJs;wH`Fy3eF2PzPvPuP9_d&w z_nuy9Xlscy@J3s5Yb&^+%^EsJ>(io6JmE3LphB;=fFX3$`!`8$__E+E@foMJN3+@i z`h+;-R=qCGEWO`{?vk;nOD;ZS)iuoHeiR=Zmlbr^d5sECQYsElc(+sTn89{fz3;cf zu`eueb>rQ>Xr#~kR5CIsJ5Fz-yl~KLY*Vm>c+3OZ8(UPV;H82J{`jL)->bcBG;0)% zQrfB}x$*>pclzNlV6KQ|4r-DULZY=+N}anTMHwY+e+V<)q~CWJy?%iE<9Q095D1Dq z#0&MqFo`tH92*F!D4{SA>EU_cbSGn$E+XKD-)gLBv=K-22+*`WoJ9R=s1|fMsBC;r z+wWWg&5A>cQ$o<^Y)-O|!*CkofZ2dlF=L=0PM0k+aHP7+v~xnhrgV=E4%nr1_UT0# zpr;|tpa5jS3*bthpA!jbH4cwQ(9@P51!b?5o6{nT?ubDgHIM7G!*^;J$BBm14nw4s zX$Z?1AP~UV5)rMuLez^(J53)hW9)K(N1)tvbV72p>hu8j(U$)rc>ko`8zvh-;NZR= zU4$@PNx-7XXcWTn1*OrGkS~fi1AlCHgpA=<>3EEOf#b34uDo<}SSdC_B5u1w1tY7` zq|H2=%WD;7TKTrCIBOm-8E!TQlVl)7Isa-&e_vv6=(MBctB!F}n7h88O4k~Om;OcI zcUM-|``?_S~hGcc%+=39GGWa%h^MJ!bczQpqs8cM|_k{86GL!PCejb zChW`}V`?B4K_2NW6hteyaNI4!$h;|M@!+UGdGvwJ`?PhMllwr$SGCU>nFlaLA?2Ju zo5bj-#REn~P8-ezbQ0qS!621uAnbSJ%b{^3s^_LrIEu~lQ$0RY{3dHY$-srPBEtP> z?=F{~RzzvO{cN;~H3>juHkrH=P2v-4lYM7-!&$TGnZ+i68O_)NU1JAG5^mcDYx8ac zE56?|x^G*t{WBo@e*vzq-94_a6~Oh?99%E2djhwk;LkK(D~knqy6#$0I)}Z$oLxER zrFLbJH_s&>u_n6W<4g5>Er6a~&hm%F_{m4oo#`(i*%m@$cN!){m zd8j@XH_72)6~;hFU>DZVl^B6jE?2A%pT|E;^0@O1!7-(*vR@}p@nsSgk46m}5FSpw zQ%}#N7VBBMG{%^b zd-IcdqyPl5Omv%-dpB7`6h#Q8VSQZR*^97;iaR5*1^oz5b6N)$N!!=K{p zd+Ldb4;V%{^_TlQcv27#xLzthcK)37;Ak8N6g#04V4wvm^w`6L1!Y^6T9m2_<}p$sNI6zHBmCIaxxfz?f^D`0sS z`@MLPgj~Rz#(r6GY13yV!`xaLy9g-0xs{8d{kBCI?G@4A-`Is{@ zoDu;bl#e!9=ypHsTt-e54k)B8aAST95W=R7CY=lHm&s3Q9Y|0(42QNHd_tmycETJ5 zpriAC*!C&<4+~%SSkRppT$#h2|p+;CF5hfJ}44TAb?uzR`TL7~j(^d3^k_cCs9=v6o$o-SdHQP=& zK`obqqk||mzo^IerC2eHbQp;Fz;;g9lU6IeP|56PS93}!~ zd}B|}&H%mTp;O`mf!VDPX?f=IX-C$wKf1u7F>2akBtk^)bhONEEEo;!n|{w$^CQZX zosVK4FETgLjvw!1l(i#d_teU(k_zZ43KzF-V2`T~DN^Wr6c#R)b=y239D z^!bTCzoE}6eLkYkzp=NhUkZZ%kwu7-z<*-T_#eu>_K`(kBKTqP_-5V^v%zKtV~G){ z;>#(0{s&M`ycAKOF0H_IE(Hg5(-`eNd!JA zmH6z!`qbEp(B~F?GSsQl=MFpR>GL&x?$YN!>GLQ0++&EzR#+klLpg?T|3jY)7Y`Xm z(&sUK{!E|0&?iIg-{|vy>C>mrwrsaUzq|Aq(B~O_p3~0va*Q`5=-TLLFBL=|3K7qcCEK$fUANaJG(glL#%U zkHfH!p|4ovD-3+a8TfY4hr<9*n5c6R3;?$~B+sI2CC^2G%GNW)QV}OiV+x5ZmoTjj zi^wbv0U3^YcgHKOR*_xome#7wDB$F2m4*9il}gC>Wz~guRGALi@(=?JAOl$5@F)r# zo{Gbu6GRalL;Y)jgRy*B&Cy$G5MSXbC5raN?F!z(A-IxyezHa{xE>ARhhO{EYPE%f zLMFOYYGn7^VBMmYoStAMQ78omYkZ}2@~!Cekr(3O0^hP&;k>w^KfleC)|$=QZxrnD zsCm3~l!Gctr_kX_oJyrarW4rmD)3v@cL>6MMnwV(a5 zOBAr9G)WjzrW;CWy`YNcOw+Atk*#|jL>Ok=B=il8T2^2}JKJz2xlkqY8C~nLWu%p_ zfw4x~{1GFKGPy@tHYiJ{fGeLq`hYM^xn|B`vV3f9D)egAKf>Oyyyz@?)6*L_#kG{F zrluv!lriCqIctOHTuj}UH&p_fn?qWXe8wit4BA+bM~gGL?t41+af9O_1EXITSrAE6S+>uN zm%$9-agk7R^{IODWb7gwzI(_z{{=f22{0YZfa#{#YI7&a6@k0qj8$-!9CY;=PYl@qk$tt1d;X%mh4#7mY-flT0z1^}$Oc&q68aRwd&T{0EKh#qqA3R5eF z`B!6U!{MutF$)Ht`@g|BOn6=Kv_aRylU6B<`6aCtxu>)P7QqHzMyTEM`$X>}QPuB9 zp>u}WA%+1|j$)Py8;Kp|!bT|{m5I?LkGIck7dsue&&6LE?Ef|f+hnFk7G@d|S7WH{ z?X4DV4^Un3@>0*`krIm6}WO5dF;ZX3&-FacrJ|oiyM~pkY zEl?N?-rUB9Q|`eHUo$(w?N&LAdpGIJuQyahiva?uZ! z;8adD+PsC!Vd{06K;W+d-$#t#-w6%|bDh%S_1B1CCJL zFCMe|r&r5~qfN4>mSInMcQA%dI2ibngIsqOmrR%o?mQhWg*~y^7#r5cX6{1w)P?S) z7TU{O-*(u&)|PRu^P3VNK zrbxDMFrV+tVnV6j+1r3CofV0c{!|!7CJ(2{S=E?%P!&B%8ncfP>{0TdcwrFSLx%X2 zn&eEBJL@=V+~O=sj50K~T|6~$dKQ)fYsQC}hm@J6G$zj=>I#T?n^ZefaV(mp$?V|> zI>7-10-?X=lq2KyEe4Q2JmA$i`nPi%Sw>A!C?F#343n&f?cfak6Y#q9gsw>19E3dtS!Qn@{I6pWY{bZ>IJq+oO`_^S;Dm(56jE%g|P&WQxnGbkY8DVHy5nnX!4^ zV(|-o5yhSYF5T<}tTUOK*>y5Fn$NXjr0AMYrn1OoDs@SRNNZB9R_9oSWV_`GRa9w; zO&pR3CXs`M9GEibN20Liseao5qr4dRU4{e0z+eIH?#|`?uHG%6^jHSx`u;W^J%>kc zj%I3o@5#mMt7F$4J9ZV&7T4KQ7StBwjpU6Ich#mf4nC*W7)V$&3OX3UQIIm{syB`& z@Sr;x46db>Fw-QDF8pB}4iej8*cHqI`|x0^ytd*!vjuU~LoVosahcH=W-Cqe6sLLD zDA`@^U1~V#_ubFK4cw91`2o*7K|@HAo@pjt?F~EqNjHduUfD~;z;hZK;I3H|M&x)> zF5b0$!M(E&QlFf7JW^!Ti{AMKs)&IR=mcGKV6V_+XAIBdL5NqxA!#)SlYZPoH_>+( zG=V%xmGF@xEZP{QpB#)knigGjFLmQdm{(vV6IHReUZE2wvSH+VDOKDoQcNGC&tO&; z90;2Dz{NFIT|JS|Mt7^%hpSxwI^768wlqS|Q;keUERYfpjVDHRDAp)Z87ii)G%pwh z7e`9VlZ1187qFO_aymqlbXXfF&oCg%XN;~%SRoGfUd*BsB)3&8&I#97a{!AufE*t! zvpGKYFk^ff#sVJ-4^lxy>mXH(6oXWemj zW(+|e3miiOG|jcp0oc_#3COYe&;hiXSqM5+#Lyx&CDwp_Hn%J}@yn~Nik$If4A`fr zbk%?5auVT>U~D;?+L7a#9#~rK1y;LxAu4Mj1Nk$OX(%n~qqPT1<;9Wf!$z`8uv5|O zGi_d?9hR3fLLNP|!HI_ZU!eVpdbDOrPt0p%AsI@b*rgm{)%grksLFqwJxWm29wk|q z2Ni4<5;vqz&V*LpLefbHQ$wHEInad43mQ3}pzVI9x1jKt=D2nL;^Tmck5B518#f^={7p_;Ruk`|b zt*d>ZN?Z#?jT)a`L{mu-P6Ldzd>&GSnb2uZdi^e@7-9=Rel$w$GQ)qM(49U?gd!dZ zZ9DP z&sGh#|DKuRsX#U8t!ehA!pWS??0Kd1-kWCcvznRDoK?~9@=235rQC)N_KLQHG2!xB+H1Vp-NHhJ zT6;OGxtE2T`Z24WH_dv%CQdV(_-A8x-_nL%9eyg()J-ViO*nEEF_aa9K@)E!cNWb|7THk>WLn`uv@O4RNq3d4ubVNavQ zvH5be+P<^}|LVv7RdM}Qas6McxYmL}p?^s62F0mRDpJARTGBgG zYi6N!rt<@(p&GIXscIa;J3rnt*bh9?P7`Q19(_oDOe+!_#B=xxPx-9FL;h`gYUf(C z(?aq7Nqm0%l*f-2h0Pb9KK@*OKB8IfEwx6Gt3vGs)RMX(uots+O3C64y{2RJD(TXe* z^XJt2_DFVj@8G2SH{}KRw3^U-f!5uGP7$Bd3aGs6Gl$&N#_X#24la@fE+I^dIgeSt04nby^5nA__TORHGSlz9 zoQ(X1z7w2v-5pE`eRdHHQ-QaTx}+Z7>gM$Ie)Ba>*Tcr?vO&QjTM3lMSObjAZDiVP z7L9C`Y@)H95fwAVw6{6xDA)wnpB-#M-5I=Tyku|{ zJqbWr*02SAmkSh7uPd2~>D?W&I>xkP)Wxjm5&-(V(IsxG@-ah6@PW+;C1Ed>ABlm9 zn3Gr`130@%X$8-nL~aqwLt3Y@*fa}lv0lhsv|c=@%bm2CW7{SWOK*9WR&TAy%%o;V z^fA9iZ?2j-+PospeEA{(*i!D2#sj%Y+>&NpgaJRuHj}GLcsmXS>w3m`q&Dnu zmY~<^_M#Z@ang$};QQHGfc2fr-mn|V03s1;aygxeNZ)(HOdQ#n-%`i{Kom93nkXJ4 zAkwMnzH)dvhljhY7tv%jM!_&)mAo7Bde2f=0B3J>(L0Nkwr$2NS{)Yh$yqEfdX{Bn z{7TZX_qiENWfr#pQG**yqYR1Ei{2n&!mgn9Y*SN71ofUx0g%THo#Ha%5kO$9J7f%E zc7WC*Fb=Jl>{89VYKu3#c1r(r59{LbVGd zW=W43yO|Rv${vshjGEQu^M$35C*Cn?U^mR2%z1ABYNi{y;@(K~Pa?D8{rBpy3PIV@~2PU27yY+!Wr|Z`+}4uvdGE9A5-r%gDJngq&HGxh*QQ6I&MkdpI96* zv`7EhZqVRJUYw(i(2vaBwcq>QG<&$&(V+8L8`Ypcmp%5D!(r`N~*A|WctQfW8z40*#1ED{UR zA0_5G3~On1ijzrZv`Na|>1mFc;tlid=7e8of+sDCqH^_EjNnqh38iq z9L9lr1?^hZ5?N9pDMu-=&1Ea@lG${|Vy1;II{CgE6-}?GqLr=^WHejtMf8{7N9ZzQ zykTv4nJC3VveFU9ijsty5KXajPv?kTK%q3yB|W!2sxPy;I_Rkkp$fW&LB7tdp5DhkfBH2v!#P-dfYnpWL=TlX@h ztyN6kR$6Guk%X&WpBFVUT69^Yb=O&!BoIjXWMC;tPnBZ&(!WqhBN~~^gNe+@9!{_m zcCZG!aD{zeaezt1c)j!s7)_?6Qj6eq3brBMm?&!vG8IZ4d<}~&5gg5Cw}Y4CN$-y# zE1+Qdl|M#?x`Hw|V6z1>Ir+*-sBq+rWuu1p6SgzhpXtI-SuDMZSZG6DJf@D zi|Haaqc@`gZSq=G94vh;Ru9UyyP^fI(C?ig+Fq z2n2ij^RmZ6X){yI@c`Wmn6)?w50KfyoAg2!&`1!%L)!B+5uOoaC_! zc+fDIHWPiMHj^GR&RRt45*edZXc+@?XSr-`gi*Fstt7+8z|9zo)fX~n&M5BGUuw#h z?t99#!+|s%%V)iD1PtFKP4?BX+9L|;C96Oj>VD^fvtJs^!ub>=P!?kf6S&WP_@rkc zxhHKnYqL}?)~ZWX+JEKU;;bVdz4xQBz2IugYUviUyd^;Nh_Yd&yP%caM&J%8r3vsj zAUQvBEGUN@U?hJnr}JCRme()@5%OzdPAYiJ%|8ERpe?`}MN1e!>(ca}iP zloY_3UitlA*H)*;EFMI;-jGV-#yI!KUA(qnQZbP-Odh@EUNxvX-4HopR9IVd(W!V- zTEgmfkE=RTx5Bb=6nmwahT?_zq6|zr?a^Hd`3MqEi*Bc=Q34Q6eRmpFW`bphHC72N zbd*y{5LRN&xUt=AczDdKw=fygEN0HL8~dxBPNFgO>t>!30^4WO{9GYX!z21|m6?z^hqlI6nN;hjkBE^v9w6<000v%x7sB8y76U?bOH*Gk{|SX2O;fQzN%pUQ4&Y4eRR!0mNw7Vbrpt z9RplLrMXXrQ{(n~0kq40*@5jXrc>SIshjOhB@w`daq)wC-~JDl259SLg0XRX6 zX?v84(zH}!#tX-XwO4hgv6B+??Cv`UwdP^{O=G)`4m*d=>w2>eRjB5V;_4SUf^)`{ zm_pKpo9ds3aY#8bW40Bfq$?z@LQFrgXiy?NbJzZYFR7gq0 zM2MqyoPk*dOU5oY`QlL0b1>EiN& zlic{yD=)#$_Y z@z@_meLp4^jd#ov?J*GDB^_9A$*uejCnAH0Ze72~kKL#W0iE_T2VY;DB75&x_sLW+LQD8`=CQle+jzh za+GxMI`2hzgOQiHlT;pyx`GO!$;L5z4wQaBjaI3O$dPLRzq%PNA&FAmhF2+hiCTW8b(7`32h1eBC&Nzit4LtX zk~GcL{2=W1&U)y6g!j&3E#^{~^h{07NQ?4b4Ogec{?HzN%AiP+x3|qLr}?TdFfW5i zN~|?~CR$LLkQL7&+`oeZ8$-gO`C9S&e(K~+Uvk@rRp95gv9F_x$?#Ge2}Ukb4K+yQ zOIA$|vbPT7X>orNf1{L%B(ttsb%?iein)<(C>+ZsEGRs?1NN_>ieez5G%9ZZbn7`& z+5w6aL>+&Gob-#FIEk zn#o~ZlL4jK49b+NG-A6hC!;flWNAT_mYIq?y_8TGWvRfV0-Y8;P)&omTLPkJTC9Tb z5bmTdU>t0-PxaPyN>?8Ad=Qb11oojxjHt8jl)1fT1+6%#RXT|iZ&bMt(O_=jpxM~= zn6`{xvywt&+PKBMo%n?-tAg0Lc#Yk3tK(o4_;Fb*>C&nP#Kd*n#F8-Z$Gl1R_o3sE zC<=i}(1sp4c$;>g+ggNc_3vinU1e}EtM*ZN+2sDmAD#NI^{wNhy0Cg(PiE2i@yAoo z=qcTHw)~Qf%pqh3GVU&62TN{2N|s=#r@4=jTQHCcD03`G=7x#(>BAt6^ciYy>1ojo zG35r!N7ajAgU{~7V`32!Iz591$XqPsF+eH&MR~)M4#0^46OG1^Cx8f^=MS&xCaQ2$ zeF-#k9z!ydYfMN~7S7n8KkQuS=w>Gy znWSa#GbYzaCzoNa378WP#tpX??s=`dDcDv^sHis+OI|Lb`Q>QGwWf?rQRi%utYL*c z`y#opqg|^Iej;Wg|3$inGC4+c(Me0bXT@=*$)=UNm@xf;r%U`Y(2GeDizBcht{_MP zyymi#pG01C2iTdU&L=lP_bxZ5uGkI>J^!E2q$buCOQa};RwKiDA|bmm;glGaS>2a{ z6B`?rNeAU>rG^iuiy@?>E~Xy9kb1?S^Hi>T2A`2)Pz})$YtR;K5MQL9>r4txV9Oet z8_5ncZjg#h=x^OoqpvuhHd)x^Vra^3S29QhTCmLKDUN{=9HM<1hr{(0NEKT=8TMh3 zX~h;rUQyU(*+X}SvN)1GR|CKh zuhgIxE&B$Z0Q#h-0M9KX{;?3}vqa=&yB&;15v@gFLk$B)yKG0gXRmcXf|)xvr%o6qu76Q-LjjT?hxwEs)|K$%U=qDuIZZ0?2!2zws7|e|wDHGiAI< zWY!yoqjA_3m9S{DUfcc+D_YB+=>aAr2bXl2Z^GwgS5G>h)r8iD2th4iv;`!|W9k+* zoV6@2M=A5P=(zA73T)9hFz8+p+i*7=Arlnck&xe&@R?Q<`L%%XZMaLavQ#08qhOA< zNadK>Scb`5W@|!6nc1MwXPUCM(ri&Tu4nIz%sn^Mec>L}506~f73Nx=NDx}aibM#@L8pBViYO)kiNxZzY`?u?b;;i3K=2>us0;HZyp7oFtXwl z@OqIvpWE2K6$O265XZLPRb$=lkm-U+x)TYN9xUXp$fVhiV8b{Emw|(JmnC-D9Ao?u z%>pzP>}-t%JIk3Y7Wyo;rmS@qx9H}xaG4IMm?Caj+ERnC0ZQ1&nmUV>WbKrYBsdO> z;)VKHYiLraWclF|8itXu+{gYD8|Gwd6uY z_LEzs@|?yPhAP)uB6E7VN|PkCy`jT*cLZ2lJ+E>Zq2K8QQB*;d*mzk8R8>dB$uLR> zCbWre?R*njiZFW@pf6}yliVgs)*UM z@eIvVxW!!^k%UTpi%+a(7l!q0sy!wqVOFMIQMV42U*;o0CzhcwiL|%{r8>51!gD8o zqli&MzOR;)m!L{YRi3#;3|12EgbSiEhJ>~RSx|9tM**?3fUuHYs`)@n^gV^4Brm}P zAw>(BK1PYU9LAF*2O-4L&u-^`^f*T$HCL=_;*i%O#y&7dWoi_?>Q5rKXSuX^5J!uBfq%YzW z*%Uh_vyB0?X0S9y+p4*In)Q`d7J9Ah9yE|@OIO?j$MB%i6iH%eW`nuS{HI2U5He>_b@yFbFh%^rR$Cve8<{fp(sQ1 z;*+JPF94CqO7({O4{N5Tdm+sKZ(S%2eZkeF24spVrvoiNV`o4JI#5VWN1dj9#x1iD;@x)v6Nl7|Zbxq3Lq$MH2qGzE^wMXx5H)8~bp+s(ECX z*)fYaD(qk)@_G~$Jn0T-3Um~y0oTX%KHS{KerD)Z9^O+7h&Q1QL=Hl{`uqTZ|>%&r}I9n~Aduq4a3~GZzX2Aex3uaNk9MF& zIMD2xQyvcyqbC@vdw2h+-aKg5kM?%=>nagBL0!A*nZX>PQzr8*)ls1~?7j-(LQV>* zYrZsM%Ogn~)}O zcrpUDf*#3h)iGQ-C~eBHloG7oY&M!`_-!%zUtVUxEdaiHeH~S08As#v_aSR%s{!b_ ze{@KNZ=zje%Ogg?oNC&5D(>|n4NtFN=c6cStm0+QdG4&jBGd57uA{_%X0Ouk*w3E& zLw^U0b>%Tg`UVVS{>#kR_jV7Dbl)`WloU=J{a9|IWF_DPYfsK3;n4=_+4w%Au{HCQ zW(`uV6S&7(T^Ur15ZReokqOwy_QR-d<-c&p^Z_r#wVL3h^z6u$R>{JWk2Oi4gGx40 z?xT84kWr6JT_<(=}sy?!q z@5L{#NK!*@&cis|^V_hp@RIxYrb8O@X0~fbwYRX*504JbhsVuBAOd3bcK5gIzrF=B zTc<}fka55QL)nJXtnVKilY#39V>d@aILAV4G=n~IQ~2_vmAvjyW`xz1BwjZ@Rg&Ln zIGIX*r);RpRz8|Y_)a>QU|XWxWeezXZ(v9i4X`P{76PlAF#zyO5_(IJRhcJ{mhQ=U z7){#h*&ijtR)#Dg%^P(xoJ&j8PxUQurF-E!>oo$Qa$GNas%Y5mtHx2oap8(+&WhFz zhA|z#O2co0!45noze7o6jAK;g3pO^5 z!&Ylx2~#HExO<*Bn3f#O>U70_@;os_w$8?Zg=pza0ze-1{vE*ab{@o-D+#(gga>7h zHv(MRFsqfLW^Jnuw@sDkL;16_*%jFRo@#UhjH8fM#bhFck+tzG$Ee!VZkbU$8TGo( zKm98|>Wq6M^xPOO#+b8o$WlH4p9zEjHA__%*`W+`<*D~^N{7_$L#fnTt6XBJ49ab4y;p{=OgjkD5(Tudjv7rR^kFf2O zyMF9r5(?npDozl0yi{oCD4&OkO(BRiuD)zAeKHw~nrJZMeCfYopU&hF$xHz$k7pWQ zq=IXD#&Q}G&;{XqGdt`i$p~6J_68y;DnS$m*vw|?cj5`gu5kExm}4JXR*9HQdZMJZ zmVy%{adz16n#FhwQ(ce>sGyT09f=5oa+eLtZZAWaU#7(=RouT5Hj$wD!jIEPES|9J9r0#Xsg zm%%l>6K7OEq+DO}1UiFeplFOq8HLcNQQOv!BDGL88ufc|dGYt*qNkrA25A*C4}4W6 z_w&YCB7~CnUA{xG5aX&>kV+fzeE0o*=G39hKTm##x!;$OSA{GM#+%bUSyh)X5&q@T7~gM*JOX)3^?-_-yJfnYP zjuBO%a5M0`wSFJKifdx=hIyFuZ&QBsVOjFH0^NPpRE@|S1G w#F+BZJ8&3~;$9+%4#{=s+D>xkb+Z(9aGE;`05oS9Lm0*X4<#5