basic: currying can be used in recursive function and QSORT works

This commit is contained in:
minjaesong
2020-12-22 15:02:38 +09:00
parent 443b5ba31d
commit d785ecf568
4 changed files with 104 additions and 37 deletions

View File

@@ -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

View File

@@ -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
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

View File

@@ -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
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
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<XS(0),XS)) # XS(0)!NIL # QSORT(FILTER([K]~>K>=XS(0),XS))
%% 100 L=7!9!4!5!2!3!1!8!6!NIL
%% 110 SL=QSORT(L)
%% 120 PRINT L:PRINT SL
%% \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 DEFUN 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))
\end{lstlisting}

View File

@@ -709,7 +709,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";