basic: fixed a bug where empty string would be resolved to number zero; improving performance by building parsing trees prior to the actual execution

This commit is contained in:
minjaesong
2020-12-06 23:23:41 +09:00
parent d5606b5fe5
commit 3003de0bb2
3 changed files with 92 additions and 50 deletions

View File

@@ -1,13 +1,13 @@
1 FOR I = 99 TO 1 STEP -1 1 FOR I = 99 TO 1 STEP -1
2 MODE = 1 2 MODE = 1
3 GOSUB 12 3 GOSUB 12
4 PRINT(I+" bottle"+BOTTLES$+" of beer on the wall, "+i+" bottle"+BOTTLES$+" of beer.") 4 PRINT I;" bottle";BOTTLES;" of beer on the wall, ";i;" bottle";BOTTLES;" of beer."
5 MODE = 2 5 MODE = 2
6 GOSUB 12 6 GOSUB 12
7 PRINT("Take one down and pass it around, "+(i-1)+" bottle"+BOTTLES$+" of beer on the wall.") 7 PRINT "Take one down and pass it around, ";(I-1);" bottle";BOTTLES;" of beer on the wall."
8 NEXT 8 NEXT
9 PRINT "No more bottles of beer on the wall, no more bottles of beer." 9 PRINT "No more bottles of beer on the wall, no more bottles of beer."
10 PRINT "Go to the store and buy some more. 99 bottles of beer on the wall." 10 PRINT "Go to the store and buy some more. 99 bottles of beer on the wall."
11 END 11 END
12 IF I == MODE THEN BOTTLES$ = "" ELSE BOTTLES$ = "s" 12 IF I == MODE THEN BOTTLES = "" ELSE BOTTLES = "s"
13 RETURN 13 RETURN

View File

