diff --git a/assets/basic.js b/assets/basic.js index 89ca808..10b3cd7 100644 --- a/assets/basic.js +++ b/assets/basic.js @@ -46,7 +46,7 @@ function isNumable(s) { return s !== undefined && (typeof s.trim == "function" && s.trim() !== "" || s.trim == undefined) && !isNaN(s); } -function cloneObject(o) = JSON.parse(JSON.stringify(o)); +function cloneObject(o) { return JSON.parse(JSON.stringify(o)); } class ParserError extends Error { constructor(...args) { @@ -55,6 +55,13 @@ class ParserError extends Error { } } +class InternalError extends Error { + constructor(...args) { + super(...args); + Error.captureStackTrace(this, ParserError); + } +} + let lang = {}; lang.badNumberFormat = Error("Illegal number format"); lang.badOperatorFormat = Error("Illegal operator format"); @@ -222,7 +229,8 @@ let astToString = function(ast, depth) { ("op" == ast.astType) ? String.fromCharCode(0xB1) : ("string" == ast.astType) ? String.fromCharCode(0xB6) : ("num" == ast.astType) ? String.fromCharCode(0xA2) : - ("array" == ast.astType) ? "[" : String.fromCharCode(0x192); + ("array" == ast.astType) ? "[" : + ("defun_args" === ast.astType) ? String.fromCharCode(0x3B4) : String.fromCharCode(0x192); sb += l__.repeat(recDepth) + marker+" Line "+ast.astLnum+" ("+ast.astType+")\n"; sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n"; sb += l__.repeat(recDepth+1) + "value: "+ast.astValue+" (type: "+typeof ast.astValue+")\n"; @@ -277,14 +285,31 @@ let curryDefun = function(inputTree, value) { let exprTree = cloneObject(inputTree); bF._recurseApplyAST(exprTree, it => { if (it.astType == "defun_args") { + if (DBGON) { + serial.println("[curryDefun] found defun_args #"+it.astValue); + serial.println(astToString(it)); + } // apply arg0 into the tree if (it.astValue == 0) { it.astType = JStoBASICtype(value); it.astValue = value; + + if (DBGON) { + serial.println("[curryDefun] applying value "+value); + } } // shift down arg index else { it.astValue -= 1; + + if (DBGON) { + serial.println("[curryDefun] decrementing arg index"); + } + } + + if (DBGON) { + serial.println("[curryDefun] after the task:"); + serial.println(astToString(it)); } } }); @@ -1220,25 +1245,7 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return {asgnVarName: varname, asgnValue: keys}; }, "CURRY" : function(lnum, stmtnum, args) { - return twoArg(lnum, stmtnum, args, (fn, arg0) => { - if (fn.astLeaves === undefined) throw lang.badFunctionCallFormat("Not a Function"); - - if (DBGON) { - serial.println("[BASIC.CURRY] currying "+args[0].troValue+" with "+arg0); - serial.println("args[1] = "+Object.entries(args[1])); - serial.println("[BASIC.CURRY] before currying:"); - serial.println(astToString(fn)) - } - - curryDefun(fn, arg0); - - if (DBGON) { - serial.println("[BASIC.CURRY] after currying:"); - serial.println(astToString(fn)); - } - - return fn; - }); + throw new InternalError("Uh-oh you're not supposed to see this!"); }, "TYPEOF" : function(lnum, stmtnum, args) { return oneArg(lnum, stmtnum, args, bv => { @@ -1253,6 +1260,30 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length return lh.length; }); }, +"HEAD" : 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) { + 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) { + 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) { + 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) { oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); @@ -1723,7 +1754,7 @@ bF._tokenise = function(lnum, cmd) { else if (states[k] == "n2" || states[k] == "nsep") states[k] = "num"; } - if (tokens.length != states.length) throw Error("BasicIntpError: size of tokens and states does not match (line: "+lnum+")"); + if (tokens.length != states.length) throw new InternalError("size of tokens and states does not match (line: "+lnum+")"); return { "tokens": tokens, "states": states }; }; @@ -2701,6 +2732,38 @@ 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)); @@ -2775,9 +2838,9 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) { return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1); } else { - serial.println(recWedge+"Parse error in "+lnum); + serial.println(recWedge+"Parsing error in "+lnum); serial.println(recWedge+astToString(syntaxTree)); - throw Error("Parse error"); + throw Error("Parsing error"); } }; // END OF bF._executeSyntaxTree // @return ARRAY of BasicAST diff --git a/assets/qsort.bas b/assets/qsort.bas index 0ed1080..cb87162 100644 --- a/assets/qsort.bas +++ b/assets/qsort.bas @@ -1,6 +1,8 @@ -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(LESSER,XS(0)),XS)) # XS(0)!NIL # QSORT(FILTER(CURRY(GTEQ,XS(0)),XS)) +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)) 100 L=7!9!4!5!2!3!1!8!6!NIL 110 SL=QSORT(L) 120 PRINT L:PRINT SL diff --git a/assets/tbas/doc/langguide.tex b/assets/tbas/doc/langguide.tex index 591a4fe..fae2151 100644 --- a/assets/tbas/doc/langguide.tex +++ b/assets/tbas/doc/langguide.tex @@ -53,20 +53,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,XS(0)),XS)) # XS(0)!NIL # QSORT(FILTER(CURRY(GTEQ,XS(0)),XS))
+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))
100 L=7!9!4!5!2!3!1!8!6!NIL
110 SL=QSORT(L)
120 PRINT L:PRINT SL
\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}}, then using curried version of \code{LESS} and \code{GTEQ} to move values lesser than pivot 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{XS(0)} simply fetches a head-elemement of \code{XS}. \code{XS(0)!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%
-%% Using \emph{closure}, the definition of \code{QSORT} can be \emph{even more cryptic}:
-%%
-%% \begin{lstlisting}
-%% 10 QSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE QSORT(FILTER([K]~>K