Files
tsvm/assets/bios/basic_1.js
2023-04-15 00:48:14 +09:00

3158 lines
106 KiB
JavaScript

[..."TerranBASIC"].map(s=>s.charCodeAt(0)).forEach((c,i)=>{
sys.poke(-1025 - i, c)
})
let _BIOS = {}
_BIOS.FIRST_BOOTABLE_PORT=[0,1]
const THEVERSION = "1.2.1"
const PROD = true
let INDEX_BASE = 0
let TRACEON = (!PROD) && true
let DBGON = (!PROD) && true
let DATA_CURSOR = 0
let DATA_CONSTS = []
const BASIC_HOME_PATH = "/"
if (sys.maxmem() < 8192) {
println("Out of memory. BASIC requires 8K or more User RAM")
throw Error("Out of memory")
}
let vmemsize = sys.maxmem()
let cmdbuf = []
let gotoLabels = {}
let cmdbufMemFootPrint = 0
let prompt = "Ok"
let prescan = false
let replCmdBuf = []
let replUsrConfirmed = false
let lambdaBoundVars = []
function isNumable(s) {
if (Array.isArray(s)) return false
if (s === undefined) return false
if (typeof s.trim == "function" && s.trim().length == 0) return false
return !isNaN(s)
}
let tonum = (t) => t*1.0
function cloneObject(o) { return JSON.parse(JSON.stringify(o)) }
class ParserError extends Error {
constructor(...args) {
super(...args)
Error.captureStackTrace(this, ParserError)
}
}
class BASICerror extends Error {
constructor(...args) {
super(...args)
Error.captureStackTrace(this, ParserError)
}
}
let lang = {}
lang.badNumberFormat = Error("Illegal number format")
lang.badOperatorFormat = Error("Illegal operator format")
lang.divByZero = Error("Division by zero")
lang.badFunctionCallFormat = function(line, reason) {
return Error("Illegal function call" + ((line) ? " in "+line : "") + ((reason) ? ": "+reason : ""))
}
lang.unmatchedBrackets = Error("Unmatched brackets")
lang.missingOperand = Error("Missing operand")
lang.noSuchFile = Error("No such file")
lang.outOfData = function(line) {
return Error("Out of DATA"+(line !== undefined ? (" in "+line) : ""))
}
lang.nextWithoutFor = function(line, varname) {
return Error("NEXT "+((varname !== undefined) ? ("'"+varname+"'") : "")+"without FOR in "+line)
}
lang.syntaxfehler = function(line, reason) {
return Error("Syntax error" + ((line !== undefined) ? (" in "+line) : "") + ((reason !== undefined) ? (": "+reason) : ""))
}
lang.illegalType = function(line, obj) {
return Error("Type mismatch" + ((obj !== undefined) ? ` "${obj} (typeof ${typeof obj})"` : "") + ((line !== undefined) ? (" in "+line) : ""))
}
lang.refError = function(line, obj) {
serial.printerr(`${line} Unresolved reference:`)
serial.printerr(` object: ${obj}, typeof: ${typeof obj}`)
if (obj !== null && obj !== undefined) serial.printerr(` entries: ${Object.entries(obj)}`)
return Error("Unresolved reference" + ((obj !== undefined) ? ` "${obj}"` : "") + ((line !== undefined) ? (" in "+line) : ""))
}
lang.nowhereToReturn = function(line) { return "RETURN without GOSUB in " + line }
lang.errorinline = function(line, stmt, errobj) {
return Error('Error'+((line !== undefined) ? (" in "+line) : "")+' on "'+stmt+'": '+errobj)
}
lang.parserError = function(line, errorobj) {
return Error("Parser error in " + line + ": " + errorobj)
}
lang.outOfMem = function(line) {
return Error("Out of memory in " + line)
}
lang.dupDef = function(line, varname) {
return Error("Duplicate definition"+((varname !== undefined) ? (" on "+varname) : "")+" in "+line)
}
lang.asgnOnConst = function(line, constname) {
return Error('Trying to modify constant "'+constname+'" in '+line)
}
lang.subscrOutOfRng = function(line, object, index, maxlen) {
return Error("Subscript out of range"+(object !== undefined ? (' for "'+object+'"') : '')+(index !== undefined ? (` (index: ${index}, len: ${maxlen})`) : "")+(line !== undefined ? (" in "+line) : ""))
}
lang.aG = " arguments were given"
lang.ord = function(n) {
if (n % 10 == 1 && n % 100 != 11) return n+"st"
if (n % 10 == 2 && n % 100 != 12) return n+"nd"
if (n % 10 == 3 && n % 100 != 13) return n+"rd"
return n+"th"
}
Object.freeze(lang)
let fs = {}
fs._close = function(portNo) {
com.sendMessage(portNo, "CLOSE")
}
fs._flush = function(portNo) {
com.sendMessage(portNo, "FLUSH")
}
fs.open = function(path, operationMode) {
var port = _BIOS.FIRST_BOOTABLE_PORT
fs._flush(port[0]); fs._close(port[0])
var mode = operationMode.toUpperCase()
if (mode != "R" && mode != "W" && mode != "A") {
throw Error("Unknown file opening mode: " + mode)
}
com.sendMessage(port[0], "OPEN"+mode+'"'+BASIC_HOME_PATH+path+'",'+port[1])
let response = com.getStatusCode(port[0])
return (response == 0)
}
fs.readAll = function() {
var port = _BIOS.FIRST_BOOTABLE_PORT
com.sendMessage(port[0], "READ")
var response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Reading a file failed with "+response)
}
return com.pullMessage(port[0])
}
fs.write = function(string) {
var port = _BIOS.FIRST_BOOTABLE_PORT
com.sendMessage(port[0], "WRITE"+string.length)
var response = com.getStatusCode(port[0])
if (135 == response) {
throw Error("File not opened")
}
if (response < 0 || response >= 128) {
throw Error("Writing a file failed with "+response)
}
com.sendMessage(port[0], string)
fs._flush(port[0]); fs._close(port[0])
}
Object.freeze(fs)
let getUsedMemSize = function() {
var varsMemSize = 0
Object.entries(bS.vars).forEach((pair, i) => {
var object = pair[1]
if (Array.isArray(object)) {
varsMemSize += object.length * 8
}
else if (!isNaN(object)) varsMemSize += 8
else if (typeof object === "string" || object instanceof String) varsMemSize += object.length
else varsMemSize += 1
})
return varsMemSize + cmdbufMemFootPrint
}
let reLineNum = /^[0-9]+ /
let reNumber = /([0-9]*[.][0-9]+[eE]*[\-+0-9]*[fF]*|[0-9]+[.eEfF][0-9+\-]*[fF]?)|([0-9]+(\_[0-9])*)|(0[Xx][0-9A-Fa-f_]+)|(0[Bb][01_]+)/
let reNum = /[0-9]+/
let tbasexit = false
const termWidth = con.getmaxyx()[1]
const termHeight = con.getmaxyx()[0]
const greetText = (termWidth >= 70) ? `Terran BASIC ${THEVERSION} `+String.fromCharCode(179)+" Scratchpad Memory: "+vmemsize+" bytes" : `Terran BASIC ${THEVERSION}`
const greetLeftPad = (termWidth - greetText.length - 6) >> 1
const greetRightPad = termWidth - greetLeftPad - greetText.length - 6
con.clear()
con.color_pair(253,255)
print(' ');con.addch(17)
con.color_pair(0,253)
con.move(1,4)
print(" ".repeat(greetLeftPad)+greetText+" ".repeat(greetRightPad))
con.color_pair(253,255)
con.addch(16);con.curs_right();print(' ')
con.move(3,1)
con.color_pair(253,255)
println(prompt)
let BasicVar = function(literal, type) {
this.bvLiteral = literal
this.bvType = type
}
let astToString = function(ast, depth, isFinalLeaf) {
let l__ = "| "
let recDepth = depth || 0
if (!isAST(ast)) return ""
let hastStr = ast.astHash
let sb = ""
let marker = ("lit" == ast.astType) ? "i" :
("op" == ast.astType) ? "+" :
("string" == ast.astType) ? "@" :
("num" == ast.astType) ? "$" :
("array" == ast.astType) ? "[" :
("defun_args" === ast.astType) ? "d" : "f"
sb += l__.repeat(recDepth)+`${marker} ${ast.astLnum}: "${ast.astValue}" (astType:${ast.astType}); leaves: ${ast.astLeaves.length}; hash:"${hastStr}"\n`
for (var k = 0; k < ast.astLeaves.length; k++) {
sb += astToString(ast.astLeaves[k], recDepth + 1, k == ast.astLeaves.length - 1)
if (ast.astSeps[k] !== undefined)
sb += l__.repeat(recDepth)+` sep:${ast.astSeps[k]}\n`
}
sb += l__.repeat(recDepth)+"`"+"-".repeat(22)+'\n'
return sb
}
let monadToString = function(monad, depth) {
let recDepth = depth || 0
let l__ = " "
let sb = ` M"${monad.mHash}"(${monad.mType}): `
sb += (monad.mVal === undefined) ? "(undefined)" : (isAST(monad.mVal)) ? `f"${monad.mVal.astHash}"` : (isMonad(monad.mVal)) ? `M"${monad.mVal.mHash}"` : monad.mVal
return sb
}
let arrayToString = function(a) {
let acc = ""
for (let k = 0; k < a.length; k++) {
if (k > 0) acc += ","
acc += (Array.isArray(a[k])) ? arrayToString(a[k]) : a[k]
}
return "{"+acc+"}"
}
let theLambdaBoundVars = function() {
let sb = ""
lambdaBoundVars.forEach((it,i) => {
if (i > 0) sb += ' |'
sb += ` ${i} [`
it.forEach((it,i) => {
if (i > 0) sb += ','
sb += `${it[0]}:${it[1]}`
})
sb += ']'
})
return sb
}
let makeBase32Hash = ()=>[1,2,3,4,5].map(i=>"YBNDRFG8EJKMCPQXOTLVWIS2A345H769"[Math.random()*32|0]).join('')
let BasicAST = function() {
this.astLnum = 0
this.astLeaves = []
this.astSeps = []
this.astValue = undefined
this.astType = "null"
this.astHash = makeBase32Hash()
}
let isAST = (object) => (object === undefined) ? false : object.astLeaves !== undefined && object.astHash !== undefined
let isRunnable = (object) => isAST(object) || object.mType == "funseq"
let BasicFunSeq = function(f) {
if (!Array.isArray(f) || !isAST(f[0])) throw new BASICerror("Not an array of functions")
this.mHash = makeBase32Hash()
this.mType = "funseq"
this.mVal = f
}
let BasicListMonad = function(m) {
this.mHash = makeBase32Hash()
this.mType = "list"
this.mVal = [m]
}
let BasicMemoMonad = function(m) {
this.mHash = makeBase32Hash()
this.mType = "value"
this.mVal = m
this.seq = undefined
}
let isMonad = (o) => (o === undefined) ? false : (o.mType !== undefined)
let literalTypes = ["string", "num", "bool", "array", "generator", "usrdefun", "monad"]
let resolve = function(variable) {
if (variable === undefined) return undefined
if (variable.troType === undefined) {
if (isNumable(variable)) return tonum(variable)
if (Array.isArray(variable)) return variable
if (isGenerator(variable) || isAST(variable) || isMonad(variable)) return variable
if (typeof variable == "object")
throw Error(`BasicIntpError: trying to resolve unknown object '${variable}' with entries ${Object.entries(variable)}`)
return variable
}
else if (variable.troType === "internal_arrindexing_lazy")
return eval("variable.troValue.arrFull"+variable.troValue.arrKey)
else if (literalTypes.includes(variable.troType) || variable.troType.startsWith("internal_"))
return variable.troValue
else if (variable.troType == "lit") {
if (isNumable(variable.troValue)) {
return tonum(variable.troValue)
}
else if (bS.builtin[variable.troValue] !== undefined) {
return bS.wrapBuiltinToUsrdefun(variable.troValue)
}
else {
let basicVar = bS.vars[variable.troValue]
if (basicVar === undefined) throw lang.refError(undefined, variable.troValue)
if (basicVar.bvLiteral === "") return ""
return (basicVar !== undefined) ? basicVar.bvLiteral : undefined
}
}
else if (variable.troType == "null")
return undefined
else
throw Error("BasicIntpError: unknown variable/object with type "+variable.troType+", with value "+variable.troValue)
}
let findHighestIndex = function(exprTree) {
let highestIndex = [-1,-1]
let rec = function(exprTree) {
bF._recurseApplyAST(exprTree, it => {
if (it.astType == "defun_args") {
let recIndex = it.astValue[0]
let ordIndex = it.astValue[1]
if (recIndex > highestIndex[0]) {
highestIndex = [recIndex, 0]
}
if (recIndex == highestIndex[0] && ordIndex > highestIndex[1]) {
highestIndex[1] = ordIndex
}
}
else if (isAST(it.astValue)) {
rec(it.astValue)
}
})
};rec(exprTree)
return highestIndex
}
let indexDec = function(node, recIndex) {
if (node.astType == "defun_args" && node.astValue[0] === recIndex) {
let newNode = cloneObject(node)
newNode.astValue[1] -= 1
return newNode
}
else return node
}
let curryDefun = function(inputTree, inputValue) {
let exprTree = cloneObject(inputTree)
let value = cloneObject(inputValue)
let highestIndex = findHighestIndex(exprTree)[0]
if (DBGON) {
serial.println("[curryDefun] highest index to curry: "+highestIndex)
}
let substitution = new BasicAST()
if (isAST(value)) {
substitution = value
}
else {
substitution.astLnum = "??"
substitution.astType = JStoBASICtype(value)
substitution.astValue = value
}
bF._recurseApplyAST(exprTree, it => {
return (it.astType == "defun_args" && it.astValue[0] === highestIndex && it.astValue[1] === 0) ? substitution : indexDec(it, highestIndex)
})
return exprTree
}
let getMonadEvalFun = (monad) => function(lnum, stmtnum, args, sep) {
if (!isMonad(monad)) throw lang.badFunctionCallFormat(lnum, "not a monad")
if (DBGON) {
serial.println("[BASIC.MONADEVAL] monad:")
serial.println(monadToString(monad))
}
if (monad.mType == "funseq") {
let arg = args[0]
monad.mVal.forEach(f => {
arg = bS.getDefunThunk(f)(lnum, stmtnum, [arg])
})
return arg
}
else {
return monad.mVal
}
}
let listMonConcat = function(parentm, childm) {
parentm.mVal = parentm.mVal.concat(childm.mVal)
return parentm
}
let countArgs = function(defunTree) {
let cnt = -1
bF._recurseApplyAST(defunTree, it => {
if (it.astType == "defun_args" && it.astValue > cnt)
cnt = it.astValue
})
return cnt+1
}
let argCheckErr = function(lnum, o) {
if (o === undefined) throw lang.refError(lnum, "(variable is undefined)")
if (o.troType == "null") throw lang.refError(lnum, o)
if (o.troType == "lit" && bS.builtin[o.troValue] !== undefined) return
if (o.troType == "lit" && bS.vars[o.troValue] === undefined) throw lang.refError(lnum, o.troValue)
}
let oneArg = function(lnum, stmtnum, args, action) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0])
return action(rsvArg0)
}
let oneArgNul = function(lnum, stmtnum, args, action) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var rsvArg0 = resolve(args[0])
return action(rsvArg0)
}
let oneArgNum = function(lnum, stmtnum, args, action) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0], 1)
if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, args[0])
return action(rsvArg0)
}
let twoArg = function(lnum, stmtnum, args, action) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0])
argCheckErr(lnum, args[1])
var rsvArg1 = resolve(args[1])
return action(rsvArg0, rsvArg1)
}
let twoArgNul = function(lnum, stmtnum, args, action) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var rsvArg0 = resolve(args[0])
var rsvArg1 = resolve(args[1])
return action(rsvArg0, rsvArg1)
}
let twoArgNum = function(lnum, stmtnum, args, action) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0], 1)
if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, "LH:"+Object.entries(args[0]))
argCheckErr(lnum, args[1])
var rsvArg1 = resolve(args[1], 1)
if (!isNumable(rsvArg1)) throw lang.illegalType(lnum, "RH:"+Object.entries(args[1]))
return action(rsvArg0, rsvArg1)
}
let threeArg = function(lnum, stmtnum, args, action) {
if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0])
argCheckErr(lnum, args[1])
var rsvArg1 = resolve(args[1])
argCheckErr(lnum, args[2])
var rsvArg2 = resolve(args[2])
return action(rsvArg0, rsvArg1, rsvArg2)
}
let threeArgNum = function(lnum, stmtnum, args, action) {
if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG)
argCheckErr(lnum, args[0])
var rsvArg0 = resolve(args[0], 1)
if (!isNumable(rsvArg0)) throw lang.illegalType(lnum, "1H:"+Object.entries(args[0]))
argCheckErr(lnum, args[1])
var rsvArg1 = resolve(args[1], 1)
if (!isNumable(rsvArg1)) throw lang.illegalType(lnum, "2H:"+Object.entries(args[1]))
argCheckErr(lnum, args[2])
var rsvArg2 = resolve(args[2], 1)
if (!isNumable(rsvArg2)) throw lang.illegalType(lnum, "3H:"+Object.entries(args[2]))
return action(rsvArg0, rsvArg1, rsvArg2)
}
let varArg = function(lnum, stmtnum, args, action) {
var rsvArg = args.map((it) => {
argCheckErr(lnum, it)
var r = resolve(it)
return r
})
return action(rsvArg)
}
let varArgNum = function(lnum, stmtnum, args, action) {
var rsvArg = args.map((it) => {
argCheckErr(lnum, it)
var r = resolve(it)
if (isNaN(r)) throw lang.illegalType(lnum, r)
return r
})
return action(rsvArg)
}
let makeIdFun = () => {
return JSON.parse(`{"astLnum":"**","astLeaves":[],"astSeps":[],"astValue":[0,0],"astType":"defun_args","astHash":"IDFUN"}`)
}
let _basicConsts = {
"NIL": new BasicVar([], "array"),
"PI": new BasicVar(Math.PI, "num"),
"TAU": new BasicVar(Math.PI * 2.0, "num"),
"EULER": new BasicVar(Math.E, "num"),
"ID": new BasicVar(makeIdFun(), "usrdefun"),
"UNDEFINED": new BasicVar(undefined, "null"),
"TRUE": new BasicVar(true, "bool"),
"FALSE": new BasicVar(false, "bool")
}
Object.freeze(_basicConsts)
let initBvars = function() {
return cloneObject(_basicConsts)
}
let ForGen = function(s,e,t) {
this.start = s
this.end = e
this.step = t || 1
this.current = this.start
this.stepsgn = (this.step > 0) ? 1 : -1
}
let isGenerator = (o) => o.start !== undefined && o.end !== undefined && o.step !== undefined && o.stepsgn !== undefined
let genToArray = (gen) => {
let a = []
let cur = gen.start
while (cur*gen.stepsgn + gen.step*gen.stepsgn <= (gen.end + gen.step)*gen.stepsgn) {
a.push(cur)
cur += gen.step
}
return a
}
let genHasHext = (o) => o.current*o.stepsgn + o.step*o.stepsgn <= (o.end + o.step)*o.stepsgn
let genGetNext = (gen, mutated) => {
if (mutated !== undefined) gen.current = tonum(mutated)
gen.current += gen.step
return genHasHext(gen) ? gen.current : undefined
}
let genToString = (gen) => `Generator: ${gen.start} to ${gen.end}`+((gen.step !== 1) ? ` step ${gen.step}` : '')
let genReset = (gen) => { gen.current = gen.start }
let bS = {}
bS.gosubStack = []
bS.forLnums = {}
bS.forStack = []
bS.vars = initBvars()
bS.rnd = 0
bS.getDimSize = function(array, dim) {
var dims = []
while (true) {
dims.push(array.length)
if (Array.isArray(array[0]))
array = array[0]
else
break
}
return dims[dim]
}
bS.getArrayIndexFun = function(lnum, stmtnum, arrayName, array) {
if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`)
return function(lnum, stmtnum, args, seps) {
if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`)
return varArgNum(lnum, stmtnum, args, (dims) => {
if (TRACEON) serial.println("ar dims: "+dims)
let dimcnt = 1
let oldIndexingStr = ""
let indexingstr = ""
dims.forEach(d => {
oldIndexingStr = indexingstr
indexingstr += `[${d-INDEX_BASE}]`
var testingArr = eval(`array${indexingstr}`)
if (testingArr === undefined)
throw lang.subscrOutOfRng(lnum, `${arrayName}${oldIndexingStr} (${lang.ord(dimcnt)} dim)`, d-INDEX_BASE, bS.getDimSize(array, dimcnt-1))
dimcnt += 1
})
if (TRACEON)
serial.println("ar indexedValue = "+`array${indexingstr}`)
return {arrFull: array, arrName: arrayName, arrKey: indexingstr}
})
}
}
bS.getDefunThunk = function(exprTree, norename) {
if (!isRunnable(exprTree)) throw new BASICerror("not a syntax tree")
if (isMonad(exprTree)) return getMonadEvalFun(exprTree)
let tree = cloneObject(exprTree)
return function(lnum, stmtnum, args, seps) {
if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`)
if (!norename) {
let argsMap = args.map(it => {
let rit = resolve(it)
return [JStoBASICtype(rit), rit]
})
lambdaBoundVars.unshift(argsMap)
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] unthunking: ")
serial.println(astToString(tree))
serial.println("[BASIC.getDefunThunk.invoke] thunk args:")
serial.println(argsMap)
serial.println("[BASIC.getDefunThunk.invoke] lambda bound vars:")
serial.println(theLambdaBoundVars())
}
bF._recurseApplyAST(tree, (it) => {
if ("defun_args" == it.astType) {
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] thunk renaming arg-tree branch:")
serial.println(astToString(it))
}
let recIndex = it.astValue[0]
let argIndex = it.astValue[1]
let theArg = lambdaBoundVars[recIndex][argIndex]
if (theArg !== undefined) {
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] thunk renaming-theArg: "+theArg)
serial.println(`${Object.entries(theArg)}`)
}
if (theArg[0] === "null") {
throw new BASICerror(`Bound variable is ${theArg}; lambdaBoundVars: ${theLambdaBoundVars()}`)
}
it.astValue = theArg[1]
it.astType = theArg[0]
}
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] thunk successfully renamed arg-tree branch:")
serial.println(astToString(it))
}
}
})
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] resulting thunk tree:")
serial.println(astToString(tree))
}
}
else {
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] no rename, resulting thunk tree:")
serial.println(astToString(tree))
}
}
if (DBGON) {
serial.println("[BASIC.getDefunThunk.invoke] evaluating tree:")
}
let ret = resolve(bF._executeSyntaxTree(lnum, stmtnum, tree, 0))
if (!norename) {
lambdaBoundVars.shift()
}
return ret
}
}
bS.wrapBuiltinToUsrdefun = function(funcname) {
let argCount = bS.builtin[funcname].argc
if (argCount === undefined) throw new BASICerror(`${funcname} cannot be wrapped into usrdefun`)
let leaves = []
for (let k = 0; k < argCount; k++) {
let l = new BasicAST()
l.astLnum = "**"
l.astValue = [0,k]
l.astType = "defun_args"
leaves.push(l)
}
let tree = new BasicAST()
tree.astLnum = "**"
tree.astValue = funcname
tree.astType = "function"
tree.astLeaves = leaves
return tree
}
bS.addAsBasicVar = function(lnum, troValue, rh) {
if (troValue.arrFull !== undefined) {
let arr = eval("troValue.arrFull"+troValue.arrKey)
if (Array.isArray(arr)) throw lang.subscrOutOfRng(lnum, arr)
eval("troValue.arrFull"+troValue.arrKey+"=rh")
return {asgnVarName: troValue.arrName, asgnValue: rh}
}
else {
let varname = troValue.toUpperCase()
if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname)
if (bS.builtin[varname]) throw lang.asgnOnConst(lnum, varname)
let type = JStoBASICtype(rh)
bS.vars[varname] = new BasicVar(rh, type)
return {asgnVarName: varname, asgnValue: rh}
}
}
bS.builtin = {
"=" : {argc:2, f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var troValue = args[0].troValue
var rh = resolve(args[1])
if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue)
if (isNumable(rh)) rh = tonum(rh)
return bS.addAsBasicVar(lnum, troValue, rh)
}},
"IN" : {argc:2, f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var troValue = args[0].troValue
var rh = resolve(args[1])
if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue)
if (troValue.arrFull !== undefined) {
throw lang.syntaxfehler(lnum)
}
else {
var varname = troValue.toUpperCase()
var type = JStoBASICtype(rh)
if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname)
return {asgnVarName: varname, asgnValue: rh}
}
}},
"==" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNul(lnum, stmtnum, args, (lh,rh) => lh == rh)
}},
"<>" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh)
}},
"><" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh)
}},
"<=" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh)
}},
"=<" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh)
}},
">=" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh)
}},
"=>" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh)
}},
"<" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh < rh)
}},
">" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh > rh)
}},
"<<" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh << rh)
}},
">>" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >>> rh)
}},
"UNARYMINUS" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => -lh)
}},
"UNARYPLUS" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => +lh)
}},
"UNARYLOGICNOT" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => !(lh))
}},
"UNARYBNOT" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => ~(lh))
}},
"BAND" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh & rh)
}},
"BOR" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh | rh)
}},
"BXOR" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh ^ rh)
}},
"!" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (Array.isArray(rh)) {
return [lh].concat(rh)
}
else if (rh.mType === "list") {
rh.mVal = [lh].concat(rh.mVal)
return rh
}
else throw lang.illegalType(lnum, rh)
})
}},
"~" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (Array.isArray(lh)) {
return lh.concat([rh])
}
else if (lh.mType === "list") {
lh.mVal = [lh.mVal].concat([rh])
return lh
}
else throw lang.illegalType(lnum, lh)
})
}},
"#" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (Array.isArray(lh) && Array.isArray(rh)) {
return lh.concat(rh)
}
else if (lh.mType == "list" && rh.mType == "list") {
let newMval = lh.mVal.concat(rh.mVal)
return new BasicListMonad(newMval)
}
else
throw lang.illegalType(lnum)
})
}},
"+" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => (!isNaN(lh) && !isNaN(rh)) ? (tonum(lh) + tonum(rh)) : (lh + rh))
}},
"-" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh - rh)
}},
"*" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh * rh)
}},
"/" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero
return lh / rh
})
}},
"\\" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero
return (lh / rh)|0
})
}},
"MOD" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero
return lh % rh
})
}},
"^" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
let r = Math.pow(lh, rh)
if (isNaN(r)) throw lang.badFunctionCallFormat(lnum)
if (!isFinite(r)) throw lang.divByZero
return r
})
}},
"TO" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (from, to) => new ForGen(from, to, 1))
}},
"STEP" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (gen, step) => {
if (!isGenerator(gen)) throw lang.illegalType(lnum, gen)
return new ForGen(gen.start, gen.end, step)
})
}},
"DIM" : {f:function(lnum, stmtnum, args) {
return varArgNum(lnum, stmtnum, args, (revdims) => {
let dims = revdims.reverse()
let arraydec = "Array(dims[0]).fill(0)"
for (let k = 1; k < dims.length; k++) {
arraydec = `Array(dims[${k}]).fill().map(_=>${arraydec})`
}
return eval(arraydec)
})
}},
"ARRAY CONSTRUCTOR" : {f:function(lnum, stmtnum, args) {
return args.map(v => resolve(v))
}},
"PRINT" : {argc:1, f:function(lnum, stmtnum, args, seps) {
if (args.length == 0)
println()
else {
for (var llll = 0; llll < args.length; llll++) {
if (llll >= 1) {
if (seps[llll - 1] == ",") print("\t")
}
var rsvArg = resolve(args[llll])
let printstr = ""
if (Array.isArray(rsvArg))
printstr = arrayToString(rsvArg)
else if (rsvArg === undefined || rsvArg === "")
printstr = ""
else if (rsvArg.toString !== undefined)
printstr = rsvArg.toString()
else
printstr = rsvArg
print(printstr)
if (TRACEON) serial.println("[BASIC.PRINT] "+printstr)
}
}
if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println()
}},
"EMIT" : {argc:1, f:function(lnum, stmtnum, args, seps) {
if (args.length == 0)
println()
else {
for (var llll = 0; llll < args.length; llll++) {
if (llll >= 1) {
if (seps[llll - 1] == ",") print("\t")
}
var rsvArg = resolve(args[llll])
if (rsvArg === undefined && args[llll] !== undefined && args[llll].troType != "null") throw lang.refError(lnum, args[llll].troValue)
let printstr = ""
if (rsvArg === undefined)
print("")
else if (isNumable(rsvArg)) {
let c = con.getyx()
con.addch(tonum(rsvArg))
con.move(c[0],c[1]+1)
}
else if (rsvArg.toString !== undefined)
print(rsvArg.toString())
else
printstr = (rsvArg)
if (TRACEON) serial.println("[BASIC.EMIT] "+printstr)
}
}
if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println()
}},
"POKE" : {argc:2, f:function(lnum, stmtnum, args) {
twoArgNum(lnum, stmtnum, args, (lh,rh) => sys.poke(lh, rh))
}},
"PEEK" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => sys.peek(lh))
}},
"GOTO" : {argc:1, f:function(lnum, stmtnum, args) {
let line = gotoLabels[args[0].troValue]
if (line === undefined) line = resolve(args[0])
if (line < 0) throw lang.syntaxfehler(lnum, line)
return new JumpObj(line, 0, lnum, line)
}},
"GOSUB" : {argc:1, f:function(lnum, stmtnum, args) {
let line = gotoLabels[args[0].troValue]
if (line === undefined) line = resolve(args[0])
if (line < 0) throw lang.syntaxfehler(lnum, line)
bS.gosubStack.push([lnum, stmtnum + 1])
return new JumpObj(line, 0, lnum, line)
}},
"RETURN" : {f:function(lnum, stmtnum, args) {
var r = bS.gosubStack.pop()
if (r === undefined) throw lang.nowhereToReturn(lnum)
return new JumpObj(r[0], r[1], lnum, r)
}},
"CLEAR" : {argc:0, f:function(lnum, stmtnum, args) {
bS.vars = initBvars()
}},
"PLOT" : {argc:3, f:function(lnum, stmtnum, args) {
threeArgNum(lnum, stmtnum, args, (xpos, ypos, color) => graphics.plotPixel(xpos, ypos, color))
}},
"AND" : {argc:2, f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var rsvArg = args.map((it) => resolve(it))
rsvArg.forEach((v) => {
if (v === undefined) throw lang.refError(lnum, v)
if (typeof v !== "boolean") throw lang.illegalType(lnum, v)
})
var argum = rsvArg.map((it) => {
if (it === undefined) throw lang.refError(lnum, it)
return it
})
return argum[0] && argum[1]
}},
"OR" : {argc:2, f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG)
var rsvArg = args.map((it) => resolve(it))
rsvArg.forEach((v) => {
if (v === undefined) throw lang.refError(lnum, v.value)
if (typeof v !== "boolean") throw lang.illegalType(lnum, v)
})
var argum = rsvArg.map((it) => {
if (it === undefined) throw lang.refError(lnum, it)
return it
})
return argum[0] || argum[1]
}},
"RND" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => {
if (!(args.length > 0 && args[0].troValue === 0))
bS.rnd = Math.random()
return bS.rnd
})
}},
"ROUND" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.round(lh))
}},
"FLOOR" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh))
}},
"INT" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh))
}},
"CEIL" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.ceil(lh))
}},
"FIX" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => (lh|0))
}},
"CHR" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => String.fromCharCode(lh))
}},
"TEST" : {argc:1, f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
return !!resolve(args[0])
}},
"FOREACH" : {f:function(lnum, stmtnum, args) {
var asgnObj = resolve(args[0])
if (asgnObj === undefined) throw lang.syntaxfehler(lnum)
if (!Array.isArray(asgnObj.asgnValue)) throw lang.illegalType(lnum, asgnObj)
var varname = asgnObj.asgnVarName
bS.vars[varname] = new BasicVar(asgnObj.asgnValue[0], JStoBASICtype(asgnObj.asgnValue.shift()))
bS.vars["for var "+varname] = new BasicVar(asgnObj.asgnValue, "array")
bS.forLnums[varname] = [lnum, stmtnum]
bS.forStack.push(varname)
}},
"FOR" : {f:function(lnum, stmtnum, args) {
var asgnObj = resolve(args[0])
if (asgnObj === undefined) throw lang.syntaxfehler(lnum)
if (!isGenerator(asgnObj.asgnValue)) throw lang.illegalType(lnum, typeof asgnObj)
var varname = asgnObj.asgnVarName
var generator = asgnObj.asgnValue
bS.vars[varname] = new BasicVar(generator.start, "num")
bS.vars["for var "+varname] = new BasicVar(generator, "generator")
bS.forLnums[varname] = [lnum, stmtnum]
bS.forStack.push(varname)
}},
"NEXT" : {f:function(lnum, stmtnum, args) {
if (args.length == 0 || (args.length == 1 && args.troType == "null")) {
var forVarname = bS.forStack.pop()
if (forVarname === undefined) {
throw lang.nextWithoutFor(lnum)
}
if (TRACEON) serial.println("[BASIC.FOR] looping "+forVarname)
var forVar = bS.vars["for var "+forVarname].bvLiteral
if (isGenerator(forVar))
bS.vars[forVarname].bvLiteral = genGetNext(forVar, bS.vars[forVarname].bvLiteral)
else
bS.vars[forVarname].bvLiteral = forVar.shift()
if ((bS.vars[forVarname].bvLiteral !== undefined)) {
bS.forStack.push(forVarname)
let forLnum = bS.forLnums[forVarname]
return new JumpObj(forLnum[0], forLnum[1]+1, lnum, [forLnum[0], forLnum[1]+1])
}
else {
if (isGenerator(forVar))
bS.vars[forVarname].bvLiteral = forVar.current
else
bS.vars[forVarname] === undefined
return new JumpObj(lnum, stmtnum + 1, lnum, [lnum, stmtnum + 1])
}
}
throw lang.syntaxfehler(lnum, "extra arguments for NEXT")
}},
/*
10 input;"what is your name";a$
£ Line 10 (function)
| leaves: 3
| value: input (type: string)
£ Line 0 (null)
| leaves: 0
| value: undefined (type: undefined)
`-----------------
|
| ¶ Line 10 (string)
| | leaves: 0
| | value: what is your name (type: string)
| `-----------------
|
| i Line 10 (literal)
| | leaves: 0
| | value: A$ (type: string)
| `-----------------
`-----------------
10 input "what is your name";a$
£ Line 10 (function)
| leaves: 2
| value: input (type: string)
| ¶ Line 10 (string)
| | leaves: 0
| | value: what is your name (type: string)
| `-----------------
|
| i Line 10 (literal)
| | leaves: 0
| | value: A$ (type: string)
| `-----------------
`-----------------
*/
"INPUT" : {argc:1, f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
let troValue = args[0].troValue
print("? "); var rh = sys.read().trim()
if (!isNaN(rh)) rh = tonum(rh)
return bS.addAsBasicVar(lnum, troValue, rh)
}},
"CIN" : {argc:0, f:function(lnum, stmtnum, args) {
return sys.read().trim()
}},
"END" : {argc:0, f:function(lnum, stmtnum, args) {
serial.println("Program terminated in "+lnum)
return new JumpObj(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1, lnum, undefined)
}},
"SPC" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => " ".repeat(lh))
}},
"LEFT" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (str, len) => str.substring(0, len))
}},
"MID" : {argc:3, f:function(lnum, stmtnum, args) {
return threeArg(lnum, stmtnum, args, (str, start, len) => str.substring(start-INDEX_BASE, start-INDEX_BASE+len))
}},
"RIGHT" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (str, len) => str.substring(str.length - len, str.length))
}},
"SGN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => (it > 0) ? 1 : (it < 0) ? -1 : 0)
}},
"ABS" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.abs(it))
}},
"SIN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sin(it))
}},
"COS" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cos(it))
}},
"TAN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.tan(it))
}},
"EXP" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.exp(it))
}},
"ASN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.asin(it))
}},
"ACO" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.acos(it))
}},
"ATN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.atan(it))
}},
"SQR" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sqrt(it))
}},
"CBR" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cbrt(it))
}},
"SINH" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sinh(it))
}},
"COSH" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cosh(it))
}},
"TANH" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.tanh(it))
}},
"LOG" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.log(it))
}},
"RESTORE" : {argc:0, f:function(lnum, stmtnum, args) {
DATA_CURSOR = 0
}},
"READ" : {argc:1, f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG)
let troValue = args[0].troValue
let rh = DATA_CONSTS[DATA_CURSOR++]
if (rh === undefined) throw lang.outOfData(lnum)
return bS.addAsBasicVar(lnum, troValue, rh)
}},
"DGET" : {argc:0, f:function(lnum, stmtnum, args) {
let r = DATA_CONSTS[DATA_CURSOR++]
if (r === undefined) throw lang.outOfData(lnum)
return r
}},
"OPTIONBASE" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line)
INDEX_BASE = lh|0
})
}},
"DATA" : {f:function(lnum, stmtnum, args) {
if (prescan) {
args.forEach(it => DATA_CONSTS.push(resolve(it)))
}
}},
"MAP" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fn, functor) => {
if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn))
if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : ""))
if (isGenerator(functor)) functor = genToArray(functor)
return functor.map(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it]))
})
}},
"FOLD" : {argc:3, f:function(lnum, stmtnum, args) {
return threeArg(lnum, stmtnum, args, (fn, init, functor) => {
if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn))
if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, `not a mappable type '${Object.entries(args[2])}': `+functor+((typeof functor == "object") ? Object.entries(functor) : ""))
if (isGenerator(functor)) functor = genToArray(functor)
let akku = init
functor.forEach(it => {
akku = bS.getDefunThunk(fn)(lnum, stmtnum, [akku, it])
})
return akku
})
}},
"FILTER" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fn, functor) => {
if (!isRunnable(fn)) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fn))
if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, `not a mappable type '${Object.entries(args[1])}': `+functor+((typeof functor == "object") ? Object.entries(functor) : (typeof functor)))
if (isGenerator(functor)) functor = genToArray(functor)
return functor.filter(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it]))
})
}},
"DO" : {f:function(lnum, stmtnum, args) {
return args[args.length - 1]
}},
"LABEL" : {f:function(lnum, stmtnum, args) {
if (prescan) {
let labelname = args[0].troValue
if (labelname === undefined) throw lang.syntaxfehler(lnum, "empty LABEL")
gotoLabels[labelname] = lnum
}
}},
"ON" : {f:function(lnum, stmtnum, args) {
if (args[2] === undefined) throw lang.syntaxfehler(lnum)
let jmpFun = args.shift()
let testvalue = resolve(args.shift())-INDEX_BASE
let jmpTarget = args[testvalue]
if (jmpFun !== "GOTO" && jmpFun !== "GOSUB")
throw lang.badFunctionCallFormat(lnum, `Not a jump statement: ${jmpFun}`)
if (jmpTarget === undefined)
return undefined
return bS.builtin[jmpFun].f(lnum, stmtnum, [jmpTarget])
}},
"MIN" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => (lh > rh) ? rh : lh)
}},
"MAX" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => (lh < rh) ? rh : lh)
}},
"GETKEYSDOWN" : {argc:0, f:function(lnum, stmtnum, args) {
let keys = []
sys.poke(-40, 255)
for (let k = -41; k >= -48; k--) {
keys.push(sys.peek(k))
}
return keys
}},
"~<" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fn, value) => {
if (!isAST(fn)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function: got "+JStoBASICtype(fn))
if (DBGON) {
serial.println("[BASIC.BUILTIN.CURRY] currying this function tree...")
serial.println(astToString(fn))
serial.println("[BASIC.BUILTIN.CURRY] with this value: "+value)
serial.println(Object.entries(value))
}
let curriedTree = curryDefun(fn, value)
if (DBGON) {
serial.println("[BASIC.BUILTIN.CURRY] Here's your curried tree:")
serial.println(astToString(curriedTree))
}
return curriedTree
})
}},
"TYPEOF" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNul(lnum, stmtnum, args, bv => {
if (bv === undefined) return "undefined"
if (bv.bvType === undefined || !(bv instanceof BasicVar)) {
let typestr = JStoBASICtype(bv)
if (typestr == "monad")
return bv.mType+"-"+typestr
else return typestr
}
return bv.bvType
})
}},
"LEN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined) throw lang.illegalType()
return lh.length
})
}},
"HEAD" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType()
return lh[0]
})
}},
"TAIL" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType()
return lh.slice(1, lh.length)
})
}},
"INIT" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType()
return lh.slice(0, lh.length - 1)
})
}},
"LAST" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType()
return lh[lh.length - 1]
})
}},
"CLS" : {argc:0, f:function(lnum, stmtnum, args) {
con.clear()
}},
"CLPX" : {argc:0, f:function(lnum, stmtnum, args) {
graphics.clearPixels(255)
}},
"$" : {argc:2, f:function(lnum, stmtnum, args) {
let fn = resolve(args[0])
let value = resolve(args[1])
if (DBGON) {
serial.println("[BASIC.BUILTIN.APPLY] applying this function tree... "+fn)
serial.println(astToString(fn))
serial.println("[BASIC.BUILTIN.APPLY] with this value: "+value)
if (value !== undefined)
serial.println(Object.entries(value))
}
if (fn.mType == "funseq") {
return getMonadEvalFun(fn)(lnum, stmtnum, [value])
}
else {
let valueTree = new BasicAST()
valueTree.astLnum = lnum
valueTree.astType = JStoBASICtype(value)
valueTree.astValue = value
let newTree = new BasicAST()
newTree.astLnum = lnum
newTree.astValue = fn
newTree.astType = "usrdefun"
newTree.astLeaves = [valueTree]
if (DBGON) {
serial.println("[BASIC.BUILTIN.APPLY] Here's your applied tree:")
serial.println(astToString(newTree))
}
return bF._executeSyntaxTree(lnum, stmtnum, newTree, 0)
}
}},
"&" : {argc:2, f:function(lnum, stmtnum, args) {
return bS.builtin["$"].f(lnum, stmtnum, [args[1], args[0]].concat(args.slice(2)))
}},
"REDUCE" : {noprod:1, argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, bv => {
if (isAST(bv)) {
if (DBGON) {
serial.println("[BASIC.BUILTIN.REDUCE] reducing:")
serial.println(astToString(bv))
}
let reduced = bF._uncapAST(bv, it => {
return it
})
if (DBGON) {
serial.println("[BASIC.BUILTIN.REDUCE] reduced: "+reduced)
serial.println(astToString(reduced))
}
let newTree = new BasicAST()
newTree.astLnum = lnum
newTree.astType = JStoBASICtype(reduced)
newTree.astValue = reduced
return newTree
}
else {
return bv
}
})
}},
">>=" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (ma, a_to_mb) => {
if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma))
if (!isRunnable(a_to_mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a usrdefun: got "+JStoBASICtype(a_to_mb))
if (DBGON) {
serial.println("[BASIC.BIND] binder:")
serial.println(monadToString(ma))
serial.println("[BASIC.BIND] bindee:")
serial.println(astToString(a_to_mb))
}
let a = ma.mVal
let mb = bS.getDefunThunk(a_to_mb)(lnum, stmtnum, [a])
if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand function did not return a monad")
if (DBGON) {
serial.println("[BASIC.BIND] bound monad:")
serial.println(monadToString(mb))
}
return mb
})
}},
">>~" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (ma, mb) => {
if (!isMonad(ma)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a monad: got "+JStoBASICtype(ma))
if (!isMonad(mb)) throw lang.badFunctionCallFormat(lnum, "right-hand is not a monad: got "+JStoBASICtype(mb))
if (DBGON) {
serial.println("[BASIC.BIND] binder:")
serial.println(monadToString(ma))
serial.println("[BASIC.BIND] bindee:")
serial.println(monadToString(mb))
}
let a = ma.mVal
let b = mb.mVal
return mb
})
}},
"." : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fa, fb) => {
if (!isRunnable(fa)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fa))
if (!isRunnable(fb)) throw lang.badFunctionCallFormat(lnum, "left-hand is not a function/funseq: got"+JStoBASICtype(fb))
let ma = (isAST(fa)) ? [fa] : fa.mVal
let mb = (isAST(fb)) ? [fb] : fb.mVal
let mc = mb.concat(ma)
return new BasicFunSeq(mc)
})
}},
"MLIST" : {noprod:1, argc:1, f:function(lnum, stmtnum, args) {
return oneArgNul(lnum, stmtnum, args, fn => {
return new BasicListMonad([fn])
})
}},
"MRET" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArgNul(lnum, stmtnum, args, fn => {
return new BasicMemoMonad(fn)
})
}},
"MJOIN" : {argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, m => {
if (!isMonad(m)) throw lang.illegalType(lnum, m)
return m.mVal
})
}},
"GOTOYX" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (y, x) => {
con.move(y + (1-INDEX_BASE),x + (1-INDEX_BASE))
})
}},
"TEXTFORE" : {argc:2, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, col => {
print(String.fromCharCode(27,91)+"38;5;"+(col|0)+"m")
})
}},
"TEXTBACK" : {argc:2, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, col => {
print(String.fromCharCode(27,91)+"48;5;"+(col|0)+"m")
})
}},
"<*>" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fns, functor) => {
if (!Array.isArray(fns) || !isRunnable(fns[0])) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fns))
if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : ""))
if (isGenerator(functor)) functor = genToArray(functor)
let ret = []
fns.forEach(fn => ret = ret.concat(functor.map(it => bS.getDefunThunk(fn)(lnum, stmtnum, [it]))))
return ret
})
}},
"<$>" : {argc:2, f:function(lnum, stmtnum, args) {
return bS.builtin.MAP.f(lnum, stmtnum, args)
}},
"<~>" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fns, functor) => {
if (!isRunnable(fns) && !(Array.isArray(fns) && isRunnable(fns[0]))) throw lang.badFunctionCallFormat(lnum, "first argument is not a function: got "+JStoBASICtype(fns))
if (!isGenerator(functor) && !Array.isArray(functor)) throw lang.syntaxfehler(lnum, "not a mappable type: "+functor+((typeof functor == "object") ? Object.entries(functor) : ""))
if (isGenerator(functor)) functor = genToArray(functor)
if (!Array.isArray(fns)) fns = [fns]
let ret = []
fns.forEach(fn => ret = ret.concat(functor.map(it => bS.builtin["~<"].f(lnum, stmtnum, [fn, it]))))
return ret
})
}},
"CPUT" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (devnum, msg) => {
if (!isNumable(devnum)) throw lang.illegalType(lnum, "LH:"+Object.entries(devnum))
com.sendMessage(devnum, msg)
return com.getStatusCode(devnum)
})
}},
"CGET" : {argc:2, f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (devnum, ptr) => {
let msg = com.pullMessage(devnum)
let len = msg.length|0
for (let i = 0; i < len; i++) {
sys.poke(ptr + i, msg.charCodeAt(i))
}
return len
})
}},
"CSTA" : {argc:2, f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (devnum) => {
return com.getStatusCode(devnum)
})
}},
"OPTIONDEBUG" : {f:function(lnum, stmtnum, args) {
oneArgNum(lnum, stmtnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line)
DBGON = (1 == lh|0)
})
}},
"OPTIONTRACE" : {f:function(lnum, stmtnum, args) {
oneArgNum(lnum, stmtnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line)
TRACEON = (1 == lh|0)
})
}},
"PRINTMONAD" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, (it) => {
println(monadToString(it))
})
}},
"RESOLVE" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, (it) => {
if (isAST(it)) {
println(lnum+" RESOLVE PRINTTREE")
println(astToString(it))
if (typeof it.astValue == "object") {
if (isAST(it.astValue)) {
println(lnum+" RESOLVE PRINTTREE ASTVALUE PRINTTREE")
println(astToString(it.astValue))
}
else {
println(lnum+" RESOLVE PRINTTREE ASTVALUE")
println(it.astValue)
}
}
}
else
println(it)
})
}},
"RESOLVEVAR" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, (it) => {
let v = bS.vars[args[0].troValue]
if (v === undefined) println("Undefined variable: "+args[0].troValue)
else println(`type: ${v.bvType}, value: ${v.bvLiteral}`)
})
}},
"UNRESOLVE" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
println(args[0])
}},
"UNRESOLVE0" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
println(Object.entries(args[0]))
}},
"TOJSON" : {debugonly:1, argc:1, f:function(lnum, stmtnum, args) {
println(JSON.stringify(resolve(args[0])))
}},
}
Object.freeze(bS.builtin)
let bF = {}
bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1,"$":1,".":1,"@":1,"\\":1,"%":1,"|":1,"`":1}
bF._uos = {"+":1,"-":1,"NOT":1,"BNOT":1,"^":1,"@":1,"`":1}
bF._isNum = function(code) {
return (code >= 0x30 && code <= 0x39) || code == 0x5F
}
bF._isNum2 = function(code) {
return (code >= 0x30 && code <= 0x39) || code == 0x5F || (code >= 0x41 && code <= 0x46) || (code >= 0x61 && code <= 0x66)
}
bF._isNumSep = function(code) {
return code == 0x2E || code == 0x42 || code == 0x58 || code == 0x62 || code == 0x78
}
bF._is1o = function(code) {
return bF._1os[String.fromCharCode(code)]
}
bF._isUnary = function(code) {
return bF._uos[String.fromCharCode(code)]
}
bF._isParenOpen = function(code) {
return (code == 0x28 || code == 0x5B || code == 0x7B) || (code == '(' || code == '[' || code == '{')
}
bF._isParenClose = function(code) {
return (code == 0x29 || code == 0x5D || code == 0x7D) || (code == ')' || code == ']' || code == '}')
}
bF._isMatchingParen = function(open, close) {
return (open == '(' && close == ')' || open == '[' && close == ']' || open == '{' && close == '}')
}
bF._isParen = function(code) {
return bF._isParenOpen(code) || bF._isParenClose(code)
}
bF._isSep = function(code) {
return code == 0x2C || code == 0x3B
}
bF._opPrc = {
"`":10,
"^":20,
"*":30,"/":30,"\\":30,
"MOD":40,
"+":50,"-":50,
"NOT":60,"BNOT":60,
"<<":70,">>":70,
"<":80,">":80,"<=":80,"=<":80,">=":80,"=>":80,
"==":90,"<>":90,"><":90,
"MIN":100,"MAX":100,
"BAND":200,
"BXOR":201,
"BOR":202,
"AND":300,
"OR":301,
"TO":400,"STEP":401,
"!":500,
"~":501,
"#":502,
".": 600,
"$": 600,
"&": 600,
"~<": 601,
"<$>": 602,
"<*>": 602,
"<~>": 602,
"@":700,
"~>": 1000,
">>~": 1000,
">>=": 1000,
"=":9999,"IN":9999
}
bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"~>":1,"$":1,".":1,">>=":1,">>~":1,">!>":1,"@":1,"`":1,"<$>":1}
bF._tokenise = function(lnum, cmd) {
var _debugprintStateTransition = false
var k
var tokens = []
var states = []
var sb = ""
var mode = "lit"
if (_debugprintStateTransition) println("@@ TOKENISE @@")
if (_debugprintStateTransition) println("Ln "+lnum+" cmd "+cmd)
for (k = 0; k < cmd.length; k++) {
var char = cmd[k]
var charCode = cmd.charCodeAt(k)
if (_debugprintStateTransition) print("Char: "+char+"("+charCode+"), state: "+mode)
if ("lit" == mode) {
if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "qot"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "paren"
}
else if (" " == char) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "limbo"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "sep"
}
else if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "op"
}
else {
sb += char
}
}
else if ("num" == mode) {
if (bF._isNum(charCode)) {
sb += char
}
else if (bF._isNumSep(charCode)) {
sb += char
mode = "nsep"
}
else if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "qot"
}
else if (" " == char) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "limbo"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "paren"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "sep"
}
else if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "op"
}
else {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "lit"
}
}
else if ("nsep" == mode) {
if (bF._isNum2(charCode)) {
sb += char
mode = "n2"
}
else {
throw lang.syntaxfehler(lnum, lang.badNumberFormat)
}
}
else if ("n2" == mode) {
if (bF._isNum2(charCode)) {
sb += char
}
else if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push("num")
mode = "qot"
}
else if (" " == char) {
tokens.push(sb); sb = ""; states.push("num")
mode = "limbo"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push("num")
mode = "paren"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push("num")
mode = "sep"
}
else if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push("num")
mode = "op"
}
else {
tokens.push(sb); sb = "" + char; states.push("num")
mode = "lit"
}
}
else if ("op" == mode) {
if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "op"
}
else if (bF._isUnary(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
}
else if (bF._isNum(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "num"
}
else if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "qot"
}
else if (" " == char) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "limbo"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "paren"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "sep"
}
else {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "lit"
}
}
else if ("qot" == mode) {
if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "quote_end"
}
else {
sb += char
}
}
else if ("quote_end" == mode) {
if (" " == char) {
sb = ""
mode = "limbo"
}
else if (0x22 == charCode) {
sb = "" + char
mode = "qot"
}
else if (bF._isParen(charCode)) {
sb = "" + char
mode = "paren"
}
else if (bF._isSep(charCode)) {
sb = "" + char
mode = "sep"
}
else if (bF._isNum(charCode)) {
sb = "" + char
mode = "num"
}
else if (bF._is1o(charCode)) {
sb = "" + char
mode = "op"
}
else {
sb = "" + char
mode = "lit"
}
}
else if ("limbo" == mode) {
if (char == " ") {
}
else if (0x22 == charCode) {
mode = "qot"
}
else if (bF._isParen(charCode)) {
sb = "" + char
mode = "paren"
}
else if (bF._isSep(charCode)) {
sb = "" + char
mode = "sep"
}
else if (bF._isNum(charCode)) {
sb = "" + char
mode = "num"
}
else if (bF._is1o(charCode)) {
sb = "" + char
mode = "op"
}
else {
sb = "" + char
mode = "lit"
}
}
else if ("paren" == mode) {
if (char == " ") {
tokens.push(sb); sb = ""; states.push(mode)
mode = "limbo"
}
else if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "qot"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "paren"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "sep"
}
else if (bF._isNum(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "num"
}
else if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "op"
}
else {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "lit"
}
}
else if ("sep" == mode) {
if (char == " ") {
tokens.push(sb); sb = ""; states.push(mode)
mode = "limbo"
}
else if (0x22 == charCode) {
tokens.push(sb); sb = ""; states.push(mode)
mode = "qot"
}
else if (bF._isParen(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "paren"
}
else if (bF._isSep(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "sep"
}
else if (bF._isNum(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "num"
}
else if (bF._is1o(charCode)) {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "op"
}
else {
tokens.push(sb); sb = "" + char; states.push(mode)
mode = "lit"
}
}
else {
throw Error("Unknown parser state: " + mode)
}
if (_debugprintStateTransition) println("->"+mode)
}
if (sb.length > 0) {
tokens.push(sb); states.push(mode)
}
if (tokens[0].length == 0) {
tokens = tokens.slice(1, tokens.length)
states = states.slice(1, states.length)
}
for (k = 0; k < states.length; k++) {
if (states[k] == "o2" || states[k] == "o3") states[k] = "op"
else if (states[k] == "n2" || states[k] == "nsep") states[k] = "num"
}
if (tokens.length != states.length) {
throw new BASICerror("size of tokens and states does not match (line: "+lnum+")\n"+
tokens+"\n"+states)
}
return { "tokens": tokens, "states": states }
}
bF._parserElaboration = function(lnum, ltokens, lstates) {
let _debugprintElaboration = (!PROD) && true
if (_debugprintElaboration) serial.println("@@ ELABORATION @@")
let tokens = cloneObject(ltokens)
let states = cloneObject(lstates)
let k = 0
while (k < states.length) {
if (states[k] == "num" && !reNumber.test(tokens[k]))
states[k] = "lit"
else if (states[k] == "lit" && bF._opPrc[tokens[k].toUpperCase()] !== undefined)
states[k] = "op"
else if ((tokens[k].toUpperCase() == "TRUE" || tokens[k].toUpperCase() == "FALSE") && states[k] == "paren")
states[k] = "bool"
if (states[k] == "num") {
if (tokens[k].toUpperCase().startsWith("0B")) {
tokens[k] = parseInt(tokens[k].substring(2, tokens[k].length), 2) + ""
}
}
k += 1
}
k = 0; let l = states.length
while (k < l) {
let lookahead012 = tokens[k]+tokens[k+1]+tokens[k+2]
let lookahead01 = tokens[k]+tokens[k+1]
if (k < states.length - 3 && states[k] == "op" && states[k+1] == "op" && states[k+2] == "op" && bF._opPrc[lookahead012]) {
if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Trigraph (${lookahead012}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`)
tokens[k] = lookahead012
let oldtkn = cloneObject(tokens)
let oldsts = cloneObject(states)
tokens = oldtkn.slice(0, k+1).concat(oldtkn.slice(k+3, oldtkn.length))
states = oldsts.slice(0, k+1).concat(oldsts.slice(k+3, oldsts.length))
l -= 2
}
else if (k < states.length - 2 && states[k] == "op" && states[k+1] == "op" && bF._opPrc[lookahead01]) {
if (_debugprintElaboration) serial.println(`[ParserElaboration] Line ${lnum}: Digraph (${lookahead01}) found starting from the ${lang.ord(k+1)} token of [${tokens}]`)
tokens[k] = lookahead01
let oldtkn = cloneObject(tokens)
let oldsts = cloneObject(states)
tokens = oldtkn.slice(0, k+1).concat(oldtkn.slice(k+2, oldtkn.length))
states = oldsts.slice(0, k+1).concat(oldsts.slice(k+2, oldsts.length))
l -= 1
}
else if (tokens[k] == ":" && states[k] == "op")
states[k] = "seq"
k += 1
}
return {"tokens":tokens, "states":states}
}
bF._recurseApplyAST = function(tree, action) {
if (!isAST(tree)) throw new BASICerror(`tree is not a AST (${tree})`)
if (tree.astLeaves !== undefined && tree.astLeaves[0] === undefined) {
return action(tree) || tree
}
else {
let newLeaves = tree.astLeaves.map(it => bF._recurseApplyAST(it, action))
let newTree = action(tree)
if (newTree !== undefined) {
tree.astLnum = newTree.astLnum
tree.astValue = newTree.astValue
tree.astSeps = newTree.astSeps
tree.astType = newTree.astType
for (let k = 0; k < tree.astLeaves.length; k++) {
if (newLeaves[k] !== undefined) tree.astLeaves[k] = newLeaves[k]
}
}
}
}
bF._uncapAST = function(tree, action) {
let expr = cloneObject(tree)
bF._recurseApplyAST(expr, it => {
if (isAST(it.astValue)) {
let capTree = bF._uncapAST(it.astValue, action)
it.astLnum = capTree.astLnum
it.astValue = capTree.astValue
it.astSeps = capTree.astSeps
it.astType = capTree.astType
it.astLeaves = capTree.astLeaves
}
return action(it)
})
action(expr)
return expr
}
bF._EquationIllegalTokens = ["IF","THEN","ELSE","DEFUN","ON"]
bF.isSemanticLiteral = function(token, state) {
return undefined == token || "]" == token || ")" == token || "}" == token ||
"qot" == state || "num" == state || "bool" == state || "lit" == state
}
bF.parserDoDebugPrint = (!PROD) && true
bF.parserPrintdbg = any => { if (bF.parserDoDebugPrint) serial.println(any) }
bF.parserPrintdbg2 = function(icon, lnum, tokens, states, recDepth) {
if (bF.parserDoDebugPrint) {
let treeHead = "| ".repeat(recDepth)
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${tokens.join(' ')}`)
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${states.join(' ')}`)
}
}
bF.parserPrintdbgline = function(icon, msg, lnum, recDepth) {
if (bF.parserDoDebugPrint) {
let treeHead = "| ".repeat(recDepth)
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${msg}`)
}
}
bF._parseTokens = function(lnum, tokens, states) {
if (tokens.length !== states.length) throw Error("unmatched tokens and states length")
bF.parserPrintdbg2('Line ', lnum, tokens, states, 0)
if (tokens.length !== states.length) throw lang.syntaxfehler(lnum)
if (tokens[0].toUpperCase() == "REM" && states[0] != "qot") return
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let seps = []
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "(" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == ")" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
if (parenDepth == 0 && tokens[k] == ":" && states[k] == "seq")
seps.push(k)
}
let startPos = [0].concat(seps.map(k => k+1))
let stmtPos = startPos.map((s,i) => {return{start:s, end:(seps[i] || tokens.length)}})
return stmtPos.map((x,i) => {
if (stmtPos.length > 1)
bF.parserPrintdbgline('Line ', 'Statement #'+(i+1), lnum, 0)
if (x.end - x.start <= 0) throw new ParserError("Malformed Line")
let tree = bF._parseStmt(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
1
)
bF.parserPrintdbgline('Tree in ', '\n'+astToString(tree), lnum, 0)
return tree
})
}
bF._parseStmt = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('$', lnum, tokens, states, recDepth)
if (tokens.length == 1 && states.length == 1) {
bF.parserPrintdbgline('$', "Single Word Function Call", lnum, recDepth)
return bF._parseLit(lnum, tokens, states, recDepth + 1, true)
}
let headTkn = tokens[0].toUpperCase()
let headSta = states[0]
let treeHead = new BasicAST()
treeHead.astLnum = lnum
if (headTkn == "REM" && headSta != "qot") return
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let onGoPos = -1
let sepsZero = []
let sepsOne = []
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "(" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == ")" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
if (parenDepth == 0 && states[k] == "sep")
sepsZero.push(k)
if (parenDepth == 1 && states[k] == "sep")
sepsOne.push(k)
if (parenDepth == 0) {
let tok = tokens[k].toUpperCase()
if (-1 == onGoPos && ("GOTO" == tok || "GOSUB" == tok) && "lit" == states[k])
onGoPos = k
}
}
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
try {
bF.parserPrintdbgline('$', "Trying IF Statement...", lnum, recDepth)
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false)
}
catch (e) {
if (!(e instanceof ParserError)) throw e
bF.parserPrintdbgline('$', 'It was NOT!', lnum, recDepth)
}
if ("DEFUN" == headTkn && "lit" == headSta &&
parenStart == 2 && tokens[parenEnd + 1] == "=" && states[parenEnd + 1] == "op"
) {
bF.parserPrintdbgline('$', 'DEFUN Stmt', lnum, recDepth)
treeHead.astValue = "DEFUN"
treeHead.astType = "function"
if (tokens[1] == "(") {
treeHead.astLeaves[0] = new BasicAST()
treeHead.astLeaves[0].astLnum = lnum
treeHead.astLeaves[0].astType = "lit"
}
else {
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Name:', lnum, recDepth)
treeHead.astLeaves[0] = bF._parseIdent(lnum, [tokens[1]], [states[1]], recDepth + 1)
}
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments -- ', lnum, recDepth)
let defunArgDeclSeps = sepsOne.filter((i) => i < parenEnd + 1).map(i => i-1).concat([parenEnd - 1])
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments comma position: '+defunArgDeclSeps, lnum, recDepth)
treeHead.astLeaves[0].astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1))
let parseFunction = bF._parseExpr
treeHead.astLeaves[1] = parseFunction(lnum,
tokens.slice(parenEnd + 2, tokens.length),
states.slice(parenEnd + 2, states.length),
recDepth + 1
)
return treeHead
}
if ("ON" == headTkn && "lit" == headSta) {
bF.parserPrintdbgline('$', 'ON Stmt', lnum, recDepth)
if (onGoPos == -1) throw ParserError("Malformed ON Statement")
treeHead.astValue = "ON"
treeHead.astType = "function"
let testvalue = bF._parseExpr(lnum,
tokens.slice(1, onGoPos),
states.slice(1, onGoPos),
recDepth + 1,
true
)
let functionname = bF._parseExpr(lnum,
[tokens[onGoPos]],
[states[onGoPos]],
recDepth + 1,
true
)
let onArgSeps = sepsZero.filter(i => (i > onGoPos))
let onArgStartPos = [onGoPos + 1].concat(onArgSeps.map(k => k + 1))
let onArgPos = onArgStartPos.map((s,i) => {return{start:s, end: (onArgSeps[i] || tokens.length)}})
treeHead.astLeaves = [testvalue, functionname].concat(onArgPos.map((x,i) => {
bF.parserPrintdbgline('$', 'ON GOTO/GOSUB Arguments #'+(i+1), lnum, recDepth)
if (x.end - x.start <= 0) throw new ParserError("Malformed ON arguments")
return bF._parseExpr(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
recDepth + 1,
true
)
}))
return treeHead
}
if (parenStart == 0 && parenEnd == tokens.length - 1) {
bF.parserPrintdbgline('$', '( Stmt )', lnum, recDepth)
return bF._parseStmt(lnum,
tokens.slice(parenStart + 1, parenEnd),
states.slice(parenStart + 1, parenEnd),
recDepth + 1
)
}
try {
bF.parserPrintdbgline('$', 'Trying Expression Call...', lnum, recDepth)
return bF._parseExpr(lnum, tokens, states, recDepth + 1)
}
catch (e) {
bF.parserPrintdbgline('$', 'Error!', lnum, recDepth)
throw new ParserError("Statement cannot be parsed in "+lnum+": "+e.stack)
}
throw new ParserError("Statement cannot be parsed in "+lnum)
}
bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) {
bF.parserPrintdbg2('e', lnum, tokens, states, recDepth)
if (tokens[0] === undefined && states[0] === undefined) {
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = undefined
treeHead.astType = "null"
return treeHead
}
let headTkn = tokens[0].toUpperCase()
let headSta = states[0]
if (!bF._EquationIllegalTokens.includes(headTkn) && tokens.length == 1) {
bF.parserPrintdbgline('e', 'Literal Call', lnum, recDepth)
return bF._parseLit(lnum, tokens, states, recDepth + 1)
}
let topmostOp
let topmostOpPrc = 0
let operatorPos = -1
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let curlyDepth = 0
let curlyStart = -1
let curlyEnd = -1
let uptkn = ""
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "(" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == "{" && states[k] == "paren") {
curlyDepth += 1
if (curlyStart == -1 && curlyDepth == 1) curlyStart = k
}
else if (tokens[k] == ")" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
else if (tokens[k] == "}" && states[k] == "paren") {
if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k
curlyDepth -= 1
}
if (parenDepth == 0 && curlyDepth == 0) {
let uptkn = tokens[k].toUpperCase()
if (states[k] == "op" && bF.isSemanticLiteral(tokens[k-1], states[k-1]) &&
((bF._opPrc[uptkn] > topmostOpPrc) ||
(!bF._opRh[uptkn] && bF._opPrc[uptkn] == topmostOpPrc))
) {
topmostOp = uptkn
topmostOpPrc = bF._opPrc[uptkn]
operatorPos = k
}
}
}
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
try {
bF.parserPrintdbgline('e', "Trying Tuple...", lnum, recDepth)
return bF._parseTuple(lnum, tokens, states, recDepth + 1, false)
}
catch (e) {
if (!(e instanceof ParserError)) throw e
bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth)
}
if (curlyStart == 0 && curlyEnd == tokens.length - 1) {
bF.parserPrintdbgline('e', "Array", lnum, recDepth)
return bF._parseArrayLiteral(lnum, tokens, states, recDepth + 1)
}
if (parenStart == 0 && parenEnd == tokens.length - 1) {
bF.parserPrintdbgline('e', '( [Expr] )', lnum, recDepth)
return bF._parseExpr(lnum,
tokens.slice(parenStart + 1, parenEnd),
states.slice(parenStart + 1, parenEnd),
recDepth + 1
)
}
try {
bF.parserPrintdbgline('e', "Trying IF Expression...", lnum, recDepth)
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false)
}
catch (e) {
if (!(e instanceof ParserError)) throw e
bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth)
}
try {
bF.parserPrintdbgline('e', "Trying FOR Expression...", lnum, recDepth)
return bF._parseForLoop(lnum, tokens, states, recDepth + 1)
}
catch (e) {
if (!(e instanceof ParserError)) throw e
bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth)
}
if (topmostOp !== undefined) {
bF.parserPrintdbgline('e', 'Operators', lnum, recDepth)
if (ifMode && topmostOp == "=") throw lang.syntaxfehler(lnum, "'=' used on IF, did you mean '=='?")
if (ifMode && topmostOp == ":") throw lang.syntaxfehler(lnum, "':' used on IF")
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = topmostOp
treeHead.astType = "op"
if (operatorPos > 0) {
let subtknL = tokens.slice(0, operatorPos)
let substaL = states.slice(0, operatorPos)
let subtknR = tokens.slice(operatorPos + 1, tokens.length)
let substaR = states.slice(operatorPos + 1, tokens.length)
treeHead.astLeaves[0] = bF._parseExpr(lnum, subtknL, substaL, recDepth + 1)
treeHead.astLeaves[1] = bF._parseExpr(lnum, subtknR, substaR, recDepth + 1)
}
else {
if (topmostOp === "-") treeHead.astValue = "UNARYMINUS"
else if (topmostOp === "+") treeHead.astValue = "UNARYPLUS"
else if (topmostOp === "NOT") treeHead.astValue = "UNARYLOGICNOT"
else if (topmostOp === "BNOT") treeHead.astValue = "UNARYBNOT"
else if (topmostOp === "@") treeHead.astValue = "MRET"
else if (topmostOp === "`") treeHead.astValue = "MJOIN"
else throw new ParserError(`Unknown unary op '${topmostOp}'`)
treeHead.astLeaves[0] = bF._parseExpr(lnum,
tokens.slice(operatorPos + 1, tokens.length),
states.slice(operatorPos + 1, states.length),
recDepth + 1
)
}
return treeHead
}
if (bS.builtin[headTkn] && headSta == "lit" && !bF._opPrc[headTkn] &&
states[1] != "paren" && tokens[1] != "("
) {
bF.parserPrintdbgline('e', 'Builtin Function Call w/o Paren', lnum, recDepth)
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1)
}
if (topmostOp === undefined) {
try {
bF.parserPrintdbgline('e', "Trying Function Call...", lnum, recDepth)
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1)
}
catch (e) {
if (!(e instanceof ParserError)) throw e
bF.parserPrintdbgline('e', 'It was NOT!', lnum, recDepth)
}
}
throw new ParserError(`Expression "${tokens.join(" ")}" cannot be parsed in ${lnum}`)
}
bF._parseArrayLiteral = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('{', lnum, tokens, states, recDepth)
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let curlyDepth = 0
let curlyStart = -1
let curlyEnd = -1
let argSeps = []
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "(" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == "{" && states[k] == "paren") {
curlyDepth += 1
if (curlyStart == -1 && curlyDepth == 1) curlyStart = k
}
else if (tokens[k] == ")" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
else if (tokens[k] == "}" && states[k] == "paren") {
if (curlyEnd == -1 && curlyDepth == 1) curlyEnd = k
curlyDepth -= 1
}
if (parenDepth == 0 && curlyDepth == 1 && tokens[k] == "," && states[k] == "sep") {
argSeps.push(k)
}
}
if (curlyDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
if (curlyStart == -1) throw new ParserError("not an array")
bF.parserPrintdbgline('{', `curlyStart=${curlyStart}, curlyEnd=${curlyEnd}, argSeps=${argSeps}`, lnum, recDepth)
let argStartPos = [1].concat(argSeps.map(k => k+1))
let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || curlyEnd)}})
bF.parserPrintdbgline("{", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth)
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = "ARRAY CONSTRUCTOR"
treeHead.astType = "function"
if (curlyStart == 0 && curlyEnd == 1) {
treeHead.astLeaves = []
}
else {
treeHead.astLeaves = argPos.map((x,i) => {
bF.parserPrintdbgline("{", 'Array Element #'+(i+1), lnum, recDepth)
if (x.end - x.start < 0) throw new lang.syntaxfehler(lnum)
return bF._parseExpr(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
recDepth + 1
)}
)
}
return treeHead
}
bF._parseIfMode = function(lnum, tokens, states, recDepth, exprMode) {
bF.parserPrintdbg2('/', lnum, tokens, states, recDepth)
let headTkn = tokens[0].toUpperCase()
let headSta = states[0]
let parseFunction = (exprMode) ? bF._parseExpr : bF._parseStmt
let thenPos = -1
let elsePos = -1
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "(" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == ")" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
if (parenDepth == 0) {
if (-1 == thenPos && "THEN" == tokens[k].toUpperCase() && "lit" == states[k])
thenPos = k
else if (-1 == elsePos && "ELSE" == tokens[k].toUpperCase() && "lit" == states[k])
elsePos = k
}
}
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
let treeHead = new BasicAST()
treeHead.astLnum = lnum
if ("IF" == headTkn && "lit" == headSta) {
if (thenPos == -1) throw lang.syntaxfehler(lnum, "IF without THEN")
treeHead.astValue = "IF"
treeHead.astType = "function"
treeHead.astLeaves[0] = bF._parseExpr(lnum,
tokens.slice(1, thenPos),
states.slice(1, thenPos),
recDepth + 1,
true
)
treeHead.astLeaves[1] = parseFunction(lnum,
tokens.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
states.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
recDepth + 1
)
if (elsePos != -1)
treeHead.astLeaves[2] = parseFunction(lnum,
tokens.slice(elsePos + 1, tokens.length),
states.slice(elsePos + 1, tokens.length),
recDepth + 1
)
return treeHead
}
throw new ParserError("not an IF "+(exprMode) ? "expression" : "statement")
}
bF._parseForLoop = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('\\', lnum, tokens, states, recDepth)
let headTkn = tokens[0].toUpperCase()
let headSta = states[0]
let treeHead = new BasicAST()
treeHead.astLnum = lnum
if (("FOR" == headTkn || "FOREACH" == headTkn) && "lit" == headSta) {
treeHead.astValue = headTkn
treeHead.astType = "function"
treeHead.astLeaves[0] = bF._parseExpr(lnum,
tokens.slice(1),
states.slice(1),
recDepth + 1
)
return treeHead
}
throw new ParserError("not an FOR/FOREACH expression")
}
bF._parseTuple = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2(']', lnum, tokens, states, recDepth)
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let argSeps = []
for (let k = 0; k < tokens.length; k++) {
if (tokens[k] == "[" && states[k] == "paren") {
parenDepth += 1
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (tokens[k] == "]" && states[k] == "paren") {
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
parenDepth -= 1
}
if (parenDepth == 1 && parenEnd == -1 && states[k] == "sep")
argSeps.push(k)
if (parenStart != -1 && parenEnd != -1)
break
}
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
if (parenStart != 0 || parenEnd != tokens.length - 1)
throw new ParserError("not a Tuple expression")
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = undefined
treeHead.astType = "closure_args"
bF.parserPrintdbgline(']', 'Tuple arguments -- ', lnum, recDepth)
let defunArgDeclSeps = argSeps.map(i => i-1).concat([parenEnd - 1])
bF.parserPrintdbgline(']', 'Tuple comma position: '+defunArgDeclSeps, lnum, recDepth)
treeHead.astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1))
return treeHead
}
bF._parseFunctionCall = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2("F", lnum, tokens, states, recDepth)
let parenDepth = 0
let parenStart = -1
let parenEnd = -1
let _argsepsOnLevelZero = []
let _argsepsOnLevelOne = []
let currentParenMode = []
let depthsOfRoundParen = []
for (let k = 0; k < tokens.length; k++) {
if (bF._isParenOpen(tokens[k]) && states[k] == "paren") {
parenDepth += 1; currentParenMode.unshift(tokens[k])
if (currentParenMode[0] == '(') depthsOfRoundParen.push(parenDepth)
if (parenStart == -1 && parenDepth == 1) parenStart = k
}
else if (bF._isParenClose(tokens[k]) && states[k] == "paren") {
if (!bF._isMatchingParen(currentParenMode[0], tokens[k]))
throw lang.syntaxfehler(lnum, `Opening paren: ${currentParenMode[0]}, closing paren: ${tokens[k]}`)
if (parenEnd == -1 && parenDepth == 1) parenEnd = k
if (currentParenMode[0] == '(') depthsOfRoundParen.pop()
parenDepth -= 1; currentParenMode.shift()
}
if (parenDepth == 0 && states[k] == "sep" && currentParenMode[0] === undefined)
_argsepsOnLevelZero.push(k)
if (parenDepth == depthsOfRoundParen[0] && states[k] == "sep" && currentParenMode[0] == "(")
_argsepsOnLevelOne.push(k)
}
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets)
let parenUsed = (parenStart == 1)
bF.parserPrintdbgline("F", `parenStart: ${parenStart}, parenEnd: ${parenEnd}`, lnum, recDepth)
bF.parserPrintdbgline("F", `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth)
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = bF._parseIdent(lnum, [tokens[0]], [states[0]], recDepth + 1).astValue
let argSeps = parenUsed ? _argsepsOnLevelOne : _argsepsOnLevelZero
bF.parserPrintdbgline("F", "argSeps = "+argSeps, lnum, recDepth)
let argStartPos = [1 + (parenUsed)].concat(argSeps.map(k => k+1))
bF.parserPrintdbgline("F", "argStartPos = "+argStartPos, lnum, recDepth)
let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || (parenUsed ? parenEnd : tokens.length) )}})
bF.parserPrintdbgline("F", "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth)
treeHead.astLeaves = argPos.map((x,i) => {
bF.parserPrintdbgline("F", `Function Arguments #${i+1} of ${argPos.length}`, lnum, recDepth)
if (x.end - x.start < 0) throw new ParserError("not a function call because it's malformed")
return bF._parseExpr(lnum,
tokens.slice(x.start, x.end),
states.slice(x.start, x.end),
recDepth + 1
)}
)
treeHead.astType = "function"
treeHead.astSeps = argSeps.map(i => tokens[i])
bF.parserPrintdbgline("F", "astSeps = "+treeHead.astSeps, lnum, recDepth)
return treeHead
}
bF._parseIdent = function(lnum, tokens, states, recDepth) {
bF.parserPrintdbg2('i', lnum, tokens, states, recDepth)
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array")
if (tokens.length != 1 || states[0] != "lit") throw new ParserError(`illegal tokens '${tokens}' with states '${states}' in ${lnum}`)
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = tokens[0].toUpperCase()
treeHead.astType = "lit"
return treeHead
}
bF._parseLit = function(lnum, tokens, states, recDepth, functionMode) {
bF.parserPrintdbg2('i', lnum, tokens, states, recDepth)
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array")
if (tokens.length != 1) throw new ParserError("parseLit 1")
let treeHead = new BasicAST()
treeHead.astLnum = lnum
treeHead.astValue = ("qot" == states[0]) ? tokens[0] : tokens[0].toUpperCase()
treeHead.astType = ("qot" == states[0]) ? "string" :
("num" == states[0]) ? "num" :
(functionMode) ? "function" : "lit"
return treeHead
}
bF._findDeBruijnIndex = function(varname, offset) {
let recurseIndex = -1
let orderlyIndex = -1
for (recurseIndex = 0; recurseIndex < lambdaBoundVars.length; recurseIndex++) {
orderlyIndex = lambdaBoundVars[recurseIndex].findIndex(it => it == varname)
if (orderlyIndex != -1)
return [recurseIndex + (offset || 0), orderlyIndex]
}
throw new ParserError("Unbound variable: "+varname)
}
bF._pruneTree = function(lnum, tree, recDepth) {
if (tree === undefined) return
if (DBGON) {
serial.println("[Parser.PRUNE] pruning following subtree, lambdaBoundVars = "+Object.entries(lambdaBoundVars))
serial.println(astToString(tree))
if (isAST(tree) && isAST(tree.astValue)) {
serial.println("[Parser.PRUNE] unpacking astValue:")
serial.println(astToString(tree.astValue))
}
}
let defunName = undefined
if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") {
let nameTree = tree.astLeaves[0]
if (tree.astValue == "DEFUN") {
defunName = nameTree.astValue
if (DBGON) {
serial.println("[Parser.PRUNE.~>] met DEFUN, function name: "+defunName)
}
}
let vars = nameTree.astLeaves.map((it, i) => {
if (it.astType !== "lit") throw new ParserError("Malformed bound variable for function definition; tree:\n"+astToString(nameTree))
return it.astValue
})
lambdaBoundVars.unshift(vars)
if (DBGON) {
serial.println("[Parser.PRUNE.~>] added new bound variables: "+Object.entries(lambdaBoundVars))
}
}
else if (tree.astValue == "UNARYMINUS" && tree.astType == "op" &&
tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num"
) {
tree.astValue = -(tree.astLeaves[0].astValue)
tree.astType = "num"
tree.astLeaves = []
}
else if (tree.astValue == "UNARYPLUS" && tree.astType == "op" &&
tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num"
) {
tree.astValue = +(tree.astLeaves[0].astValue)
tree.astType = "num"
tree.astLeaves = []
}
if (tree.astLeaves[0] != undefined) {
tree.astLeaves.forEach(it => bF._pruneTree(lnum, it, recDepth + 1))
}
if (tree.astType == "op" && tree.astValue == "~>" || tree.astType == "function" && tree.astValue == "DEFUN") {
if (tree.astLeaves.length !== 2) throw lang.syntaxfehler(lnum, tree.astLeaves.length+lang.aG)
let nameTree = tree.astLeaves[0]
let exprTree = tree.astLeaves[1]
if (DBGON) {
serial.println("[Parser.PRUNE.~>] closure bound variables: "+Object.entries(lambdaBoundVars))
}
bF._recurseApplyAST(exprTree, (it) => {
if (it.astType == "lit" || it.astType == "function") {
try {
let dbi = bF._findDeBruijnIndex(it.astValue)
if (DBGON) {
serial.println(`index for ${it.astValue}: ${dbi}`)
}
it.astValue = dbi
it.astType = "defun_args"
}
catch (_) {}
}
})
tree.astType = "usrdefun"
tree.astValue = exprTree
tree.astLeaves = []
lambdaBoundVars.shift()
}
if (defunName) {
let nameTree = new BasicAST()
nameTree.astLnum = tree.astLnum
nameTree.astType = "lit"
nameTree.astValue = defunName
let newTree = new BasicAST()
newTree.astLnum = tree.astLnum
newTree.astType = "op"
newTree.astValue = "="
newTree.astLeaves = [nameTree, tree]
tree = newTree
if (DBGON) {
serial.println(`[Parser.PRUNE] has DEFUN, function name: ${defunName}`)
}
}
if (DBGON) {
serial.println("[Parser.PRUNE] pruned subtree:")
serial.println(astToString(tree))
if (isAST(tree) && isAST(tree.astValue)) {
serial.println("[Parser.PRUNE] unpacking astValue:")
serial.println(astToString(tree.astValue))
}
serial.println("======================================================\n")
}
return tree
}
let JStoBASICtype = function(object) {
if (typeof object === "boolean") return "bool"
else if (object === undefined) return "null"
else if (object.arrName !== undefined) return "internal_arrindexing_lazy"
else if (object.asgnVarName !== undefined) return "internal_assignment_object"
else if (isGenerator(object)) return "generator"
else if (isAST(object)) return "usrdefun"
else if (isMonad(object)) return "monad"
else if (Array.isArray(object)) return "array"
else if (isNumable(object)) return "num"
else if (typeof object === "string" || object instanceof String) return "string"
else throw Error("BasicIntpError: un-translatable object with typeof "+(typeof object)+",\ntoString = "+object+",\nentries = "+Object.entries(object))
}
let SyntaxTreeReturnObj = function(type, value, nextLine) {
if (nextLine === undefined || !Array.isArray(nextLine))
throw Error("TODO change format of troNextLine to [linenumber, stmtnumber]")
this.troType = type
this.troValue = value
this.troNextLine = nextLine
}
let JumpObj = function(targetLnum, targetStmtNum, fromLnum, rawValue) {
this.jmpNext = [targetLnum, targetStmtNum]
this.jmpFrom = fromLnum
this.jmpReturningValue = rawValue
}
bF._makeRunnableFunctionFromExprTree = function(lnum, stmtnum, expression, args, recDepth, _debugExec, recWedge) {
let defunArgs = args.map(it => {
let rit = resolve(it)
return [JStoBASICtype(rit), rit]
})
lambdaBoundVars.unshift(defunArgs)
if (_debugExec) {
serial.println(recWedge+"usrdefun dereference")
serial.println(recWedge+"usrdefun dereference function: ")
serial.println(astToString(expression))
serial.println(recWedge+"usrdefun dereference bound vars: "+theLambdaBoundVars())
}
let bindVar = function(tree, recDepth) {
bF._recurseApplyAST(tree, it => {
if (_debugExec) {
serial.println(recWedge+`usrdefun${recDepth} trying to bind some variables to:`)
serial.println(astToString(it))
}
if (it.astType == "defun_args") {
let recIndex = it.astValue[0] - recDepth
let varIndex = it.astValue[1]
if (_debugExec) {
serial.println(recWedge+`usrdefun${recDepth} bindvar d(${recIndex},${varIndex})`)
}
let theVariable = undefined
try {
theVariable = lambdaBoundVars[recIndex][varIndex]
}
catch (e0) {}
if (theVariable !== undefined) {
it.astValue = theVariable[1]
it.astType = theVariable[0]
}
if (_debugExec) {
serial.println(recWedge+`usrdefun${recDepth} the bindvar: ${theVariable}`)
serial.println(recWedge+`usrdefun${recDepth} modified tree:`)
serial.println(astToString(it))
}
}
else if (it.astType == "usrdefun") {
bindVar(it.astValue, recDepth + 1)
}
})
};bindVar(expression, 0)
if (_debugExec) {
serial.println(recWedge+"usrdefun dereference final tree:")
serial.println(astToString(expression))
}
return bS.getDefunThunk(expression, true)
}
bF._troNOP = function(lnum, stmtnum) { return new SyntaxTreeReturnObj("null", undefined, [lnum, stmtnum+1]) }
bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
if (syntaxTree == undefined) return bF._troNOP(lnum, stmtnum)
if (syntaxTree.astLeaves === undefined && syntaxTree.astValue === undefined) {
throw new BASICerror("not a syntax tree")
}
if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`)
let _debugExec = (!PROD) && true
let _debugPrintCurrentLine = (!PROD) && true
let recWedge = ">".repeat(recDepth+1) + " "
let tearLine = "\n =====ExecSyntaxTree===== "+("<".repeat(recDepth+1))+"\n"
if (_debugExec || _debugPrintCurrentLine) serial.println(recWedge+`@@ EXECUTE ${lnum}:${stmtnum} @@`)
if (_debugPrintCurrentLine) {
serial.println("Syntax Tree in "+lnum+":")
serial.println(astToString(syntaxTree))
}
let callingUsrdefun = (syntaxTree.astType == "usrdefun" && syntaxTree.astLeaves[0] !== undefined)
if (syntaxTree.astValue == undefined && syntaxTree.mVal == undefined) {
if (syntaxTree.astLeaves.length > 1) throw Error("WTF")
return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth)
}
else if (syntaxTree.astType == "array" && syntaxTree.astLeaves[0] !== undefined) {
let indexer = bS.getArrayIndexFun(lnum, stmtnum, "substituted array", syntaxTree.astValue)
let args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1))
let retVal = indexer(lnum, stmtnum, args)
if (_debugExec) serial.println(recWedge+`indexing substituted array(${Object.entries(args)}) = ${Object.entries(retVal)}`)
return new SyntaxTreeReturnObj(
JStoBASICtype(retVal),
retVal,
[lnum, stmtnum + 1]
)
}
else if (syntaxTree.astType == "op" && syntaxTree.astValue == "~>") {
throw new BASICerror("Untended closure")
}
else if (syntaxTree.astType == "function" && syntaxTree.astValue == "DEFUN") {
throw new BASICerror("Untended DEFUN")
}
else if (syntaxTree.astType == "function" || syntaxTree.astType == "op" || callingUsrdefun) {
if (_debugExec) serial.println(recWedge+"function|operator")
if (_debugExec) serial.println(recWedge+astToString(syntaxTree))
let callerHash = syntaxTree.astHash
let funcName = (typeof syntaxTree.astValue.toUpperCase == "function") ? syntaxTree.astValue.toUpperCase() : "(usrdefun)"
let lambdaBoundVarsAppended = (callingUsrdefun)
let func = (callingUsrdefun)
? bF._makeRunnableFunctionFromExprTree(
lnum, stmtnum,
cloneObject(syntaxTree.astValue),
syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)),
recDepth, _debugExec, recWedge
)
: (bS.builtin[funcName] === undefined)
? undefined
: (!DBGON && bS.builtin[funcName].debugonly) ? "NO_DBG4U" : (PROD && bS.builtin[funcName].noprod) ? "NO_PRODREADY" : bS.builtin[funcName].f
if (func === "NO_DBG4U") throw lang.syntaxfehler(lnum)
if (func === "NO_PRODREADY") throw lang.syntaxfehler(lnum)
if ("IF" == funcName) {
if (syntaxTree.astLeaves.length != 2 && syntaxTree.astLeaves.length != 3) throw lang.syntaxfehler(lnum)
var testedval = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1)
if (_debugExec) {
serial.println(recWedge+"testedval:")
serial.println(recWedge+"type="+testedval.troValue.astType)
serial.println(recWedge+"value="+testedval.troValue.astValue)
serial.println(recWedge+"nextLine="+testedval.troValue.astNextLine)
}
try {
var iftest = bS.builtin["TEST"].f(lnum, stmtnum, [testedval])
let r = (!iftest && syntaxTree.astLeaves[2] !== undefined) ?
bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[2], recDepth + 1)
: (iftest) ?
bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[1], recDepth + 1)
: bF._troNOP(lnum, stmtnum)
if (_debugExec) serial.println(tearLine)
return r
}
catch (e) {
serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`)
throw lang.errorinline(lnum, "TEST", e)
}
}
else if ("ON" == funcName) {
if (syntaxTree.astLeaves.length < 3) throw lang.badFunctionCallFormat(lnum)
let testValue = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1)
let functionName = syntaxTree.astLeaves[1].astValue
let arrays = []
for (let k = 2; k < syntaxTree.astLeaves.length; k++)
arrays.push(bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[k], recDepth + 1))
try {
let r = bS.builtin["ON"].f(lnum, stmtnum, [functionName, testValue].concat(arrays))
let r2 = new SyntaxTreeReturnObj(JStoBASICtype(r.jmpReturningValue), r.jmpReturningValue, r.jmpNext)
if (_debugExec) serial.println(tearLine)
return r2
}
catch (e) {
serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`)
throw lang.errorinline(lnum, "ON error", e)
}
}
else {
let args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1))
if (_debugExec) {
serial.println(recWedge+`fn caller: "${callerHash}"`)
serial.println(recWedge+`fn call name: "${funcName}"`)
serial.println(recWedge+"fn call args: "+(args.map(it => (it == undefined) ? it : (it.troType+" "+it.troValue)).join(", ")))
}
if (func === undefined) {
var someVar = bS.vars[funcName]
if (someVar !== undefined && DBGON) {
serial.println(recWedge+`variable dereference of '${funcName}' : ${someVar.bvLiteral} (bvType: ${someVar.bvType})`)
if (typeof someVar.bvLiteral == "object")
serial.println(recWedge+"variable as an object : "+Object.entries(someVar.bvLiteral))
}
if (someVar === undefined) {
throw lang.syntaxfehler(lnum, funcName + " is undefined")
}
else if ("array" == someVar.bvType) {
func = bS.getArrayIndexFun(lnum, stmtnum, funcName, someVar.bvLiteral)
}
else if ("usrdefun" == someVar.bvType) {
let expression = cloneObject(someVar.bvLiteral)
lambdaBoundVarsAppended = true
func = bF._makeRunnableFunctionFromExprTree(lnum, stmtnum, expression, args, recDepth, _debugExec, recWedge)
}
else if ("monad" == someVar.bvType) {
func = getMonadEvalFun(someVar.bvLiteral)
}
else {
throw lang.syntaxfehler(lnum, funcName + " is not a function or an array")
}
}
if (func === undefined) {
serial.printerr(lnum+` ${funcName} is undefined`)
throw lang.syntaxfehler(lnum, funcName + " is undefined")
}
let funcCallResult = func(lnum, stmtnum, args, syntaxTree.astSeps)
if (funcCallResult instanceof SyntaxTreeReturnObj) return funcCallResult
let retVal = (funcCallResult instanceof JumpObj) ? funcCallResult.jmpReturningValue : funcCallResult
let theRealRet = new SyntaxTreeReturnObj(
JStoBASICtype(retVal),
retVal,
(funcCallResult instanceof JumpObj) ? funcCallResult.jmpNext : [lnum, stmtnum + 1]
)
if (lambdaBoundVarsAppended) lambdaBoundVars.shift()
if (_debugExec) serial.println(tearLine)
return theRealRet
}
}
else if (syntaxTree.astType == "defun_args") {
if (_debugExec) {
serial.println(recWedge+"defun_args lambda bound vars: "+(lambdaBoundVars === undefined) ? undefined : theLambdaBoundVars())
serial.println(recWedge+"defun_args defun args: "+syntaxTree.astValue)
}
let recIndex = syntaxTree.astValue[0]
let varIndex = syntaxTree.astValue[1]
let theVar = lambdaBoundVars[recIndex, varIndex]
if (_debugExec) {
serial.println(recWedge+"defun_args thevar: "+(theVar === undefined) ? undefined : Object.entries(theVar))
serial.println(tearLine)
}
return theVar
}
else if (syntaxTree.astType == "num") {
if (_debugExec) serial.println(recWedge+"num "+(tonum(syntaxTree.astValue)))
let r = new SyntaxTreeReturnObj(syntaxTree.astType, tonum(syntaxTree.astValue), [lnum, stmtnum + 1])
if (_debugExec) serial.println(tearLine)
return r
}
else if (syntaxTree.astType == "lit" || literalTypes.includes(syntaxTree.astType)) {
if (_debugExec) {
serial.println(recWedge+"literal with astType: "+syntaxTree.astType+", astValue: "+syntaxTree.astValue)
if (isAST(syntaxTree.astValue)) {
serial.println(recWedge+"astValue is a tree, unpacking: \n"+astToString(syntaxTree.astValue))
}
}
let r = new SyntaxTreeReturnObj(syntaxTree.astType, syntaxTree.astValue, [lnum, stmtnum + 1])
if (_debugExec) serial.println(tearLine)
return r
}
else if (syntaxTree.astType == "null") {
if (_debugExec) serial.println(recWedge+"null")
let r = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1)
if (_debugExec) serial.println(tearLine)
return r
}
else {
serial.println(recWedge+"Parsing error in "+lnum)
serial.println(recWedge+astToString(syntaxTree))
throw Error("Parsing error")
}
}
bF._interpretLine = function(lnum, cmd) {
let _debugprintHighestLevel = false
if (cmd.toUpperCase().startsWith("REM")) {
if (_debugprintHighestLevel) serial.println(lnum+" "+cmd)
return undefined
}
let tokenisedObject = bF._tokenise(lnum, cmd)
let tokens = tokenisedObject.tokens
let states = tokenisedObject.states
let newtoks = bF._parserElaboration(lnum, tokens, states)
tokens = newtoks.tokens
states = newtoks.states
let syntaxTrees = bF._parseTokens(lnum, tokens, states).map(it => {
if (lambdaBoundVars.length != 0)
throw new BASICerror("lambdaBoundVars not empty")
return bF._pruneTree(lnum, it, 0)
})
if (_debugprintHighestLevel) {
syntaxTrees.forEach((t,i) => {
serial.println("\nParsed Statement #"+(i+1))
serial.println(astToString(t))
})
}
return syntaxTrees
}
bF._executeAndGet = function(lnum, stmtnum, syntaxTree) {
if (lnum === undefined || stmtnum === undefined) throw Error(`Line or statement number is undefined: (${lnum},${stmtnum})`)
try {
if (lambdaBoundVars.length != 0) throw new BASICerror()
var execResult = bF._executeSyntaxTree(lnum, stmtnum, syntaxTree, 0)
if (DBGON) serial.println(`Line ${lnum} TRO: ${Object.entries(execResult)}`)
return execResult.troNextLine
}
catch (e) {
serial.printerr(`ERROR on ${lnum}:${stmtnum} -- PARSE TREE:\n${astToString(syntaxTree)}\nERROR CONTENTS:\n${e}\n${e.stack || "Stack trace undefined"}`)
throw e
}
}
bF._basicList = function(v, i, arr) {
if (i < 10) print(" ")
if (i < 100) print(" ")
print(i)
print(" ")
println(v)
}
bF.list = function(args) {
if (args.length == 1) {
cmdbuf.forEach(bF._basicList)
}
else if (args.length == 2) {
if (cmdbuf[args[1]] !== undefined)
bF._basicList(cmdbuf[args[1]], args[1], undefined)
}
else {
var lastIndex = (args[2] === ".") ? cmdbuf.length - 1 : (args[2] | 0)
var i = 0
for (i = args[1]; i <= lastIndex; i++) {
var cmd = cmdbuf[i]
if (cmd !== undefined) {
bF._basicList(cmd, i, cmdbuf)
}
}
}
}
bF.system = function(args) {
tbasexit = true
}
bF.new = function(args) {
if (args) cmdbuf = []
bS.vars = initBvars()
gotoLabels = {}
lambdaBoundVars = []
DATA_CONSTS = []
DATA_CURSOR = 0
INDEX_BASE = 0
}
bF.renum = function(args) {
var newcmdbuf = []
var linenumRelation = [[]]
var cnt = 10
for (var k = 0; k < cmdbuf.length; k++) {
if (cmdbuf[k] !== undefined) {
newcmdbuf[cnt] = cmdbuf[k].trim()
linenumRelation[k] = cnt
cnt += 10
}
}
for (k = 0; k < newcmdbuf.length; k++) {
if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("goto ")) {
newcmdbuf[k] = "GOTO " + linenumRelation[newcmdbuf[k].match(reNum)[0]]
}
else if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("gosub ")) {
newcmdbuf[k] = "GOSUB " + linenumRelation[newcmdbuf[k].match(reNum)[0]]
}
else if (newcmdbuf[k] !== undefined && newcmdbuf[k].toLowerCase().startsWith("breakto ")) {
newcmdbuf[k] = "BREAKTO " + linenumRelation[newcmdbuf[k].match(reNum)[0]]
}
}
cmdbuf = newcmdbuf.slice()
cmdbufMemFootPrint = 0
cmdbuf.forEach((v, i, arr) =>
cmdbufMemFootPrint += ("" + i).length + 1 + v.length
)
}
bF.fre = function(args) {
println(vmemsize - getUsedMemSize())
}
bF.tron = function(args) {
TRACEON = true
}
bF.troff = function(args) {
TRACEON = false
}
bF.delete = function(args) {
if (args.length != 2 && args.length != 3) throw lang.syntaxfehler()
let start = 0; let end = 0
if (args.length == 2) {
if (!isNumable(args[1])) throw lang.badFunctionCallFormat()
start = args[1]|0
end = args[1]|0
}
else {
if (!isNumable(args[1]) && !isNumable(args[2])) throw lang.badFunctionCallFormat()
start = args[1]|0
end = args[2]|0
}
let newcmdbuf = []
cmdbuf.forEach((v,i) => {if (i < start || i > end) newcmdbuf[i]=v})
cmdbuf = newcmdbuf
}
bF.cls = function(args) {
con.clear()
}
bF.prescanStmts = ["DATA","LABEL"]
bF.run = function(args) {
bF.new(false)
let programTrees = []
prescan = true
cmdbuf.forEach((linestr, linenum) => {
let trees = bF._interpretLine(linenum, linestr.trim())
programTrees[linenum] = trees
if (trees !== undefined) {
trees.forEach((t, i) => {
if (t !== undefined && bF.prescanStmts.includes(t.astValue)) {
bF._executeAndGet(linenum, i, t)
}
})
}
})
prescan = false
if (!PROD && DBGON) {
serial.println("[BASIC] final DATA: "+DATA_CONSTS)
}
let lnum = 1
let stmtnum = 0
let oldnum = 1
let tree = undefined
do {
if (programTrees[lnum] !== undefined) {
if (TRACEON) {
serial.println("[BASIC] Line "+lnum)
}
oldnum = lnum
tree = (programTrees[lnum] !== undefined) ? programTrees[lnum][stmtnum] : undefined
if (tree !== undefined) {
let nextObj = bF._executeAndGet(lnum, stmtnum, tree)
lnum = nextObj[0]
stmtnum = nextObj[1]
}
else {
lnum += 1
stmtnum = 0
}
}
else {
lnum += 1
}
if (lnum < 0) throw lang.badNumberFormat
if (con.hitterminate()) {
println("Break in "+oldnum)
break
}
} while (lnum < cmdbuf.length)
con.resetkeybuf()
}
bF.save = function(args) {
if (args[1] === undefined) throw lang.missingOperand
if (!args[1].toUpperCase().endsWith(".BAS"))
args[1] += ".bas"
fs.open(args[1], "W")
var sb = ""
cmdbuf.forEach((v, i) => sb += i+" "+v+"\n")
fs.write(sb)
}
bF.load = function(args) {
if (args[1] === undefined) throw lang.missingOperand
var fileOpened = fs.open(args[1], "R")
if (replUsrConfirmed || cmdbuf.length == 0) {
if (!fileOpened) {
fileOpened = fs.open(args[1]+".BAS", "R")
}
if (!fileOpened) {
fileOpened = fs.open(args[1]+".bas", "R")
}
if (!fileOpened) {
throw lang.noSuchFile
return
}
var prg = fs.readAll()
bF.new(true)
prg.split('\n').forEach((line) => {
var i = line.indexOf(" ")
var lnum = line.slice(0, i)
if (isNaN(lnum)) throw lang.illegalType()
cmdbuf[lnum] = line.slice(i + 1, line.length)
})
}
else {
replCmdBuf = ["load"].concat(args)
println("Unsaved program will be lost, are you sure? (type 'yes' to confirm)")
}
}
bF.yes = function() {
if (replCmdBuf.length > 0) {
replUsrConfirmed = true
bF[replCmdBuf[0].toLowerCase()](replCmdBuf.slice(1, replCmdBuf.length))
replCmdBuf = []
replUsrConfirmed = false
}
else {
throw lang.syntaxfehler("interactive", "nothing to confirm!")
}
}
bF.catalog = function(args) {
if (args[1] === undefined) args[1] = "\\"
var pathOpened = fs.open(args[1], 'R')
if (!pathOpened) {
throw lang.noSuchFile
return
}
var port = _BIOS.FIRST_BOOTABLE_PORT[0]
com.sendMessage(port, "LIST")
println(com.pullMessage(port))
}
Object.freeze(bF)
while (!tbasexit) {
var line = sys.read().trim()
cmdbufMemFootPrint += line.length
if (reLineNum.test(line)) {
var i = line.indexOf(" ")
cmdbuf[line.slice(0, i)] = line.slice(i + 1, line.length)
}
else if (line.length > 0) {
cmdbufMemFootPrint -= line.length
var cmd = line.split(" ")
if (bF[cmd[0].toLowerCase()] === undefined) {
serial.printerr("Unknown command: "+cmd[0].toLowerCase())
println(lang.syntaxfehler())
}
else {
try {
bF[cmd[0].toLowerCase()](cmd)
}
catch (e) {
serial.printerr(`${e}\n${e.stack || "Stack trace undefined"}`)
println(`${e}`)
}
}
println(prompt)
}
}