basic: currying is now operator & does not require special treatment on tree execution

This commit is contained in:
minjaesong
2020-12-22 17:21:42 +09:00
parent d785ecf568
commit 2f054b9c67
11 changed files with 308 additions and 308 deletions

View File

@@ -584,7 +584,7 @@ bStatus.builtin = {
if no args were given (e.g. "10 NEXT()"), args[0] will be: {troType: null, troValue: , troNextLine: 11} if no args were given (e.g. "10 NEXT()"), args[0] will be: {troType: null, troValue: , troNextLine: 11}
if no arg text were given (e.g. "10 NEXT"), args will have zero length if no arg text were given (e.g. "10 NEXT"), args will have zero length
*/ */
"=" : function(lnum, stmtnum, args) { "=" : {f:function(lnum, stmtnum, args) {
// THIS FUNCTION MUST BE COPIED TO 'INPUT' // THIS FUNCTION MUST BE COPIED TO 'INPUT'
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var troValue = args[0].troValue; var troValue = args[0].troValue;
@@ -623,8 +623,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return {asgnVarName: varname, asgnValue: rh}; return {asgnVarName: varname, asgnValue: rh};
} }
} }
}, }},
"IN" : function(lnum, stmtnum, args) { // almost same as =, but don't actually make new variable. Used by FOR statement "IN" : {f:function(lnum, stmtnum, args) { // almost same as =, but don't actually make new variable. Used by FOR statement
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var troValue = args[0].troValue; var troValue = args[0].troValue;
@@ -640,56 +640,56 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname); if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname);
return {asgnVarName: varname, asgnValue: rh}; return {asgnVarName: varname, asgnValue: rh};
} }
}, }},
"==" : function(lnum, stmtnum, args) { "==" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => lh == rh); return twoArg(lnum, stmtnum, args, (lh,rh) => lh == rh);
}, }},
"<>" : function(lnum, stmtnum, args) { "<>" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh); return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh);
}, }},
"><" : function(lnum, stmtnum, args) { "><" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh); return twoArg(lnum, stmtnum, args, (lh,rh) => lh != rh);
}, }},
"<=" : function(lnum, stmtnum, args) { "<=" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh);
}, }},
"=<" : function(lnum, stmtnum, args) { "=<" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh <= rh);
}, }},
">=" : function(lnum, stmtnum, args) { ">=" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh);
}, }},
"=>" : function(lnum, stmtnum, args) { "=>" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >= rh);
}, }},
"<" : function(lnum, stmtnum, args) { "<" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh < rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh < rh);
}, }},
">" : function(lnum, stmtnum, args) { ">" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh > rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh > rh);
}, }},
"<<" : function(lnum, stmtnum, args) { "<<" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh << rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh << rh);
}, }},
">>" : function(lnum, stmtnum, args) { ">>" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >>> rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh >>> rh);
}, }},
"UNARYMINUS" : function(lnum, stmtnum, args) { "UNARYMINUS" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => -lh); return oneArgNum(lnum, stmtnum, args, (lh) => -lh);
}, }},
"UNARYPLUS" : function(lnum, stmtnum, args) { "UNARYPLUS" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => +lh); return oneArgNum(lnum, stmtnum, args, (lh) => +lh);
}, }},
"BAND" : function(lnum, stmtnum, args) { "BAND" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh & rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh & rh);
}, }},
"BOR" : function(lnum, stmtnum, args) { "BOR" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh | rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh | rh);
}, }},
"BXOR" : function(lnum, stmtnum, args) { "BXOR" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh ^ rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh ^ rh);
}, }},
"!" : function(lnum, stmtnum, args) { // Haskell-style CONS "!" : {f:function(lnum, stmtnum, args) { // Haskell-style CONS
return twoArg(lnum, stmtnum, args, (lh,rh) => { return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (isNaN(lh)) if (isNaN(lh))
throw lang.illegalType(lnum, lh); // BASIC array is numbers only throw lang.illegalType(lnum, lh); // BASIC array is numbers only
@@ -697,8 +697,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
throw lang.illegalType(lnum, rh); throw lang.illegalType(lnum, rh);
return [lh].concat(rh); return [lh].concat(rh);
}); });
}, }},
"~" : function(lnum, stmtnum, args) { // array PUSH "~" : {f:function(lnum, stmtnum, args) { // array PUSH
return twoArg(lnum, stmtnum, args, (lh,rh) => { return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (isNaN(rh)) if (isNaN(rh))
throw lang.illegalType(lnum, rh); // BASIC array is numbers only throw lang.illegalType(lnum, rh); // BASIC array is numbers only
@@ -706,8 +706,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
throw lang.illegalType(lnum, lh); throw lang.illegalType(lnum, lh);
return lh.concat([rh]); return lh.concat([rh]);
}); });
}, }},
"#" : function(lnum, stmtnum, args) { // array CONCAT "#" : {f:function(lnum, stmtnum, args) { // array CONCAT
return twoArg(lnum, stmtnum, args, (lh,rh) => { return twoArg(lnum, stmtnum, args, (lh,rh) => {
if (!Array.isArray(rh)) if (!Array.isArray(rh))
throw lang.illegalType(lnum, rh); throw lang.illegalType(lnum, rh);
@@ -715,52 +715,52 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
throw lang.illegalType(lnum, lh); throw lang.illegalType(lnum, lh);
return lh.concat(rh); return lh.concat(rh);
}); });
}, }},
"+" : function(lnum, stmtnum, args) { // addition, string concat "+" : {f:function(lnum, stmtnum, args) { // addition, string concat
return twoArg(lnum, stmtnum, args, (lh,rh) => (!isNaN(lh) && !isNaN(rh)) ? (lh*1 + rh*1) : (lh + rh)); return twoArg(lnum, stmtnum, args, (lh,rh) => (!isNaN(lh) && !isNaN(rh)) ? (lh*1 + rh*1) : (lh + rh));
}, }},
"-" : function(lnum, stmtnum, args) { "-" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh - rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh - rh);
}, }},
"*" : function(lnum, stmtnum, args) { "*" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh * rh); return twoArgNum(lnum, stmtnum, args, (lh,rh) => lh * rh);
}, }},
"/" : function(lnum, stmtnum, args) { "/" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => { return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero; if (rh == 0) throw lang.divByZero;
return lh / rh; return lh / rh;
}); });
}, }},
"\\" : function(lnum, stmtnum, args) { // integer division, rounded towards zero "\\" : {f:function(lnum, stmtnum, args) { // integer division, rounded towards zero
return twoArgNum(lnum, stmtnum, args, (lh,rh) => { return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero; if (rh == 0) throw lang.divByZero;
return (lh / rh)|0; return (lh / rh)|0;
}); });
}, }},
"MOD" : function(lnum, stmtnum, args) { "MOD" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => { return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
if (rh == 0) throw lang.divByZero; if (rh == 0) throw lang.divByZero;
return lh % rh; return lh % rh;
}); });
}, }},
"^" : function(lnum, stmtnum, args) { "^" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (lh,rh) => { return twoArgNum(lnum, stmtnum, args, (lh,rh) => {
let r = Math.pow(lh, rh); let r = Math.pow(lh, rh);
if (isNaN(r)) throw lang.badFunctionCallFormat(); if (isNaN(r)) throw lang.badFunctionCallFormat();
if (!isFinite(r)) throw lang.divByZero; if (!isFinite(r)) throw lang.divByZero;
return r; return r;
}); });
}, }},
"TO" : function(lnum, stmtnum, args) { "TO" : {f:function(lnum, stmtnum, args) {
return twoArgNum(lnum, stmtnum, args, (from, to) => new ForGen(from, to, 1)); return twoArgNum(lnum, stmtnum, args, (from, to) => new ForGen(from, to, 1));
}, }},
"STEP" : function(lnum, stmtnum, args) { "STEP" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (gen, step) => { return twoArg(lnum, stmtnum, 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);
}); });
}, }},
"DIM" : function(lnum, stmtnum, args) { "DIM" : {f:function(lnum, stmtnum, args) {
return varArgNum(lnum, stmtnum, args, (revdims) => { return varArgNum(lnum, stmtnum, args, (revdims) => {
let dims = revdims.reverse(); let dims = revdims.reverse();
let arraydec = "Array(dims[0]).fill(0)"; let arraydec = "Array(dims[0]).fill(0)";
@@ -769,8 +769,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
} }
return eval(arraydec); return eval(arraydec);
}); });
}, }},
"PRINT" : function(lnum, stmtnum, args, seps) { "PRINT" : {f:function(lnum, stmtnum, args, seps) {
if (args.length == 0) if (args.length == 0)
println(); println();
else { else {
@@ -801,8 +801,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
} }
if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println(); if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println();
}, }},
"EMIT" : function(lnum, stmtnum, args, seps) { "EMIT" : {f:function(lnum, stmtnum, args, seps) {
if (args.length == 0) if (args.length == 0)
println(); println();
else { else {
@@ -834,14 +834,14 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
} }
if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println(); if (args[args.length - 1] !== undefined && args[args.length - 1].troType != "null") println();
}, }},
"POKE" : function(lnum, stmtnum, args) { "POKE" : {f:function(lnum, stmtnum, args) {
twoArgNum(lnum, stmtnum, args, (lh,rh) => sys.poke(lh, rh)); twoArgNum(lnum, stmtnum, args, (lh,rh) => sys.poke(lh, rh));
}, }},
"PEEK" : function(lnum, stmtnum, args) { "PEEK" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => sys.peek(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => sys.peek(lh));
}, }},
"GOTO" : function(lnum, stmtnum, args) { "GOTO" : {f:function(lnum, stmtnum, args) {
// search from gotoLabels first // search from gotoLabels first
let line = gotoLabels[args[0].troValue]; let line = gotoLabels[args[0].troValue];
// if not found, use resolved variable // if not found, use resolved variable
@@ -849,8 +849,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
if (line < 0) throw lang.syntaxfehler(lnum, line); if (line < 0) throw lang.syntaxfehler(lnum, line);
return new JumpObj(line, 0, lnum, line); return new JumpObj(line, 0, lnum, line);
}, }},
"GOSUB" : function(lnum, stmtnum, args) { "GOSUB" : {f:function(lnum, stmtnum, args) {
// search from gotoLabels first // search from gotoLabels first
let line = gotoLabels[args[0].troValue]; let line = gotoLabels[args[0].troValue];
// if not found, use resolved variable // if not found, use resolved variable
@@ -860,20 +860,20 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
bStatus.gosubStack.push([lnum, stmtnum + 1]); bStatus.gosubStack.push([lnum, stmtnum + 1]);
//println(lnum+" GOSUB into "+lh); //println(lnum+" GOSUB into "+lh);
return new JumpObj(line, 0, lnum, line); return new JumpObj(line, 0, lnum, line);
}, }},
"RETURN" : function(lnum, stmtnum, args) { "RETURN" : {f:function(lnum, stmtnum, args) {
var r = bStatus.gosubStack.pop(); var r = bStatus.gosubStack.pop();
if (r === undefined) throw lang.nowhereToReturn(lnum); if (r === undefined) throw lang.nowhereToReturn(lnum);
//println(lnum+" RETURN to "+r); //println(lnum+" RETURN to "+r);
return new JumpObj(r[0], r[1], lnum, r); return new JumpObj(r[0], r[1], lnum, r);
}, }},
"CLEAR" : function(lnum, stmtnum, args) { "CLEAR" : {f:function(lnum, stmtnum, args) {
bStatus.vars = initBvars(); bStatus.vars = initBvars();
}, }},
"PLOT" : function(lnum, stmtnum, args) { "PLOT" : {f:function(lnum, stmtnum, args) {
threeArgNum(lnum, stmtnum, args, (xpos, ypos, color) => graphics.plotPixel(xpos, ypos, color)); threeArgNum(lnum, stmtnum, args, (xpos, ypos, color) => graphics.plotPixel(xpos, ypos, color));
}, }},
"AND" : function(lnum, stmtnum, args) { "AND" : {f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var rsvArg = args.map((it) => resolve(it)); var rsvArg = args.map((it) => resolve(it));
rsvArg.forEach((v) => { rsvArg.forEach((v) => {
@@ -885,8 +885,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return it; return it;
}); });
return argum[0] && argum[1]; return argum[0] && argum[1];
}, }},
"OR" : function(lnum, stmtnum, args) { "OR" : {f:function(lnum, stmtnum, args) {
if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG);
var rsvArg = args.map((it) => resolve(it)); var rsvArg = args.map((it) => resolve(it));
rsvArg.forEach((v) => { rsvArg.forEach((v) => {
@@ -898,37 +898,37 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return it; return it;
}); });
return argum[0] || argum[1]; return argum[0] || argum[1];
}, }},
"RND" : function(lnum, stmtnum, args) { "RND" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => { return oneArgNum(lnum, stmtnum, args, (lh) => {
if (!(args.length > 0 && args[0].troValue === 0)) if (!(args.length > 0 && args[0].troValue === 0))
bStatus.rnd = Math.random();//(bStatus.rnd * 214013 + 2531011) % 16777216; // GW-BASIC does this bStatus.rnd = Math.random();//(bStatus.rnd * 214013 + 2531011) % 16777216; // GW-BASIC does this
return bStatus.rnd; return bStatus.rnd;
}); });
}, }},
"ROUND" : function(lnum, stmtnum, args) { "ROUND" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.round(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => Math.round(lh));
}, }},
"FLOOR" : function(lnum, stmtnum, args) { "FLOOR" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh));
}, }},
"INT" : function(lnum, stmtnum, args) { // synonymous to FLOOR "INT" : {f:function(lnum, stmtnum, args) { // synonymous to FLOOR
return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => Math.floor(lh));
}, }},
"CEIL" : function(lnum, stmtnum, args) { "CEIL" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => Math.ceil(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => Math.ceil(lh));
}, }},
"FIX" : function(lnum, stmtnum, args) { "FIX" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => (lh|0)); return oneArgNum(lnum, stmtnum, args, (lh) => (lh|0));
}, }},
"CHR" : function(lnum, stmtnum, args) { "CHR" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => String.fromCharCode(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => String.fromCharCode(lh));
}, }},
"TEST" : function(lnum, stmtnum, args) { "TEST" : {f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG);
return resolve(args[0]); return resolve(args[0]);
}, }},
"FOREACH" : function(lnum, stmtnum, args) { // list comprehension model "FOREACH" : {f:function(lnum, stmtnum, args) { // list comprehension model
var asgnObj = resolve(args[0]); var asgnObj = resolve(args[0]);
// type check // type check
if (asgnObj === undefined) throw lang.syntaxfehler(lnum); if (asgnObj === undefined) throw lang.syntaxfehler(lnum);
@@ -944,8 +944,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
// put the varname to forstack // put the varname to forstack
bStatus.forLnums[varname] = [lnum, stmtnum]; bStatus.forLnums[varname] = [lnum, stmtnum];
bStatus.forStack.push(varname); bStatus.forStack.push(varname);
}, }},
"FOR" : function(lnum, stmtnum, args) { // generator model "FOR" : {f:function(lnum, stmtnum, args) { // generator model
var asgnObj = resolve(args[0]); var asgnObj = resolve(args[0]);
// type check // type check
if (asgnObj === undefined) throw lang.syntaxfehler(lnum); if (asgnObj === undefined) throw lang.syntaxfehler(lnum);
@@ -962,8 +962,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
// put the varname to forstack // put the varname to forstack
bStatus.forLnums[varname] = [lnum, stmtnum]; bStatus.forLnums[varname] = [lnum, stmtnum];
bStatus.forStack.push(varname); bStatus.forStack.push(varname);
}, }},
"NEXT" : function(lnum, stmtnum, args) { "NEXT" : {f:function(lnum, stmtnum, args) {
// if no args were given // if no args were given
if (args.length == 0 || (args.length == 1 && args.troType == "null")) { if (args.length == 0 || (args.length == 1 && args.troType == "null")) {
// go to most recent FOR // go to most recent FOR
@@ -999,19 +999,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
} }
throw lang.syntaxfehler(lnum, "extra arguments for NEXT"); throw lang.syntaxfehler(lnum, "extra arguments for NEXT");
}, }},
/*"BREAKTO" : function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => {
var forVarname = bStatus.forStack.pop();
if (forVarname === undefined) {
throw lang.nextWithoutFor(lnum);
}
if (TRACEON) serial.println(`[BASIC.FOR] breaking from ${forVarname}, jump to ${lh}`);
if (lh < 0) throw lang.syntaxfehler(lnum, lh);
return new JumpObj(lh, 0, lnum, lh);
});
},*/
/* /*
10 input;"what is your name";a$ 10 input;"what is your name";a$
@@ -1049,7 +1037,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
| `----------------- | `-----------------
`----------------- `-----------------
*/ */
"INPUT" : function(lnum, stmtnum, args) { "INPUT" : {f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG);
let troValue = args[0].troValue; let troValue = args[0].troValue;
@@ -1075,85 +1063,85 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
bStatus.vars[varname] = new BasicVar(rh, type); bStatus.vars[varname] = new BasicVar(rh, type);
return {asgnVarName: varname, asgnValue: rh}; return {asgnVarName: varname, asgnValue: rh};
} }
}, }},
"END" : function(lnum, stmtnum, args) { "END" : {f:function(lnum, stmtnum, args) {
serial.println("Program terminated in "+lnum); serial.println("Program terminated in "+lnum);
return new JumpObj(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1, lnum, undefined); // GOTO far-far-away return new JumpObj(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1, lnum, undefined); // GOTO far-far-away
}, }},
"SPC" : function(lnum, stmtnum, args) { "SPC" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => " ".repeat(lh)); return oneArgNum(lnum, stmtnum, args, (lh) => " ".repeat(lh));
}, }},
"LEFT" : function(lnum, stmtnum, args) { "LEFT" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (str, len) => str.substring(0, len)); return twoArg(lnum, stmtnum, args, (str, len) => str.substring(0, len));
}, }},
"MID" : function(lnum, stmtnum, args) { "MID" : {f:function(lnum, stmtnum, args) {
return threeArg(lnum, stmtnum, args, (str, start, len) => str.substring(start-INDEX_BASE, start-INDEX_BASE+len)); return threeArg(lnum, stmtnum, args, (str, start, len) => str.substring(start-INDEX_BASE, start-INDEX_BASE+len));
}, }},
"RIGHT" : function(lnum, stmtnum, args) { "RIGHT" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (str, len) => str.substring(str.length - len, str.length)); return twoArg(lnum, stmtnum, args, (str, len) => str.substring(str.length - len, str.length));
}, }},
"SGN" : function(lnum, stmtnum, args) { "SGN" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => (it > 0) ? 1 : (it < 0) ? -1 : 0); return oneArgNum(lnum, stmtnum, args, (it) => (it > 0) ? 1 : (it < 0) ? -1 : 0);
}, }},
"ABS" : function(lnum, stmtnum, args) { "ABS" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.abs(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.abs(it));
}, }},
"SIN" : function(lnum, stmtnum, args) { "SIN" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sin(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.sin(it));
}, }},
"COS" : function(lnum, stmtnum, args) { "COS" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cos(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.cos(it));
}, }},
"TAN" : function(lnum, stmtnum, args) { "TAN" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.tan(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.tan(it));
}, }},
"EXP" : function(lnum, stmtnum, args) { "EXP" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.exp(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.exp(it));
}, }},
"ASN" : function(lnum, stmtnum, args) { "ASN" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.asin(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.asin(it));
}, }},
"ACO" : function(lnum, stmtnum, args) { "ACO" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.acos(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.acos(it));
}, }},
"ATN" : function(lnum, stmtnum, args) { "ATN" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.atan(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.atan(it));
}, }},
"SQR" : function(lnum, stmtnum, args) { "SQR" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sqrt(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.sqrt(it));
}, }},
"CBR" : function(lnum, stmtnum, args) { "CBR" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cbrt(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.cbrt(it));
}, }},
"SINH" : function(lnum, stmtnum, args) { "SINH" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.sinh(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.sinh(it));
}, }},
"COSH" : function(lnum, stmtnum, args) { "COSH" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.cosh(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.cosh(it));
}, }},
"TANH" : function(lnum, stmtnum, args) { "TANH" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.tanh(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.tanh(it));
}, }},
"LOG" : function(lnum, stmtnum, args) { "LOG" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (it) => Math.log(it)); return oneArgNum(lnum, stmtnum, args, (it) => Math.log(it));
}, }},
"RESTORE" : function(lnum, stmtnum, args) { "RESTORE" : {f:function(lnum, stmtnum, args) {
DATA_CURSOR = 0; DATA_CURSOR = 0;
}, }},
"READ" : function(lnum, stmtnum, args) { "READ" : {f:function(lnum, stmtnum, args) {
let r = DATA_CONSTS.shift(); let r = DATA_CONSTS.shift();
if (r === undefined) throw lang.outOfData(lnum); if (r === undefined) throw lang.outOfData(lnum);
}, }},
"OPTIONBASE" : function(lnum, stmtnum, args) { "OPTIONBASE" : {f:function(lnum, stmtnum, args) {
return oneArgNum(lnum, stmtnum, args, (lh) => { return oneArgNum(lnum, stmtnum, 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*/ }, "DATA" : {f:function() { /*DATA must do nothing when encountered; they must be pre-processed*/ }},
/* Syopsis: MAP function, functor /* Syopsis: MAP function, functor
*/ */
"MAP" : function(lnum, stmtnum, args) { "MAP" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fn, functor) => { return twoArg(lnum, stmtnum, args, (fn, functor) => {
// TODO test only works with DEFUN'd functions // TODO test only works with DEFUN'd functions
if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet"); if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet");
@@ -1163,11 +1151,11 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return functor.map(it => bStatus.getDefunThunk(lnum, stmtnum, fn)(lnum, stmtnum, [it])); return functor.map(it => bStatus.getDefunThunk(lnum, stmtnum, fn)(lnum, stmtnum, [it]));
}); });
}, }},
/* Synopsis: FOLD function, init_value, functor /* Synopsis: FOLD function, init_value, functor
* a function must accept two arguments, of which first argument will be an accumulator * a function must accept two arguments, of which first argument will be an accumulator
*/ */
"FOLD" : function(lnum, stmtnum, args) { "FOLD" : {f:function(lnum, stmtnum, args) {
return threeArg(lnum, stmtnum, args, (fn, init, functor) => { return threeArg(lnum, stmtnum, args, (fn, init, functor) => {
// TODO test only works with DEFUN'd functions // TODO test only works with DEFUN'd functions
if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet"); if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet");
@@ -1182,10 +1170,10 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return akku; return akku;
}); });
}, }},
/* Syopsis: FILTER function, functor /* Syopsis: FILTER function, functor
*/ */
"FILTER" : function(lnum, stmtnum, args) { "FILTER" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (fn, functor) => { return twoArg(lnum, stmtnum, args, (fn, functor) => {
// TODO test only works with DEFUN'd functions // TODO test only works with DEFUN'd functions
if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet"); if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet");
@@ -1195,18 +1183,18 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return functor.filter(it => bStatus.getDefunThunk(lnum, stmtnum, fn)(lnum, stmtnum, [it])); return functor.filter(it => bStatus.getDefunThunk(lnum, stmtnum, fn)(lnum, stmtnum, [it]));
}); });
}, }},
/* GOTO and GOSUB won't work but that's probably the best...? */ /* GOTO and GOSUB won't work but that's probably the best...? */
"DO" : function(lnum, stmtnum, args) { "DO" : {f:function(lnum, stmtnum, args) {
return args[args.length - 1]; return args[args.length - 1];
}, }},
"LABEL" : function(lnum, stmtnum, args) { "LABEL" : {f:function(lnum, stmtnum, args) {
let labelname = args[0].troValue; let labelname = args[0].troValue;
if (labelname === undefined) throw lang.syntaxfehler(lnum, "empty LABEL"); if (labelname === undefined) throw lang.syntaxfehler(lnum, "empty LABEL");
gotoLabels[labelname] = lnum; gotoLabels[labelname] = lnum;
}, }},
"ON" : function(lnum, stmtnum, args) { "ON" : {f:function(lnum, stmtnum, args) {
//args: functionName (string), testvalue (SyntaxTreeReturnObj), arg0 (SyntaxTreeReturnObj), arg1 (SyntaxTreeReturnObj), ... //args: functionName (string), testvalue (SyntaxTreeReturnObj), arg0 (SyntaxTreeReturnObj), arg1 (SyntaxTreeReturnObj), ...
if (args[2] === undefined) throw lang.syntaxfehler(lnum); if (args[2] === undefined) throw lang.syntaxfehler(lnum);
@@ -1223,14 +1211,14 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
return undefined; return undefined;
return bStatus.builtin[jmpFun](lnum, stmtnum, [jmpTarget]); return bStatus.builtin[jmpFun](lnum, stmtnum, [jmpTarget]);
}, }},
"MIN" : function(lnum, stmtnum, args) { "MIN" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => (lh > rh) ? rh : lh); return twoArg(lnum, stmtnum, args, (lh,rh) => (lh > rh) ? rh : lh);
}, }},
"MAX" : function(lnum, stmtnum, args) { "MAX" : {f:function(lnum, stmtnum, args) {
return twoArg(lnum, stmtnum, args, (lh,rh) => (lh < rh) ? rh : lh); return twoArg(lnum, stmtnum, args, (lh,rh) => (lh < rh) ? rh : lh);
}, }},
"GETKEYSDOWN" : function(lnum, stmtnum, args) { "GETKEYSDOWN" : {f:function(lnum, stmtnum, args) {
if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG); if (args.length != 1) throw lang.syntaxfehler(lnum, args.length+lang.aG);
let troValue = args[0].troValue; let troValue = args[0].troValue;
let varname = troValue.toUpperCase(); let varname = troValue.toUpperCase();
@@ -1243,60 +1231,79 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname); if (_basicConsts[varname]) throw lang.asgnOnConst(lnum, varname);
bStatus.vars[varname] = new BasicVar(keys, "array"); bStatus.vars[varname] = new BasicVar(keys, "array");
return {asgnVarName: varname, asgnValue: keys}; return {asgnVarName: varname, asgnValue: keys};
}, }},
"CURRY" : function(lnum, stmtnum, args) { "<~" : {f:function(lnum, stmtnum, args) { // CURRY operator
throw new InternalError("Uh-oh you're not supposed to see this!"); return twoArg(lnum, stmtnum, args, (fn, value) => {
}, // TODO test only works with DEFUN'd functions
"TYPEOF" : function(lnum, stmtnum, args) { if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Only works with DEFUN'd functions yet");
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" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, bv => { return oneArg(lnum, stmtnum, args, bv => {
if (bv.Type === undefined || !(bv instanceof BasicVar)) if (bv.Type === undefined || !(bv instanceof BasicVar))
return JStoBASICtype(bv); return JStoBASICtype(bv);
return bv.bvType; return bv.bvType;
}); });
}, }},
"LEN" : function(lnum, stmtnum, args) { "LEN" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => { return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined) throw lang.illegalType(); if (lh.length === undefined) throw lang.illegalType();
return lh.length; return lh.length;
}); });
}, }},
"HEAD" : function(lnum, stmtnum, args) { "HEAD" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => { return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); if (lh.length === undefined || lh.length < 1) throw lang.illegalType();
return lh[0]; return lh[0];
}); });
}, }},
"TAIL" : function(lnum, stmtnum, args) { "TAIL" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => { return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); if (lh.length === undefined || lh.length < 1) throw lang.illegalType();
return lh.slice(1, lh.length); return lh.slice(1, lh.length);
}); });
}, }},
"INIT" : function(lnum, stmtnum, args) { "INIT" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => { return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); if (lh.length === undefined || lh.length < 1) throw lang.illegalType();
return lh.slice(0, lh.length - 1); return lh.slice(0, lh.length - 1);
}); });
}, }},
"LAST" : function(lnum, stmtnum, args) { "LAST" : {f:function(lnum, stmtnum, args) {
return oneArg(lnum, stmtnum, args, lh => { return oneArg(lnum, stmtnum, args, lh => {
if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); if (lh.length === undefined || lh.length < 1) throw lang.illegalType();
return lh[lh.length - 1]; return lh[lh.length - 1];
}); });
}, }},
"OPTIONDEBUG" : function(lnum, stmtnum, args) { "OPTIONDEBUG" : {f:function(lnum, stmtnum, args) {
oneArgNum(lnum, stmtnum, args, (lh) => { oneArgNum(lnum, stmtnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); if (lh != 0 && lh != 1) throw lang.syntaxfehler(line);
DBGON = (1 == lh|0); DBGON = (1 == lh|0);
}); });
}, }},
"OPTIONTRACE" : function(lnum, stmtnum, args) { "OPTIONTRACE" : {f:function(lnum, stmtnum, args) {
oneArgNum(lnum, stmtnum, args, (lh) => { oneArgNum(lnum, stmtnum, args, (lh) => {
if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); if (lh != 0 && lh != 1) throw lang.syntaxfehler(line);
TRACEON = (1 == lh|0); TRACEON = (1 == lh|0);
}); });
}, }},
"RESOLVE" : function(lnum, stmtnum, args) { "RESOLVE" : {f:function(lnum, stmtnum, args) {
if (DBGON) { if (DBGON) {
return oneArg(lnum, stmtnum, args, (it) => { return oneArg(lnum, stmtnum, args, (it) => {
println(it); println(it);
@@ -1305,8 +1312,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
else { else {
throw lang.syntaxfehler(lnum); throw lang.syntaxfehler(lnum);
} }
}, }},
"RESOLVEVAR" : function(lnum, stmtnum, args) { "RESOLVEVAR" : {f:function(lnum, stmtnum, args) {
if (DBGON) { if (DBGON) {
return oneArg(lnum, stmtnum, args, (it) => { return oneArg(lnum, stmtnum, args, (it) => {
let v = bStatus.vars[args[0].troValue]; let v = bStatus.vars[args[0].troValue];
@@ -1317,28 +1324,28 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length
else { else {
throw lang.syntaxfehler(lnum); throw lang.syntaxfehler(lnum);
} }
}, }},
"UNRESOLVE" : function(lnum, stmtnum, args) { "UNRESOLVE" : {f:function(lnum, stmtnum, args) {
if (DBGON) { if (DBGON) {
println(args[0]); println(args[0]);
} }
else { else {
throw lang.syntaxfehler(lnum); throw lang.syntaxfehler(lnum);
} }
}, }},
"UNRESOLVE0" : function(lnum, stmtnum, args) { "UNRESOLVE0" : {f:function(lnum, stmtnum, args) {
if (DBGON) { if (DBGON) {
println(Object.entries(args[0])); println(Object.entries(args[0]));
} }
else { else {
throw lang.syntaxfehler(lnum); throw lang.syntaxfehler(lnum);
} }
} }}
}; };
Object.freeze(bStatus.builtin); Object.freeze(bStatus.builtin);
let bF = {}; let bF = {};
bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1}; bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1,"$":1};
bF._2os = {"<":1,"=":1,">":1}; bF._2os = {"<":1,"=":1,">":1,"~":1,"$":1,"*":1};
bF._uos = {"+":1,"-":1,"NOT":1,"BNOT":1}; bF._uos = {"+":1,"-":1,"NOT":1,"BNOT":1};
bF._isNum = function(code) { bF._isNum = function(code) {
return (code >= 0x30 && code <= 0x39) || code == 0x5F; return (code >= 0x30 && code <= 0x39) || code == 0x5F;
@@ -1391,12 +1398,13 @@ bF._opPrc = {
"STEP":41, "STEP":41,
"!":50,"~":51, // array CONS and PUSH "!":50,"~":51, // array CONS and PUSH
"#": 52, // array concat "#": 52, // array concat
"<~": 100, // curry operator
"~>": 101, // closure operator
"=":999, "=":999,
"IN":1000 "IN":1000
}; };
bF._opRh = {"^":1,"=":1,"!":1,"IN":1}; bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"<~":1,"~>":1};
// these names appear on executeSyntaxTree as "exceptional terms" on parsing (regular function calls are not "exceptional terms") // these names appear on executeSyntaxTree as "exceptional terms" on parsing (regular function calls are not "exceptional terms")
bF._keywords = {"IF":1,"THEN":1,"ELSE":1,"DEFUN":1};
bF._tokenise = function(lnum, cmd) { bF._tokenise = function(lnum, cmd) {
var _debugprintStateTransition = false; var _debugprintStateTransition = false;
var k; var k;
@@ -2588,7 +2596,7 @@ let JStoBASICtype = function(object) {
else if (object.arrName !== undefined) return "internal_arrindexing_lazy"; else if (object.arrName !== undefined) return "internal_arrindexing_lazy";
else if (object.asgnVarName !== undefined) return "internal_assignment_object"; else if (object.asgnVarName !== undefined) return "internal_assignment_object";
else if (object instanceof ForGen) return "generator"; else if (object instanceof ForGen) return "generator";
else if (object instanceof BasicAST) return "usrdefun"; else if (object instanceof BasicAST || object.astLeaves !== undefined) return "usrdefun";
else if (Array.isArray(object)) return "array"; else if (Array.isArray(object)) return "array";
else if (!isNaN(object)) return "num"; else if (!isNaN(object)) return "num";
else if (typeof object === "string" || object instanceof String) return "string"; else if (typeof object === "string" || object instanceof String) return "string";
@@ -2638,7 +2646,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
if (_debugExec) serial.println(recWedge+"function|operator"); if (_debugExec) serial.println(recWedge+"function|operator");
if (_debugExec) serial.println(recWedge+astToString(syntaxTree)); if (_debugExec) serial.println(recWedge+astToString(syntaxTree));
var funcName = syntaxTree.astValue.toUpperCase(); var funcName = syntaxTree.astValue.toUpperCase();
var func = bStatus.builtin[funcName]; var func = (bStatus.builtin[funcName] === undefined) ? undefined : bStatus.builtin[funcName].f;
if ("IF" == funcName) { if ("IF" == funcName) {
@@ -2653,7 +2661,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
} }
try { try {
var iftest = bStatus.builtin["TEST"](lnum, stmtnum, [testedval]); var iftest = bStatus.builtin["TEST"].f(lnum, stmtnum, [testedval]);
if (!iftest && syntaxTree.astLeaves[2] !== undefined) if (!iftest && syntaxTree.astLeaves[2] !== undefined)
return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[2], recDepth + 1); return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[2], recDepth + 1);
@@ -2724,7 +2732,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
arrays.push(bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[k], recDepth + 1)); arrays.push(bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[k], recDepth + 1));
try { try {
let r = bStatus.builtin["ON"](lnum, stmtnum, [functionName, testValue].concat(arrays)) let r = bStatus.builtin["ON"].f(lnum, stmtnum, [functionName, testValue].concat(arrays))
return new SyntaxTreeReturnObj(JStoBASICtype(r.jmpReturningValue), r.jmpReturningValue, r.jmpNext); return new SyntaxTreeReturnObj(JStoBASICtype(r.jmpReturningValue), r.jmpReturningValue, r.jmpNext);
} }
catch (e) { catch (e) {
@@ -2732,38 +2740,6 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
throw lang.errorinline(lnum, "ON error", e); throw lang.errorinline(lnum, "ON error", e);
} }
} }
else if ("CURRY" == funcName) {
if (syntaxTree.astLeaves.length !== 2) throw lang.badFunctionCallFormat();
let functionName = syntaxTree.astLeaves[0].astValue;
let functionTreeVar = bStatus.vars[functionName];
if (functionTreeVar.bvType !== "usrdefun") throw lang.badFunctionCallFormat(`'${functionName}' is not a user-defined function`);
let functionTree = functionTreeVar.bvLiteral;
let valueTree = syntaxTree.astLeaves[1];
if (DBGON) {
serial.println("[BASIC.CURRY] currying this function tree...");
serial.println(astToString(functionTree));
serial.println("[BASIC.CURRY] with this value tree:");
serial.println(astToString(valueTree));
serial.println("\n[BASIC.CURRY] now resolving the value tree...")
}
let curryingValue = resolve(bF._executeSyntaxTree(lnum, stmtnum, valueTree, recDepth + 1));
if (DBGON) {
serial.println("\n[BASIC.CURRY] I'm back from resolving the value tree!");
serial.println(`[BASIC.CURRY] It seems you want to curry '${functionName}' with '${curryingValue}' amirite? Let's do it!\n`);
}
let curriedTree = curryDefun(functionTree, curryingValue);
if (DBGON) {
serial.println("[BASIC.CURRY] Here's your curried tree:");
serial.println(astToString(curriedTree));
}
return new SyntaxTreeReturnObj("internal_lambda_curry", curriedTree, [lnum, stmtnum + 1]);
}
else { else {
var args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)); var args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1));

View File

@@ -1,4 +1,6 @@
1 KS=4!1!2!5!3!NIL 1 KS=4!1!2!5!3!NIL
10 DEFUN LESSER(P,X)=X<P 10 DEFUN LESSER(P,X)=X<P
11 KLS=CURRY(LESSER,KS(0)) 11 KLS=LESSER<~HEAD KS
20 PRINT KLS(6) 20 FOR K=1 TO 5
21 PRINT KLS(K)
22 NEXT

View File

@@ -1,6 +1,4 @@
10 DEFUN F(K,T)=ABS(T)==K 10 DEFUN F(K,T)=ABS(T)==K
11 CF=CURRY(F,32) 20 CF=F<~32
20 PRINT TYPEOF(F):REM must be usrdefun 30 PRINT CF(24) : REM will print 'false'
21 PRINT TYPEOF(CF):REM also must be usrdefun 40 PRINT CF(-32) : REM will print 'true'
30 PRINT CF(24):PRINT CF(-32)
31 REM Expected printout: false true

View File

@@ -1,4 +1,4 @@
1 XS=4!1!2!3!5!NIL 1 XS=4!1!2!3!5!NIL
10 DEFUN K(P,X)=X<P 10 DEFUN K(P,X)=X<P
20 NXS=FILTER(CURRY(K,XS(0)),XS) 20 NXS=FILTER(K<~HEAD XS,XS)
30 PRINT NXS 30 PRINT NXS

View File

@@ -2,7 +2,7 @@
2 REM qsort xs = qsort [x | x<-tail xs, x<head xs] ++ [head xs] ++ qsort [x | x<-tail xs, x>=head xs] 2 REM qsort xs = qsort [x | x<-tail xs, x<head xs] ++ [head xs] ++ qsort [x | x<-tail xs, x>=head xs]
10 DEFUN LESS(P,X)=X<P 10 DEFUN LESS(P,X)=X<P
11 DEFUN GTEQ(P,X)=X>=P 11 DEFUN GTEQ(P,X)=X>=P
12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE QSORT(FILTER(CURRY(LESS,HEAD XS),TAIL XS)) # HEAD(XS)!NIL # QSORT(FILTER(CURRY(GTEQ,HEAD XS),TAIL XS)) 12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE QSORT(FILTER(LESS<~HEAD(XS),TAIL(XS))) # HEAD(XS)!NIL # QSORT(FILTER(GTEQ<~HEAD(XS),TAIL(XS)))
100 L=7!9!4!5!2!3!1!8!6!NIL 100 L=7!9!4!5!2!3!1!8!6!NIL
110 SL=QSORT(L) 110 PRINT L
120 PRINT L:PRINT SL 120 PRINT QSORT(L)

View File

@@ -15,13 +15,13 @@ There are six basic types: \emph{number}, \emph{boolean}, \emph{string}, \emph{
\emph{Boolean} is the type of the values that is either \codebf{TRUE} or \codebf{FALSE}. Number \codebf{0} and \codebf{FALSE} makes condition \emph{false}. When used in numeric context, \codebf{FALSE} will be interpreted as 0 and \codebf{TRUE} as 1. \emph{Boolean} is the type of the values that is either \codebf{TRUE} or \codebf{FALSE}. Number \codebf{0} and \codebf{FALSE} makes condition \emph{false}. When used in numeric context, \codebf{FALSE} will be interpreted as 0 and \codebf{TRUE} as 1.
\emph{String} represents immutable\footnote{cannot be altered directly} sequences of bytes. However, you can't weave them to make something like \emph{string array}\footnote{future feature\ldots maybe\ldots? Probably not\ldots}. \emph{String} represents immutable\footnote{cannot be altered directly} sequences of bytes. However, you can't weave them to make something like \emph{string array}\footnote{future feature\ldots\ maybe\ldots? Probably not\ldots}.
\emph{Array} represents collection of numbers in 1- or more dimensions. \emph{Array} represents collection of numbers in 1- or more dimensions.
\emph{Generator} represents a value that automatically counts up/down whenever they have been called in For-Next loop. \emph{Generator} represents a value that automatically counts up/down whenever they have been called in For-Next loop.
\emph{Functions} are, well\ldots functions\footnote{This is not a closure; there is no way you can define a local- or anonymous variable in BASIC.}, especially user-defined ones. Functions are \emph{type} because some built-in functions will actually take \emph{functions} as arguments. \emph{Functions} are, well\ldots\ functions\footnote{This is not a closure; there is no way you can define a local- or anonymous variable in BASIC.}, especially user-defined ones. Functions are \emph{type} because some built-in functions will actually take \emph{functions} as arguments.
\section{Control Flow} \section{Control Flow}

View File

@@ -39,12 +39,13 @@ So what the fsck is currying? Consider the following code:
\begin{lstlisting} \begin{lstlisting}
10 DEFUN F(K,T)=ABS(T)==K 10 DEFUN F(K,T)=ABS(T)==K
20 CF=CURRY(F,32) 20 CF=F<~32
30 PRINT CF(24) : REM will print 'false' 30 PRINT CF(24) : REM will print 'false'
40 PRINT CF(-32) : REM will print 'true' 40 PRINT CF(-32) : REM will print 'true'
\end{lstlisting} \end{lstlisting}
Here, \code{CF} is a curried function of \code{F}; built-in function \code{CURRY} applies \code{32} to the first parametre of the function \code{F}, which dynamically returns a \emph{function} of \code{CF(T) = ABS(T) == 32}. The fact that \code{CURRY} returns a \emph{function} opens many possibilities, for example, you can create loads of sibling functions without making loads of duplicate codes. % NOTE: you can't use \basiccurry within \code{}
Here, \code{CF} is a curried function of \code{F}; built-in operator \code{$<\!\sim$} applies \code{32} to the first parameter of the function \code{F}, which dynamically returns a \emph{function} of \code{CF(T) = ABS(T) == 32}. The fact that Curry Operator returns a \emph{function} opens many possibilities, for example, you can create loads of sibling functions without making loads of duplicate codes.
\section[Wrapping-Up]{The Grand Unification} \section[Wrapping-Up]{The Grand Unification}
@@ -53,21 +54,21 @@ Using all the knowledge we have learned, it should be trivial\footnote{/s} to wr
\begin{lstlisting} \begin{lstlisting}
10 DEFUN LESS(P,X)=X<P 10 DEFUN LESS(P,X)=X<P
11 DEFUN GTEQ(P,X)=X>=P 11 DEFUN GTEQ(P,X)=X>=P
12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE 12 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE
QSORT(FILTER(CURRY(LESS,HEAD XS),TAIL XS)) # HEAD(XS)!NIL # QSORT(FILTER(LESS<~HEAD(XS),TAIL(XS))) # HEAD(XS)!NIL #
QSORT(FILTER(CURRY(GTEQ,HEAD XS),TAIL XS)) QSORT(FILTER(GTEQ<~HEAD(XS),TAIL(XS)))
100 L=7!9!4!5!2!3!1!8!6!NIL 100 L=7!9!4!5!2!3!1!8!6!NIL
110 SL=QSORT(L) 110 PRINT L
120 PRINT L:PRINT SL 120 PRINT QSORT(L)
\end{lstlisting} \end{lstlisting}
Line 12 implements quicksort algorithm, using \code{LESS} and \code{GTEQ} as helper functions. \code{LESS} is a user-function version of less-than operator, and \code{GTEQ} is similar. \code{QSORT} selects a pivot using head-element of array \code{XS}\footnote{stands for \emph{X's}} using \code{HEAD XS}, then using curried version of \code{LESS} and \code{GTEQ} to move values lesser than pivot \emph{minus the head element (here \code{TAIL XS} returns such array)} to the left and greater to the right, and these two sorted \emph{chunks} are recursively sorted using the same \code{QSORT} function. Currying is used to give comparison functions a pivot-value to compare against, and also because \code{FILTER} wants a \emph{function} and not an \emph{expression}. \code{HEAD XS} simply fetches a head-elemement of \code{XS}. \code{HEAD(XS)!NIL} creates a single-element array contains \code{XS(0)}. Line 12 implements quicksort algorithm, using \code{LESS} and \code{GTEQ} as helper functions. \code{LESS} is a user-function version of less-than operator, and \code{GTEQ} is similar. \code{QSORT} selects a pivot using head-element of array \code{XS}\footnote{stands for \emph{X's}} using \code{HEAD XS}, then using curried version of \code{LESS} and \code{GTEQ} to move values lesser than pivot \emph{minus the head element (here \code{TAIL XS} returns such array)} to the left and greater to the right, and these two sorted \emph{chunks} are recursively sorted using the same \code{QSORT} function. Currying is used to give comparison functions a pivot-value to compare against, and also because \code{FILTER} wants a \emph{function} and not an \emph{expression}. \code{HEAD XS} simply fetches a head-elemement of \code{XS}. \code{HEAD(XS)!NIL} creates a single-element array contains \code{XS(0)}.
%Uncomment this if you finally decided to support a closure% %Uncomment this if you finally decided to support a closure%
Using \emph{closure}, the definition of \code{QSORT} can truly be a one-liner and be \emph{even more cryptic}: %% Using \emph{closure}, the definition of \code{QSORT} can truly be a one-liner and be \emph{even more cryptic}:
%%
\begin{lstlisting} %% \begin{lstlisting}
10 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE %% 10 QSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE
QSORT(FILTER([K]~>K<HEAD XS,TAIL XS)) # HEAD(XS)!NIL # %% QSORT(FILTER([K]~>K<HEAD XS,TAIL XS)) # HEAD(XS)!NIL #
QSORT(FILTER([K]~>K>=HEAD XS,TAIL XS)) %% QSORT(FILTER([K]~>K>=HEAD XS,TAIL XS))
\end{lstlisting} %% \end{lstlisting}

View File

@@ -72,13 +72,11 @@ Furthermore, \emph{sigils} are not used in the \tbas{} and attempting to use one
Types of data recognised by \tbas{} are distinguished by some arcane magic of Javascript auto-casing mumbo-jumbo Types of data recognised by \tbas{} are distinguished by some arcane magic of Javascript auto-casing mumbo-jumbo
\begin{tabulary}{\textwidth}{RCL} \begin{tabulary}{\textwidth}{rCL}
Type & Range & Precision \\ Type & Range & Precision \\
\hline \hline \hline
String & As many as the machine can handle & \, \\ String & As many as the machine can handle & \, \\
\hline
Integer & $ \pm 2^{53}-1 $ & exact within the range \\ Integer & $ \pm 2^{53}-1 $ & exact within the range \\
\hline
Float & $ \pm 4.9406564584124654 \times 10^{-324} $ -- $ \pm 1.7976931348623157 \times 10^{308} $ & about 16 significant figures \\ Float & $ \pm 4.9406564584124654 \times 10^{-324} $ -- $ \pm 1.7976931348623157 \times 10^{308} $ & about 16 significant figures \\
\end{tabulary} \end{tabulary}
@@ -87,21 +85,22 @@ Float & $ \pm 4.9406564584124654 \times 10^{-324} $ -- $ \pm 1.7976931348623157
The order of precedence of the operators is as shown below, lower numbers means they have higher precedence (more tightly bound) The order of precedence of the operators is as shown below, lower numbers means they have higher precedence (more tightly bound)
\begin{tabulary}{\textwidth}{CCC|CCC} \begin{tabulary}{\textwidth}{cCc|cCc}
Precedence & Operators & Associativity & Precedence & Operators & Associativity \\ Order & Op & Associativity & Order & Op & Associativity \\
\hline \hline
1 & \basicexp & Right & 10 & \condensedfont{BAND} & Left \\ 1 & \basicexp & Right & 11 & \condensedfont{BXOR} & Left \\
2 & \ast \quad $/$ \quad $\backslash$ & Left & 11 & \condensedfont{BXOR} & Left \\ 2 & \ast \quad $/$ \quad $\backslash$ & Left & 12 & \condensedfont{BOR} & Left \\
3 & \condensedfont{MOD} & Left & 12 & \condensedfont{BOR} & Left \\ 3 & \condensedfont{MOD} & Left & 13 & \condensedfont{AND} & Left \\
4 & $+$ \quad $-$ & Left & 13 & \condensedfont{AND} & Left \\ 4 & $+$ \quad $-$ & Left & 14 & \condensedfont{OR} & Left \\
5 & \condensedfont{NOT} \enskip \condensedfont{BNOT} & Left & 14 & \condensedfont{OR} & Left \\ 5 & \condensedfont{NOT} \enskip \condensedfont{BNOT} & Left & 15 & \condensedfont{TO} \enskip \condensedfont{STEP} & Left \\
6 & <\!< \quad >\!> & Left & 15 & \condensedfont{TO} \enskip \condensedfont{STEP} & Left \\ 6 & <\!< \quad >\!> & Left & 16 & ! & Right \\
7 & < \enskip > \enskip <\!= \enskip =\!< \enskip >\!= \enskip =\!> & Left & 16 & ! & Right \\ 7 & < \enskip > \enskip <\!= \enskip =\!< \enskip >\!= \enskip =\!> & Left & 17 & \sim & Left\\
8 & == \quad <\!> \quad >\!< & Left & 17 & \sim & Left\\ 8 & == \quad <\!> \quad >\!< & Left & 18 & \# & Left \\
9 & \condensedfont{MIN} \enskip \condensedfont{MAX} & Left & 18 & \# & Left \\ 9 & \condensedfont{MIN} \enskip \condensedfont{MAX} & Left & 19 & \basiccurry & Right \\
& & & 19 & = & Right \\ 10 & \condensedfont{BAND} & Left & 20 & = & Right \\
\end{tabulary} \end{tabulary}
\subsubsection*{Examples} \subsubsection*{Examples}
\begin{itemlist} \begin{itemlist}
\item Exponentiation is more tightly bound than negation: \code{-1\basicexp 2 == -(1\basicexp 2) == -1} but \code{(-1)\basicexp 2 == 1} \item Exponentiation is more tightly bound than negation: \code{-1\basicexp 2 == -(1\basicexp 2) == -1} but \code{(-1)\basicexp 2 == 1}
@@ -112,7 +111,7 @@ Precedence & Operators & Associativity & Precedence & Operators & Associativity
Mathematical operators operate on expressions that returns numeric value only, except for the \code{+} operator which will take the action of string concatenation if either of the operand is non-numeric. Mathematical operators operate on expressions that returns numeric value only, except for the \code{+} operator which will take the action of string concatenation if either of the operand is non-numeric.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\ Code & Operation & Result \\
\hline \hline
\emph{x} $=$ \emph{y} & Assignment & Assigns \emph{y} into \emph{x} \\ \emph{x} $=$ \emph{y} & Assignment & Assigns \emph{y} into \emph{x} \\
@@ -146,7 +145,7 @@ $-$ \emph{x} & Unary Minus & Negative value of \emph{x} \\
Comparison operator can operate on numeric and string operands. String operands will be automatically converted to numeric value if they can be; if one operand is numeric and other is non-numeric string, the former will be converted to string value. Comparison operator can operate on numeric and string operands. String operands will be automatically converted to numeric value if they can be; if one operand is numeric and other is non-numeric string, the former will be converted to string value.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\ Code & Operation & Result \\
\hline \hline
\emph{x} == \emph{y} & Equal & True if \emph{x} equals \emph{y} \\ \emph{x} == \emph{y} & Equal & True if \emph{x} equals \emph{y} \\
@@ -169,7 +168,7 @@ When comparing strings, the ordering is as follows:
Bitwise operators operate on unsigned integers only. Floating points are truncated\footnote{truncated towards zero} to integers. Bitwise operators operate on unsigned integers only. Floating points are truncated\footnote{truncated towards zero} to integers.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\ Code & Operation & Result \\
\hline \hline
\emph{x} <\!< \emph{y} & Bitwise Shift Left & Shifts entire bits of \emph{x} by \emph{y} \\ \emph{x} <\!< \emph{y} & Bitwise Shift Left & Shifts entire bits of \emph{x} by \emph{y} \\
@@ -184,7 +183,7 @@ Code & Operation & Result \\
Boolean operators operate on boolean values. If one of the operand is not boolean, it will be cast to appropriate boolean value. See \ref{valuesandtypes} for casting rules. Boolean operators operate on boolean values. If one of the operand is not boolean, it will be cast to appropriate boolean value. See \ref{valuesandtypes} for casting rules.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\ Code & Operation & Result \\
\hline \hline
\condensedfont{NOT} \emph{x} & Logical negation & True if \emph{x} is false and vice versa \\ \condensedfont{NOT} \emph{x} & Logical negation & True if \emph{x} is false and vice versa \\
@@ -196,7 +195,7 @@ Code & Operation & Result \\
Generator operators operate on numeric values and generators to create and modify a generator. Generator operators operate on numeric values and generators to create and modify a generator.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Result \\ Code & Result \\
\hline \hline
\emph{x} \condensedfont{TO} \emph{y} & Creates an generator that counts from \emph{x} to \emph{y} \\ \emph{x} \condensedfont{TO} \emph{y} & Creates an generator that counts from \emph{x} to \emph{y} \\
@@ -207,7 +206,7 @@ Code & Result \\
Array operators operate on arrays and numeric values. Array operators operate on arrays and numeric values.
\begin{tabulary}{\textwidth}{CLL} \begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\ Code & Operation & Result \\
\hline \hline
\emph{x} $!$ \emph{y} & Cons & Prepends a value of \emph{x} into an array of \emph{y} \\ \emph{x} $!$ \emph{y} & Cons & Prepends a value of \emph{x} into an array of \emph{y} \\
@@ -217,6 +216,17 @@ Code & Operation & Result \\
Arbitrary arrays can be constructed using empty-array constant \codebf{NIL}. Arbitrary arrays can be constructed using empty-array constant \codebf{NIL}.
\subsection{Function Operators}
Function operators operate on functions and some values.
\begin{tabulary}{\textwidth}{clL}
Code & Operation & Result \\
\hline
\emph{f} \basiccurry{} \emph{x} & Curry & Apply \emph{x} into the first parameter of the function \emph{f} \\
%{[}\emph{x},\,\emph{y}\ldots{]} \basicclosure{} \emph{e} & Closure & Creates a closure (anonymous function) from one or more parameters \emph{x},\,\emph{y}\ldots\ and an expression \emph{e} \\
\end{tabulary}
\section{Syntax In EBNF} \section{Syntax In EBNF}
If you're \emph{that} into the language theory of computer science, texts above are just waste of bytes/inks/pixel-spaces/whatever; this little section should be more than enough! If you're \emph{that} into the language theory of computer science, texts above are just waste of bytes/inks/pixel-spaces/whatever; this little section should be more than enough!

View File

@@ -22,7 +22,7 @@ If \code{TRUTH\_VALUE} is truthy, executes \code{TRUE\_EXPRESSION}; if \code{TRU
Jumps to \code{INDEX\_EXPRESSION}-th line number in the argements. If \code{INDEX\_EXPRESSION} is outside of range of the arguments, no jump will be performed. Jumps to \code{INDEX\_EXPRESSION}-th line number in the argements. If \code{INDEX\_EXPRESSION} is outside of range of the arguments, no jump will be performed.
\subsubsection*{Parametres} \subsubsection*{parameters}
\begin{itemlist} \begin{itemlist}
\item \code{LINEn} can be a number, numeric expression (aka equations) or a line label. \item \code{LINEn} can be a number, numeric expression (aka equations) or a line label.
@@ -39,7 +39,7 @@ With the aid of other statements\footnote{Actually, only the IF is useful, unles
Oh, and you can define your own function, in traditional \code{DEF FN} sense. Oh, and you can define your own function, in traditional \code{DEF FN} sense.
\subsubsection*{Parametres} \subsubsection*{parameters}
\begin{itemlist} \begin{itemlist}
\item \code{NAME} must be a valid variable name \item \code{NAME} must be a valid variable name

View File

@@ -51,11 +51,15 @@
\newenvironment{itemlist}{\vspace{0pt}\itemize}{\enditemize} \newenvironment{itemlist}{\vspace{0pt}\itemize}{\enditemize}
%% Idioms %% %% Idioms %%
\hyphenation{Java-scr-ipt} \hyphenation{Java-script}
\hyphenation{ECMA-scr-ipt} \hyphenation{ECMA-script}
\newcommand\forceindent{\hskip1.5em} \newcommand\forceindent{\hskip1.5em}
%% BASIC operators %%
\newcommand\basicexp{\raisebox{0.25ex}{\scriptsize\wedge}} \newcommand\basicexp{\raisebox{0.25ex}{\scriptsize\wedge}}
\newcommand\basiccurry{$<\!\sim$}
\newcommand\basicclosure{$\sim\!>$}
% Title styling % Title styling
\pretitle{\begin{flushright}} \pretitle{\begin{flushright}}
@@ -71,6 +75,9 @@
\setbeforesubsecskip{\mytextsize} \setbeforesubsecskip{\mytextsize}
\setbeforesubsubsecskip{\mytextsize} \setbeforesubsubsecskip{\mytextsize}
% extra space for table
\setlength{\extrarowheight}{0.166ex}
% chapter title -- no now page after % chapter title -- no now page after
\renewcommand\chapterheadstart{} % kill the drop \renewcommand\chapterheadstart{} % kill the drop
\renewcommand\afterchapternum{\vskip 0.5em} % space between number and title \renewcommand\afterchapternum{\vskip 0.5em} % space between number and title

View File

@@ -748,10 +748,12 @@ bF._opPrc = {
"STEP":41, "STEP":41,
"!":50,"~":51, // array CONS and PUSH "!":50,"~":51, // array CONS and PUSH
"#": 52, // array concat "#": 52, // array concat
"<~": 100, // curry operator
"~>": 101, // closure operator
"=":999, "=":999,
"IN":1000 "IN":1000
}; };
bF._opRh = {"^":1,"=":1,"!":1,"IN":1}; bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"<~":1,"~>":1};
let bStatus = {}; let bStatus = {};
bStatus.builtin = {}; bStatus.builtin = {};
["PRINT","NEXT","SPC","CHR","ROUND","SQR","RND","GOTO","GOSUB","DEFUN","FOR","MAP"].forEach(w=>{ bStatus.builtin[w] = 1 }); ["PRINT","NEXT","SPC","CHR","ROUND","SQR","RND","GOTO","GOSUB","DEFUN","FOR","MAP"].forEach(w=>{ bStatus.builtin[w] = 1 });
@@ -826,10 +828,14 @@ let states15 = ["lit","lit","paren","lit","sep","num","op","num","paren"];
let tokens16 = ["DEFUN","KA","(","X",")","=","IF","X",">","2","THEN","DO","(","PRINT","(","HAI",")",";","PRINT","(","X",")",")","ELSE","DO","(","PRINT","(","BYE",")",";","PRINT","(","X",")",")"]; let tokens16 = ["DEFUN","KA","(","X",")","=","IF","X",">","2","THEN","DO","(","PRINT","(","HAI",")",";","PRINT","(","X",")",")","ELSE","DO","(","PRINT","(","BYE",")",";","PRINT","(","X",")",")"];
let states16 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren"]; let states16 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren","lit","lit","paren","lit","paren","qot","paren","sep","lit","paren","lit","paren","paren"];
// FILTER(FN<~HEAD XS, TAIL XS)
let tokens17 = ["FILTER","(","FN","<~","HEAD","XS",",","TAIL","XS",")"];
let states17 = ["lit","paren","lit","op","lit","lit","sep","lit","lit","paren"];
try { try {
let trees = bF._parseTokens(lnum, let trees = bF._parseTokens(lnum,
tokens16, tokens17,
states16 states17
); );
trees.forEach((t,i) => { trees.forEach((t,i) => {
serial.println("\nParsed Statement #"+(i+1)); serial.println("\nParsed Statement #"+(i+1));