mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-15 00:44:05 +09:00
basic: currying can be used in recursive function and QSORT works
This commit is contained in:
111
assets/basic.js
111
assets/basic.js
@@ -46,7 +46,7 @@ function isNumable(s) {
|
|||||||
return s !== undefined && (typeof s.trim == "function" && s.trim() !== "" || s.trim == undefined) && !isNaN(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 {
|
class ParserError extends Error {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
@@ -55,6 +55,13 @@ class ParserError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InternalError extends Error {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
Error.captureStackTrace(this, ParserError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let lang = {};
|
let lang = {};
|
||||||
lang.badNumberFormat = Error("Illegal number format");
|
lang.badNumberFormat = Error("Illegal number format");
|
||||||
lang.badOperatorFormat = Error("Illegal operator format");
|
lang.badOperatorFormat = Error("Illegal operator format");
|
||||||
@@ -222,7 +229,8 @@ let astToString = function(ast, depth) {
|
|||||||
("op" == ast.astType) ? String.fromCharCode(0xB1) :
|
("op" == ast.astType) ? String.fromCharCode(0xB1) :
|
||||||
("string" == ast.astType) ? String.fromCharCode(0xB6) :
|
("string" == ast.astType) ? String.fromCharCode(0xB6) :
|
||||||
("num" == ast.astType) ? String.fromCharCode(0xA2) :
|
("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) + marker+" Line "+ast.astLnum+" ("+ast.astType+")\n";
|
||||||
sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n";
|
sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n";
|
||||||
sb += l__.repeat(recDepth+1) + "value: "+ast.astValue+" (type: "+typeof ast.astValue+")\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);
|
let exprTree = cloneObject(inputTree);
|
||||||
bF._recurseApplyAST(exprTree, it => {
|
bF._recurseApplyAST(exprTree, it => {
|
||||||
if (it.astType == "defun_args") {
|
if (it.astType == "defun_args") {
|
||||||
|
if (DBGON) {
|
||||||
|
serial.println("[curryDefun] found defun_args #"+it.astValue);
|
||||||
|
serial.println(astToString(it));
|
||||||
|
}
|
||||||
// apply arg0 into the tree
|
// apply arg0 into the tree
|
||||||
if (it.astValue == 0) {
|
if (it.astValue == 0) {
|
||||||
it.astType = JStoBASICtype(value);
|
it.astType = JStoBASICtype(value);
|
||||||
it.astValue = value;
|
it.astValue = value;
|
||||||
|
|
||||||
|
if (DBGON) {
|
||||||
|
serial.println("[curryDefun] applying value "+value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// shift down arg index
|
// shift down arg index
|
||||||
else {
|
else {
|
||||||
it.astValue -= 1;
|
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};
|
return {asgnVarName: varname, asgnValue: keys};
|
||||||
},
|
},
|
||||||
"CURRY" : function(lnum, stmtnum, args) {
|
"CURRY" : function(lnum, stmtnum, args) {
|
||||||
return twoArg(lnum, stmtnum, args, (fn, arg0) => {
|
throw new InternalError("Uh-oh you're not supposed to see this!");
|
||||||
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;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
"TYPEOF" : function(lnum, stmtnum, args) {
|
"TYPEOF" : function(lnum, stmtnum, args) {
|
||||||
return oneArg(lnum, stmtnum, args, bv => {
|
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;
|
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) {
|
"OPTIONDEBUG" : 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);
|
||||||
@@ -1723,7 +1754,7 @@ bF._tokenise = function(lnum, cmd) {
|
|||||||
else if (states[k] == "n2" || states[k] == "nsep") states[k] = "num";
|
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 };
|
return { "tokens": tokens, "states": states };
|
||||||
};
|
};
|
||||||
@@ -2701,6 +2732,38 @@ 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));
|
||||||
|
|
||||||
@@ -2775,9 +2838,9 @@ bF._executeSyntaxTree = function(lnum, stmtnum, syntaxTree, recDepth) {
|
|||||||
return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1);
|
return bF._executeSyntaxTree(lnum, stmtnum, syntaxTree.astLeaves[0], recDepth + 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
serial.println(recWedge+"Parse error in "+lnum);
|
serial.println(recWedge+"Parsing error in "+lnum);
|
||||||
serial.println(recWedge+astToString(syntaxTree));
|
serial.println(recWedge+astToString(syntaxTree));
|
||||||
throw Error("Parse error");
|
throw Error("Parsing error");
|
||||||
}
|
}
|
||||||
}; // END OF bF._executeSyntaxTree
|
}; // END OF bF._executeSyntaxTree
|
||||||
// @return ARRAY of BasicAST
|
// @return ARRAY of BasicAST
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
10 DEFUN LESSER(P,X)=X<P
|
1 REM qsort [] = []
|
||||||
|
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
|
||||||
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(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
|
100 L=7!9!4!5!2!3!1!8!6!NIL
|
||||||
110 SL=QSORT(L)
|
110 SL=QSORT(L)
|
||||||
120 PRINT L:PRINT SL
|
120 PRINT L:PRINT SL
|
||||||
|
|||||||
@@ -53,20 +53,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 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
|
100 L=7!9!4!5!2!3!1!8!6!NIL
|
||||||
110 SL=QSORT(L)
|
110 SL=QSORT(L)
|
||||||
120 PRINT L:PRINT SL
|
120 PRINT L:PRINT SL
|
||||||
\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}}, 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%
|
%Uncomment this if you finally decided to support a closure%
|
||||||
%% Using \emph{closure}, the definition of \code{QSORT} can 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 QSORT=[XS]~>IF LEN(XS)<1 THEN NIL ELSE QSORT(FILTER([K]~>K<XS(0),XS)) # XS(0)!NIL # QSORT(FILTER([K]~>K>=XS(0),XS))
|
10 DEFUN QSORT(XS)=IF LEN(XS)<1 THEN NIL ELSE
|
||||||
%% 100 L=7!9!4!5!2!3!1!8!6!NIL
|
QSORT(FILTER([K]~>K<HEAD XS,TAIL XS)) # HEAD(XS)!NIL #
|
||||||
%% 110 SL=QSORT(L)
|
QSORT(FILTER([K]~>K>=HEAD XS,TAIL XS))
|
||||||
%% 120 PRINT L:PRINT SL
|
\end{lstlisting}
|
||||||
%% \end{lstlisting}
|
|
||||||
|
|||||||
@@ -709,7 +709,8 @@ let astToString = function(ast, depth) {
|
|||||||
("op" == ast.astType) ? String.fromCharCode(0xB1) :
|
("op" == ast.astType) ? String.fromCharCode(0xB1) :
|
||||||
("string" == ast.astType) ? String.fromCharCode(0xB6) :
|
("string" == ast.astType) ? String.fromCharCode(0xB6) :
|
||||||
("num" == ast.astType) ? String.fromCharCode(0xA2) :
|
("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) + marker+" Line "+ast.astLnum+" ("+ast.astType+")\n";
|
||||||
sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n";
|
sb += l__.repeat(recDepth+1) + "leaves: "+(ast.astLeaves.length)+"\n";
|
||||||
sb += l__.repeat(recDepth+1) + "value: "+ast.astValue+" (type: "+typeof ast.astValue+")\n";
|
sb += l__.repeat(recDepth+1) + "value: "+ast.astValue+" (type: "+typeof ast.astValue+")\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user