mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-12 06:01:50 +09:00
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:
@@ -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
|
||||||
|
|||||||
134
assets/basic.js
134
assets/basic.js
@@ -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;
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user