From 2f054b9c6781bc6aa64ab0c2c627dc7a10293ac4 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 22 Dec 2020 17:21:42 +0900 Subject: [PATCH] basic: currying is now operator & does not require special treatment on tree execution --- assets/basic.js | 480 ++++++++++++++++----------------- assets/curryarray.bas | 6 +- assets/currytest.bas | 8 +- assets/filtercurry.bas | 2 +- assets/qsort.bas | 6 +- assets/tbas/doc/concepts.tex | 4 +- assets/tbas/doc/langguide.tex | 29 +- assets/tbas/doc/langref.tex | 54 ++-- assets/tbas/doc/statements.tex | 4 +- assets/tbas/doc/tbasman.tex | 11 +- assets/tbas/parser_wip.js | 12 +- 11 files changed, 308 insertions(+), 308 deletions(-) diff --git a/assets/basic.js b/assets/basic.js index 10b3cd7..222c5f4 100644 --- a/assets/basic.js +++ b/assets/basic.js @@ -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 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' if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); 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}; } } -}, -"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); 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); return {asgnVarName: varname, asgnValue: rh}; } -}, -"==" : function(lnum, stmtnum, args) { +}}, +"==" : {f:function(lnum, stmtnum, args) { 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); -}, -"><" : function(lnum, stmtnum, args) { +}}, +"><" : {f:function(lnum, stmtnum, args) { 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); -}, -"=<" : function(lnum, stmtnum, args) { +}}, +"=<" : {f:function(lnum, stmtnum, args) { 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); -}, -"=>" : function(lnum, stmtnum, args) { +}}, +"=>" : {f:function(lnum, stmtnum, args) { 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); -}, -">" : function(lnum, stmtnum, args) { +}}, +">" : {f:function(lnum, stmtnum, args) { 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); -}, -">>" : function(lnum, stmtnum, args) { +}}, +">>" : {f:function(lnum, stmtnum, args) { 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); -}, -"UNARYPLUS" : function(lnum, stmtnum, args) { +}}, +"UNARYPLUS" : {f:function(lnum, stmtnum, args) { 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); -}, -"BOR" : function(lnum, stmtnum, args) { +}}, +"BOR" : {f:function(lnum, stmtnum, args) { 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); -}, -"!" : function(lnum, stmtnum, args) { // Haskell-style CONS +}}, +"!" : {f:function(lnum, stmtnum, args) { // Haskell-style CONS return twoArg(lnum, stmtnum, args, (lh,rh) => { if (isNaN(lh)) 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); return [lh].concat(rh); }); -}, -"~" : function(lnum, stmtnum, args) { // array PUSH +}}, +"~" : {f:function(lnum, stmtnum, args) { // array PUSH return twoArg(lnum, stmtnum, args, (lh,rh) => { if (isNaN(rh)) 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); return lh.concat([rh]); }); -}, -"#" : function(lnum, stmtnum, args) { // array CONCAT +}}, +"#" : {f:function(lnum, stmtnum, args) { // array CONCAT return twoArg(lnum, stmtnum, args, (lh,rh) => { if (!Array.isArray(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); 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)); -}, -"-" : function(lnum, stmtnum, args) { +}}, +"-" : {f:function(lnum, stmtnum, args) { 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); -}, -"/" : function(lnum, stmtnum, args) { +}}, +"/" : {f:function(lnum, stmtnum, args) { return twoArgNum(lnum, stmtnum, args, (lh,rh) => { if (rh == 0) throw lang.divByZero; 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) => { if (rh == 0) throw lang.divByZero; return (lh / rh)|0; }); -}, -"MOD" : function(lnum, stmtnum, args) { +}}, +"MOD" : {f:function(lnum, stmtnum, args) { return twoArgNum(lnum, stmtnum, args, (lh,rh) => { if (rh == 0) throw lang.divByZero; return lh % rh; }); -}, -"^" : function(lnum, stmtnum, args) { +}}, +"^" : {f:function(lnum, stmtnum, args) { return twoArgNum(lnum, stmtnum, args, (lh,rh) => { let r = Math.pow(lh, rh); if (isNaN(r)) throw lang.badFunctionCallFormat(); if (!isFinite(r)) throw lang.divByZero; 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)); -}, -"STEP" : function(lnum, stmtnum, args) { +}}, +"STEP" : {f:function(lnum, stmtnum, args) { return twoArg(lnum, stmtnum, args, (gen, step) => { if (!(gen instanceof ForGen)) throw lang.illegalType(lnum, gen); 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) => { let dims = revdims.reverse(); 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); }); -}, -"PRINT" : function(lnum, stmtnum, args, seps) { +}}, +"PRINT" : {f:function(lnum, stmtnum, args, seps) { if (args.length == 0) println(); 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(); -}, -"EMIT" : function(lnum, stmtnum, args, seps) { +}}, +"EMIT" : {f:function(lnum, stmtnum, args, seps) { if (args.length == 0) println(); 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(); -}, -"POKE" : function(lnum, stmtnum, args) { +}}, +"POKE" : {f:function(lnum, stmtnum, args) { 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)); -}, -"GOTO" : function(lnum, stmtnum, args) { +}}, +"GOTO" : {f:function(lnum, stmtnum, args) { // search from gotoLabels first let line = gotoLabels[args[0].troValue]; // 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); return new JumpObj(line, 0, lnum, line); -}, -"GOSUB" : function(lnum, stmtnum, args) { +}}, +"GOSUB" : {f:function(lnum, stmtnum, args) { // search from gotoLabels first let line = gotoLabels[args[0].troValue]; // 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]); //println(lnum+" GOSUB into "+lh); return new JumpObj(line, 0, lnum, line); -}, -"RETURN" : function(lnum, stmtnum, args) { +}}, +"RETURN" : {f:function(lnum, stmtnum, args) { var r = bStatus.gosubStack.pop(); if (r === undefined) throw lang.nowhereToReturn(lnum); //println(lnum+" RETURN to "+r); return new JumpObj(r[0], r[1], lnum, r); -}, -"CLEAR" : function(lnum, stmtnum, args) { +}}, +"CLEAR" : {f:function(lnum, stmtnum, args) { 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)); -}, -"AND" : function(lnum, stmtnum, args) { +}}, +"AND" : {f:function(lnum, stmtnum, args) { if (args.length != 2) throw lang.syntaxfehler(lnum, args.length+lang.aG); var rsvArg = args.map((it) => resolve(it)); rsvArg.forEach((v) => { @@ -885,8 +885,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return it; }); 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); var rsvArg = args.map((it) => resolve(it)); 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 argum[0] || argum[1]; -}, -"RND" : function(lnum, stmtnum, args) { +}}, +"RND" : {f:function(lnum, stmtnum, args) { return oneArgNum(lnum, stmtnum, args, (lh) => { if (!(args.length > 0 && args[0].troValue === 0)) bStatus.rnd = Math.random();//(bStatus.rnd * 214013 + 2531011) % 16777216; // GW-BASIC does this return bStatus.rnd; }); -}, -"ROUND" : function(lnum, stmtnum, args) { +}}, +"ROUND" : {f:function(lnum, stmtnum, args) { 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)); -}, -"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)); -}, -"CEIL" : function(lnum, stmtnum, args) { +}}, +"CEIL" : {f:function(lnum, stmtnum, args) { 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)); -}, -"CHR" : function(lnum, stmtnum, args) { +}}, +"CHR" : {f:function(lnum, stmtnum, args) { 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); 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]); // type check 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 bStatus.forLnums[varname] = [lnum, stmtnum]; bStatus.forStack.push(varname); -}, -"FOR" : function(lnum, stmtnum, args) { // generator model +}}, +"FOR" : {f:function(lnum, stmtnum, args) { // generator model var asgnObj = resolve(args[0]); // type check 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 bStatus.forLnums[varname] = [lnum, stmtnum]; bStatus.forStack.push(varname); -}, -"NEXT" : function(lnum, stmtnum, args) { +}}, +"NEXT" : {f:function(lnum, stmtnum, args) { // if no args were given if (args.length == 0 || (args.length == 1 && args.troType == "null")) { // go to most recent FOR @@ -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"); -}, -/*"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$ @@ -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); 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); return {asgnVarName: varname, asgnValue: rh}; } -}, -"END" : function(lnum, stmtnum, args) { +}}, +"END" : {f:function(lnum, stmtnum, args) { serial.println("Program terminated in "+lnum); return new JumpObj(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER - 1, lnum, undefined); // GOTO far-far-away -}, -"SPC" : function(lnum, stmtnum, args) { +}}, +"SPC" : {f:function(lnum, stmtnum, args) { 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)); -}, -"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)); -}, -"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)); -}, -"SGN" : function(lnum, stmtnum, args) { +}}, +"SGN" : {f:function(lnum, stmtnum, args) { 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)); -}, -"SIN" : function(lnum, stmtnum, args) { +}}, +"SIN" : {f:function(lnum, stmtnum, args) { 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)); -}, -"TAN" : function(lnum, stmtnum, args) { +}}, +"TAN" : {f:function(lnum, stmtnum, args) { 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)); -}, -"ASN" : function(lnum, stmtnum, args) { +}}, +"ASN" : {f:function(lnum, stmtnum, args) { 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)); -}, -"ATN" : function(lnum, stmtnum, args) { +}}, +"ATN" : {f:function(lnum, stmtnum, args) { 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)); -}, -"CBR" : function(lnum, stmtnum, args) { +}}, +"CBR" : {f:function(lnum, stmtnum, args) { 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)); -}, -"COSH" : function(lnum, stmtnum, args) { +}}, +"COSH" : {f:function(lnum, stmtnum, args) { 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)); -}, -"LOG" : function(lnum, stmtnum, args) { +}}, +"LOG" : {f:function(lnum, stmtnum, args) { return oneArgNum(lnum, stmtnum, args, (it) => Math.log(it)); -}, -"RESTORE" : function(lnum, stmtnum, args) { +}}, +"RESTORE" : {f:function(lnum, stmtnum, args) { DATA_CURSOR = 0; -}, -"READ" : function(lnum, stmtnum, args) { +}}, +"READ" : {f:function(lnum, stmtnum, args) { let r = DATA_CONSTS.shift(); if (r === undefined) throw lang.outOfData(lnum); -}, -"OPTIONBASE" : function(lnum, stmtnum, args) { +}}, +"OPTIONBASE" : {f:function(lnum, stmtnum, args) { return oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); INDEX_BASE = lh|0; }); -}, -"DATA" : 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 */ -"MAP" : function(lnum, stmtnum, args) { +"MAP" : {f:function(lnum, stmtnum, args) { return twoArg(lnum, stmtnum, args, (fn, functor) => { // TODO test only works with DEFUN'd functions 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])); }); -}, +}}, /* Synopsis: FOLD function, init_value, functor * 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) => { // TODO test only works with DEFUN'd functions 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; }); -}, +}}, /* Syopsis: FILTER function, functor */ -"FILTER" : function(lnum, stmtnum, args) { +"FILTER" : {f:function(lnum, stmtnum, args) { return twoArg(lnum, stmtnum, args, (fn, functor) => { // TODO test only works with DEFUN'd functions 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])); }); -}, +}}, /* 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]; -}, -"LABEL" : function(lnum, stmtnum, args) { +}}, +"LABEL" : {f:function(lnum, stmtnum, args) { let labelname = args[0].troValue; if (labelname === undefined) throw lang.syntaxfehler(lnum, "empty LABEL"); gotoLabels[labelname] = lnum; -}, -"ON" : function(lnum, stmtnum, args) { +}}, +"ON" : {f:function(lnum, stmtnum, args) { //args: functionName (string), testvalue (SyntaxTreeReturnObj), arg0 (SyntaxTreeReturnObj), arg1 (SyntaxTreeReturnObj), ... 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 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); -}, -"MAX" : function(lnum, stmtnum, args) { +}}, +"MAX" : {f:function(lnum, stmtnum, args) { 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); let troValue = args[0].troValue; 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); bStatus.vars[varname] = new BasicVar(keys, "array"); return {asgnVarName: varname, asgnValue: keys}; -}, -"CURRY" : function(lnum, stmtnum, args) { - throw new InternalError("Uh-oh you're not supposed to see this!"); -}, -"TYPEOF" : function(lnum, stmtnum, args) { +}}, +"<~" : {f:function(lnum, stmtnum, args) { // CURRY operator + return twoArg(lnum, stmtnum, args, (fn, value) => { + // TODO test only works with DEFUN'd functions + 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 => { if (bv.Type === undefined || !(bv instanceof BasicVar)) return JStoBASICtype(bv); return bv.bvType; }); -}, -"LEN" : function(lnum, stmtnum, args) { +}}, +"LEN" : {f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, lh => { if (lh.length === undefined) throw lang.illegalType(); return lh.length; }); -}, -"HEAD" : function(lnum, stmtnum, args) { +}}, +"HEAD" : {f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, lh => { if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); return lh[0]; }); -}, -"TAIL" : function(lnum, stmtnum, args) { +}}, +"TAIL" : {f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, lh => { if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); return lh.slice(1, lh.length); }); -}, -"INIT" : function(lnum, stmtnum, args) { +}}, +"INIT" : {f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, lh => { if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); return lh.slice(0, lh.length - 1); }); -}, -"LAST" : function(lnum, stmtnum, args) { +}}, +"LAST" : {f:function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, lh => { if (lh.length === undefined || lh.length < 1) throw lang.illegalType(); return lh[lh.length - 1]; }); -}, -"OPTIONDEBUG" : function(lnum, stmtnum, args) { +}}, +"OPTIONDEBUG" : {f:function(lnum, stmtnum, args) { oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); DBGON = (1 == lh|0); }); -}, -"OPTIONTRACE" : function(lnum, stmtnum, args) { +}}, +"OPTIONTRACE" : {f:function(lnum, stmtnum, args) { oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); TRACEON = (1 == lh|0); }); -}, -"RESOLVE" : function(lnum, stmtnum, args) { +}}, +"RESOLVE" : {f:function(lnum, stmtnum, args) { if (DBGON) { return oneArg(lnum, stmtnum, args, (it) => { println(it); @@ -1305,8 +1312,8 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length else { throw lang.syntaxfehler(lnum); } -}, -"RESOLVEVAR" : function(lnum, stmtnum, args) { +}}, +"RESOLVEVAR" : {f:function(lnum, stmtnum, args) { if (DBGON) { return oneArg(lnum, stmtnum, args, (it) => { 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 { throw lang.syntaxfehler(lnum); } -}, -"UNRESOLVE" : function(lnum, stmtnum, args) { +}}, +"UNRESOLVE" : {f:function(lnum, stmtnum, args) { if (DBGON) { println(args[0]); } else { throw lang.syntaxfehler(lnum); } -}, -"UNRESOLVE0" : function(lnum, stmtnum, args) { +}}, +"UNRESOLVE0" : {f:function(lnum, stmtnum, args) { if (DBGON) { println(Object.entries(args[0])); } else { throw lang.syntaxfehler(lnum); } -} +}} }; Object.freeze(bStatus.builtin); let bF = {}; -bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1}; -bF._2os = {"<":1,"=":1,">":1}; +bF._1os = {"!":1,"~":1,"#":1,"<":1,"=":1,">":1,"*":1,"+":1,"-":1,"/":1,"^":1,":":1,"$":1}; +bF._2os = {"<":1,"=":1,">":1,"~":1,"$":1,"*":1}; bF._uos = {"+":1,"-":1,"NOT":1,"BNOT":1}; bF._isNum = function(code) { return (code >= 0x30 && code <= 0x39) || code == 0x5F; @@ -1391,12 +1398,13 @@ bF._opPrc = { "STEP":41, "!":50,"~":51, // array CONS and PUSH "#": 52, // array concat + "<~": 100, // curry operator + "~>": 101, // closure operator "=":999, "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") -bF._keywords = {"IF":1,"THEN":1,"ELSE":1,"DEFUN":1}; bF._tokenise = function(lnum, cmd) { var _debugprintStateTransition = false; var k; @@ -2588,7 +2596,7 @@ let JStoBASICtype = function(object) { else if (object.arrName !== undefined) return "internal_arrindexing_lazy"; else if (object.asgnVarName !== undefined) return "internal_assignment_object"; 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 (!isNaN(object)) return "num"; 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+astToString(syntaxTree)); var funcName = syntaxTree.astValue.toUpperCase(); - var func = bStatus.builtin[funcName]; + var func = (bStatus.builtin[funcName] === undefined) ? undefined : bStatus.builtin[funcName].f; if ("IF" == funcName) { @@ -2653,7 +2661,7 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { } try { - var iftest = bStatus.builtin["TEST"](lnum, stmtnum, [testedval]); + var iftest = bStatus.builtin["TEST"].f(lnum, stmtnum, [testedval]); if (!iftest && syntaxTree.astLeaves[2] !== undefined) 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)); 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); } catch (e) { @@ -2732,38 +2740,6 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { 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 { var args = syntaxTree.astLeaves.map(it => bF._executeSyntaxTree(lnum, stmtnum, it, recDepth + 1)); diff --git a/assets/curryarray.bas b/assets/curryarray.bas index 69bfc3c..206925b 100644 --- a/assets/curryarray.bas +++ b/assets/curryarray.bas @@ -1,4 +1,6 @@ 1 KS=4!1!2!5!3!NIL 10 DEFUN LESSER(P,X)=X

=head xs] 10 DEFUN LESS(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 -110 SL=QSORT(L) -120 PRINT L:PRINT SL +110 PRINT L +120 PRINT QSORT(L) diff --git a/assets/tbas/doc/concepts.tex b/assets/tbas/doc/concepts.tex index 9897416..070f92e 100644 --- a/assets/tbas/doc/concepts.tex +++ b/assets/tbas/doc/concepts.tex @@ -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{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{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} diff --git a/assets/tbas/doc/langguide.tex b/assets/tbas/doc/langguide.tex index fae2151..b6a2bb9 100644 --- a/assets/tbas/doc/langguide.tex +++ b/assets/tbas/doc/langguide.tex @@ -39,12 +39,13 @@ So what the fsck is currying? Consider the following code: \begin{lstlisting} 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' 40 PRINT CF(-32) : REM will print 'true' \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} @@ -53,21 +54,21 @@ Using all the knowledge we have learned, it should be trivial\footnote{/s} to wr \begin{lstlisting} 10 DEFUN LESS(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 -110 SL=QSORT(L) -120 PRINT L:PRINT SL +110 PRINT L +120 PRINT QSORT(L) \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)}. %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}: - -\begin{lstlisting} -10 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE - QSORT(FILTER([K]~>KK>=HEAD XS,TAIL XS)) -\end{lstlisting} +%% Using \emph{closure}, the definition of \code{QSORT} can truly be a one-liner and be \emph{even more cryptic}: +%% +%% \begin{lstlisting} +%% 10 QSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE +%% QSORT(FILTER([K]~>KK>=HEAD XS,TAIL XS)) +%% \end{lstlisting} diff --git a/assets/tbas/doc/langref.tex b/assets/tbas/doc/langref.tex index e88d7d6..1273c83 100644 --- a/assets/tbas/doc/langref.tex +++ b/assets/tbas/doc/langref.tex @@ -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 -\begin{tabulary}{\textwidth}{RCL} +\begin{tabulary}{\textwidth}{rCL} Type & Range & Precision \\ -\hline \hline +\hline String & As many as the machine can handle & \, \\ -\hline 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 \\ \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) -\begin{tabulary}{\textwidth}{CCC|CCC} -Precedence & Operators & Associativity & Precedence & Operators & Associativity \\ +\begin{tabulary}{\textwidth}{cCc|cCc} +Order & Op & Associativity & Order & Op & Associativity \\ \hline -1 & \basicexp & Right & 10 & \condensedfont{BAND} & Left \\ -2 & \ast \quad $/$ \quad $\backslash$ & Left & 11 & \condensedfont{BXOR} & Left \\ -3 & \condensedfont{MOD} & Left & 12 & \condensedfont{BOR} & Left \\ -4 & $+$ \quad $-$ & Left & 13 & \condensedfont{AND} & Left \\ -5 & \condensedfont{NOT} \enskip \condensedfont{BNOT} & Left & 14 & \condensedfont{OR} & Left \\ -6 & <\!< \quad >\!> & Left & 15 & \condensedfont{TO} \enskip \condensedfont{STEP} & Left \\ -7 & < \enskip > \enskip <\!= \enskip =\!< \enskip >\!= \enskip =\!> & Left & 16 & ! & Right \\ -8 & == \quad <\!> \quad >\!< & Left & 17 & \sim & Left\\ -9 & \condensedfont{MIN} \enskip \condensedfont{MAX} & Left & 18 & \# & Left \\ - & & & 19 & = & Right \\ +1 & \basicexp & Right & 11 & \condensedfont{BXOR} & Left \\ +2 & \ast \quad $/$ \quad $\backslash$ & Left & 12 & \condensedfont{BOR} & Left \\ +3 & \condensedfont{MOD} & Left & 13 & \condensedfont{AND} & Left \\ +4 & $+$ \quad $-$ & Left & 14 & \condensedfont{OR} & Left \\ +5 & \condensedfont{NOT} \enskip \condensedfont{BNOT} & Left & 15 & \condensedfont{TO} \enskip \condensedfont{STEP} & Left \\ +6 & <\!< \quad >\!> & Left & 16 & ! & Right \\ +7 & < \enskip > \enskip <\!= \enskip =\!< \enskip >\!= \enskip =\!> & Left & 17 & \sim & Left\\ +8 & == \quad <\!> \quad >\!< & Left & 18 & \# & Left \\ +9 & \condensedfont{MIN} \enskip \condensedfont{MAX} & Left & 19 & \basiccurry & Right \\ +10 & \condensedfont{BAND} & Left & 20 & = & Right \\ \end{tabulary} + \subsubsection*{Examples} \begin{itemlist} \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Operation & Result \\ \hline \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Operation & Result \\ \hline \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Operation & Result \\ \hline \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Operation & Result \\ \hline \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Result \\ \hline \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. -\begin{tabulary}{\textwidth}{CLL} +\begin{tabulary}{\textwidth}{clL} Code & Operation & Result \\ \hline \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}. +\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} 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! diff --git a/assets/tbas/doc/statements.tex b/assets/tbas/doc/statements.tex index 2d67bbe..23244e6 100644 --- a/assets/tbas/doc/statements.tex +++ b/assets/tbas/doc/statements.tex @@ -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. -\subsubsection*{Parametres} +\subsubsection*{parameters} \begin{itemlist} \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. -\subsubsection*{Parametres} +\subsubsection*{parameters} \begin{itemlist} \item \code{NAME} must be a valid variable name diff --git a/assets/tbas/doc/tbasman.tex b/assets/tbas/doc/tbasman.tex index d5ef957..33c0f79 100644 --- a/assets/tbas/doc/tbasman.tex +++ b/assets/tbas/doc/tbasman.tex @@ -51,11 +51,15 @@ \newenvironment{itemlist}{\vspace{0pt}\itemize}{\enditemize} %% Idioms %% -\hyphenation{Java-scr-ipt} -\hyphenation{ECMA-scr-ipt} +\hyphenation{Java-script} +\hyphenation{ECMA-script} \newcommand\forceindent{\hskip1.5em} + +%% BASIC operators %% \newcommand\basicexp{\raisebox{0.25ex}{\scriptsize\wedge}} +\newcommand\basiccurry{$<\!\sim$} +\newcommand\basicclosure{$\sim\!>$} % Title styling \pretitle{\begin{flushright}} @@ -71,6 +75,9 @@ \setbeforesubsecskip{\mytextsize} \setbeforesubsubsecskip{\mytextsize} +% extra space for table +\setlength{\extrarowheight}{0.166ex} + % chapter title -- no now page after \renewcommand\chapterheadstart{} % kill the drop \renewcommand\afterchapternum{\vskip 0.5em} % space between number and title diff --git a/assets/tbas/parser_wip.js b/assets/tbas/parser_wip.js index 2f5613a..8cdc65a 100644 --- a/assets/tbas/parser_wip.js +++ b/assets/tbas/parser_wip.js @@ -748,10 +748,12 @@ bF._opPrc = { "STEP":41, "!":50,"~":51, // array CONS and PUSH "#": 52, // array concat + "<~": 100, // curry operator + "~>": 101, // closure operator "=":999, "IN":1000 }; -bF._opRh = {"^":1,"=":1,"!":1,"IN":1}; +bF._opRh = {"^":1,"=":1,"!":1,"IN":1,"<~":1,"~>":1}; let bStatus = {}; bStatus.builtin = {}; ["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 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 { let trees = bF._parseTokens(lnum, - tokens16, - states16 + tokens17, + states17 ); trees.forEach((t,i) => { serial.println("\nParsed Statement #"+(i+1));