@@ -15,7 +15,7 @@ Test Programs:
20 GOTO 10 20 GOTO 10
*/ */
if (exec_args[1] !== undefined && exec_args[1].startsWith("-?")) { if (exec_args !== undefined && exec_args[1] !== undefined && exec_args[1].startsWith("-?")) {
println("Usage: basic <optional path to basic program>"); println("Usage: basic <optional path to basic program>");
println("When the optional basic program is set, the interpreter will run the program and then quit if successful, remain open if the program had an error."); println("When the optional basic program is set, the interpreter will run the program and then quit if successful, remain open if the program had an error.");
return 0; return 0;
@@ -25,6 +25,8 @@ if (exec_args[1] !== undefined && exec_args[1].startsWith("-?")) {
let INDEX_BASE = 0; let INDEX_BASE = 0;
let TRACEON = false; let TRACEON = false;
let DBGON = true; let DBGON = true;
let DATA_CURSOR = 0;
let DATA_CONSTS = [];
if (system.maxmem() < 8192) { if (system.maxmem() < 8192) {
println("Out of memory. BASIC requires 8K or more User RAM"); println("Out of memory. BASIC requires 8K or more User RAM");
@@ -37,6 +39,11 @@ let cmdbuf = []; // index: line number
let cmdbufMemFootPrint = 0; let cmdbufMemFootPrint = 0;
let prompt = "Ok"; let prompt = "Ok";
/* if string can be FOR REAL cast to number */
function isNumable(s) {
return s !== undefined && (typeof s.trim == "function" && s.trim() !== "" || s.trim == undefined) && !isNaN(s);
}
let lang = {}; let lang = {};
lang.badNumberFormat = "Illegal number format"; lang.badNumberFormat = "Illegal number format";
lang.badOperatorFormat = "Illegal operator format"; lang.badOperatorFormat = "Illegal operator format";
@@ -44,6 +51,9 @@ lang.badFunctionCallFormat = "Illegal function call";
lang.unmatchedBrackets = "Unmatched brackets"; lang.unmatchedBrackets = "Unmatched brackets";
lang.missingOperand = "Missing operand"; lang.missingOperand = "Missing operand";
lang.noSuchFile = "No such file"; lang.noSuchFile = "No such file";
lang.outOfData = function(line) {
return "Out of DATA"+(line !== undefined ? (" in "+line) : "");
};
lang.nextWithoutFor = function(line, varname) { lang.nextWithoutFor = function(line, varname) {
return "NEXT "+((varname !== undefined) ? ("'"+varname+"'") : "")+"without FOR in "+line; return "NEXT "+((varname !== undefined) ? ("'"+varname+"'") : "")+"without FOR in "+line;
}; };
@@ -234,6 +244,7 @@ let resolve = function(variable) {
return variable.troValue; return variable.troValue;
else if (variable.troType == "lit") { else if (variable.troType == "lit") {
var basicVar = bStatus.vars[variable.troValue]; var basicVar = bStatus.vars[variable.troValue];
if (basicVar.bvLiteral === "") return "";
return (basicVar !== undefined) ? basicVar.bvLiteral : undefined; return (basicVar !== undefined) ? basicVar.bvLiteral : undefined;
} }
else if (variable.troType == "null") else if (variable.troType == "null")
@@ -278,39 +289,39 @@ let twoArgNum = function(lnum, args, action) {
} }
let threeArg = function(lnum, args, action) { let threeArg = function(lnum, args, action) {
if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG);
argCheckErr(lnum, args[0]);
var rsvArg0 = resolve(args[0]); var rsvArg0 = resolve(args[0]);
if (rsvArg0 === undefined) throw lang.refError(lnum, args[0]); argCheckErr(lnum, args[1]);
var rsvArg1 = resolve(args[1]); var rsvArg1 = resolve(args[1]);
if (rsvArg1 === undefined) throw lang.refError(lnum, args[1]); argCheckErr(lnum, args[2]);
var rsvArg2 = resolve(args[2]); var rsvArg2 = resolve(args[2]);
if (rsvArg2 === undefined) throw lang.refError(lnum, args[2]);
return action(rsvArg0, rsvArg1, rsvArg2); return action(rsvArg0, rsvArg1, rsvArg2);
} }
let threeArgNum = function(lnum, args, action) { let threeArgNum = function(lnum, args, action) {
if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 3) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var rsvArg0 = resolve(args[0]);
if (rsvArg0 === undefined) throw lang.refError(lnum, args[0]); if (rsvArg0 === undefined) throw lang.refError(lnum, args[0]);
argCheckErr(lnum, args[0]);
if (isNaN(rsvArg0)) throw lang.illegalType(lnum, args[0]); if (isNaN(rsvArg0)) throw lang.illegalType(lnum, args[0]);
var rsvArg1 = resolve(args[1]);
if (rsvArg1 === undefined) throw lang.refError(lnum, args[1]); if (rsvArg1 === undefined) throw lang.refError(lnum, args[1]);
argCheckErr(lnum, args[1]);
if (isNaN(rsvArg1)) throw lang.illegalType(lnum, args[1]); if (isNaN(rsvArg1)) throw lang.illegalType(lnum, args[1]);
var rsvArg2 = resolve(args[2]);
if (rsvArg2 === undefined) throw lang.refError(lnum, args[2]); if (rsvArg2 === undefined) throw lang.refError(lnum, args[2]);
argCheckErr(lnum, args[2]);
if (isNaN(rsvArg2)) throw lang.illegalType(lnum, args[2]); if (isNaN(rsvArg2)) throw lang.illegalType(lnum, args[2]);
return action(rsvArg0, rsvArg1, rsvArg2); return action(rsvArg0, rsvArg1, rsvArg2);
} }
let varArg = function(lnum, args, action) { let varArg = function(lnum, args, action) {
var rsvArg = args.map((it) => { var rsvArg = args.map((it) => {
argCheckErr(lnum, it);
var r = resolve(it); var r = resolve(it);
if (r === undefined) throw lang.refError(lnum, r);
return r; return r;
}); });
return action(rsvArg); return action(rsvArg);
} }
let varArgNum = function(lnum, args, action) { let varArgNum = function(lnum, args, action) {
var rsvArg = args.map((it) => { var rsvArg = args.map((it) => {
argCheckErr(lnum, it);
var r = resolve(it); var r = resolve(it);
if (r === undefined) throw lang.refError(lnum, r);
if (isNaN(r)) throw lang.illegalType(lnum, r); if (isNaN(r)) throw lang.illegalType(lnum, r);
return r; return r;
}); });
@@ -453,7 +464,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
var rh = resolve(args[1]); var rh = resolve(args[1]);
if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue); if (rh === undefined) throw lang.refError(lnum, "RH:"+args[1].troValue);
if (!isNaN(rh)) rh = rh*1 // if string we got can be cast to number, do it if (isNumable(rh)) rh = rh*1 // if string we got can be cast to number, do it
//println(lnum+" = lh: "+Object.entries(args[0])); //println(lnum+" = lh: "+Object.entries(args[0]));
//println(lnum+" = rh raw: "+Object.entries(args[1])); //println(lnum+" = rh raw: "+Object.entries(args[1]));
@@ -596,37 +607,9 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return twoArgNum(lnum, args, (lh,rh) => Math.pow(lh, rh)); return twoArgNum(lnum, args, (lh,rh) => Math.pow(lh, rh));
}, },
"TO" : function(lnum, args) { "TO" : function(lnum, args) {
/*return twoArgNum(lnum, args, (from, to) => {
var a = [];
if (from <= to) {
for (var k = from; k <= to; k++) {
a.push(k);
}
}
else {
for (var k = -from; k <= -to; k++) {
a.push(-k);
}
}
return a;
});*/
return twoArgNum(lnum, args, (from, to) => new ForGen(from, to, 1)); return twoArgNum(lnum, args, (from, to) => new ForGen(from, to, 1));
}, },
"STEP" : function(lnum, args) { "STEP" : function(lnum, args) {
/*if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var rsvArg0 = resolve(args[0]);
if (rsvArg0 === undefined) throw lang.refError(lnum, rsvArg0);
if (!Array.isArray(rsvArg0)) throw lang.illegalType(lnum, rsvArg0);
var rsvArg1 = resolve(args[1]);
if (rsvArg1 === undefined) throw lang.refError(lnum, rsvArg1);
if (isNaN(rsvArg1)) throw lang.illegalType(lnum, rsvArg1);
var a = []; var stepcnt = 0;
rsvArg0.forEach((v,i) => {
if (stepcnt == 0) a.push(v);
stepcnt = (stepcnt + 1) % rsvArg1;
});
return a;*/
return twoArg(lnum, args, (gen, step) => { return twoArg(lnum, args, (gen, step) => {
if (!(gen instanceof ForGen)) throw lang.illegalType(lnum, gen); if (!(gen instanceof ForGen)) throw lang.illegalType(lnum, gen);
return new ForGen(gen.start, gen.end, step); return new ForGen(gen.start, gen.end, step);
@@ -657,13 +640,15 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
var rsvArg = resolve(args[llll]); var rsvArg = resolve(args[llll]);
if (rsvArg === undefined && args[llll] !== undefined && args[llll].troType != "null") throw lang.refError(lnum, args[llll].troValue); if (rsvArg === undefined && args[llll] !== undefined && args[llll].troType != "null") throw lang.refError(lnum, args[llll].troValue);
//serial.println(`${lnum} PRINT ${lang.ord(llll)} arg: ${Object.entries(args[llll])}, resolved: ${rsvArg}`);
let printstr = ""; let printstr = "";
if (rsvArg === undefined) if (rsvArg === undefined || rsvArg === "")
printstr = (""); printstr = "";
else if (rsvArg.toString !== undefined) else if (rsvArg.toString !== undefined)
printstr = (rsvArg.toString()); printstr = rsvArg.toString();
else else
printstr = (rsvArg); printstr = rsvArg;
print(printstr); print(printstr);
if (TRACEON) serial.println("[BASIC.PRINT] "+printstr); if (TRACEON) serial.println("[BASIC.PRINT] "+printstr);
@@ -960,12 +945,59 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
"ABS" : function(lnum, args) { "ABS" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.abs(it)); return oneArgNum(lnum, args, (it) => Math.abs(it));
}, },
"SIN" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.sin(it));
},
"COS" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.cos(it));
},
"TAN" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.tan(it));
},
"EXP" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.exp(it));
},
"ASN" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.asin(it));
},
"ACO" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.acos(it));
},
"ATN" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.atan(it));
},
"SQR" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.sqrt(it));
},
"CBR" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.cbrt(it));
},
"SINH" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.sinh(it));
},
"COSH" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.cosh(it));
},
"TANH" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.tanh(it));
},
"LOG" : function(lnum, args) {
return oneArgNum(lnum, args, (it) => Math.log(it));
},
"RESTORE" : function(lnum, args) {
DATA_CURSOR = 0;
},
"READ" : function(lnum, args) {
let r = DATA_CONSTS.shift();
if (r === undefined) throw outOfData(lnum);
},
"OPTIONBASE" : function(lnum, args) { "OPTIONBASE" : function(lnum, args) {
return oneArgNum(lnum, args, (lh) => { return oneArgNum(lnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); if (lh != 0 && lh != 1) throw lang.syntaxfehler(line);
INDEX_BASE = lh|0; INDEX_BASE = lh|0;
}); });
}, },
"DATA" : function() { /*DATA must do nothing when encountered; they must be pre-processed*/ },
"OPTIONDEBUG" : function(lnum, args) { "OPTIONDEBUG" : function(lnum, args) {
return oneArgNum(lnum, args, (lh) => { return oneArgNum(lnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); if (lh != 0 && lh != 1) throw lang.syntaxfehler(line);
@@ -1955,7 +1987,7 @@ bF._interpretLine = function(lnum, cmd) {
if (cmd.toUpperCase().startsWith("REM")) { if (cmd.toUpperCase().startsWith("REM")) {
if (_debugprintHighestLevel) serial.println(lnum+" "+cmd); if (_debugprintHighestLevel) serial.println(lnum+" "+cmd);
return lnum+1; return undefined;
} }
// TOKENISE // TOKENISE
@@ -1972,16 +2004,19 @@ bF._interpretLine = function(lnum, cmd) {
if (_debugprintHighestLevel) serial.println("Final syntax tree:"); if (_debugprintHighestLevel) serial.println("Final syntax tree:");
if (_debugprintHighestLevel) serial.println(syntaxTree.toString()); if (_debugprintHighestLevel) serial.println(syntaxTree.toString());
return syntaxTree;
}; // end INTERPRETLINE
bF._executeAndGet = function(lnum, syntaxTree) {
// EXECUTE // EXECUTE
try { try {
var execResult = bF._executeSyntaxTree(lnum, syntaxTree, 0); var execResult = bF._executeSyntaxTree(lnum, syntaxTree, 0);
return execResult.troNextLine; return execResult.troNextLine;
} }
catch (e) { catch (e) {
serial.printerr(`ERROR on ${lnum} -- PARSE TREE:\n${syntaxTree.toString()}\nERROR CONTENTS:\n${e}`); serial.printerr(`ERROR on ${lnum} -- PARSE TREE:\n${syntaxTree.toString()}\nERROR CONTENTS:\n${e}\n${e.stack || "Stack trace undefined"}`);
throw e; throw e;
} }
}; // end INTERPRETLINE };
bF._basicList = function(v, i, arr) { bF._basicList = function(v, i, arr) {
if (i < 10) print(" "); if (i < 10) print(" ");
if (i < 100) print(" "); if (i < 100) print(" ");
@@ -2056,12 +2091,19 @@ bF.troff = function(args) {
TRACEON = false; TRACEON = false;
}; };
bF.run = function(args) { // RUN function bF.run = function(args) { // RUN function
// pre-build the trees
let programTree = [];
cmdbuf.forEach((linestr, linenum) => {
programTree[linenum] = bF._interpretLine(linenum, linestr.trim());
});
// actually execute the program
var linenumber = 1; var linenumber = 1;
var oldnum = 1; var oldnum = 1;
do { do {
if (cmdbuf[linenumber] !== undefined) { if (cmdbuf[linenumber] !== undefined) {
oldnum = linenumber; oldnum = linenumber;
linenumber = bF._interpretLine(linenumber, cmdbuf[linenumber].trim()); linenumber = bF._executeAndGet(linenumber, programTree[linenumber]);
} }
else { else {
linenumber += 1; linenumber += 1;

View File

@@ -53,7 +53,7 @@ object BasicRom : VMProgramRom {
private val contents: ByteArray private val contents: ByteArray
init { init {
val bytes = File("./assets/basic.js").readBytes() val bytes = File("./assets/bios/basic.bin").readBytes()
contents = bytes.sliceArray(0 until minOf(65536, bytes.size)) contents = bytes.sliceArray(0 until minOf(65536, bytes.size))
} }