From e11038254a49a6de623eebf6e8bd1dfa434fd448 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 21 Dec 2020 14:12:09 +0900 Subject: [PATCH] basic: CURRY, TYPEOF, LEN --- assets/basic.js | 108 +++++++++++++++++++++++----------- assets/currytest.bas | 6 ++ assets/tbas/doc/tbasman.tex | 6 +- assets/tbas/doc/technical.tex | 57 ++++++++++++++++++ 4 files changed, 142 insertions(+), 35 deletions(-) create mode 100644 assets/currytest.bas create mode 100644 assets/tbas/doc/technical.tex diff --git a/assets/basic.js b/assets/basic.js index e3e862b..e04531c 100644 --- a/assets/basic.js +++ b/assets/basic.js @@ -27,7 +27,7 @@ let TRACEON = true; let DBGON = true; let DATA_CURSOR = 0; let DATA_CONSTS = []; -let DEFUNS_BUILD_DEFUNS = false; +let DEFUNS_BUILD_DEFUNS = true; if (system.maxmem() < 8192) { println("Out of memory. BASIC requires 8K or more User RAM"); @@ -238,7 +238,7 @@ let BasicAST = function() { this.astValue = undefined; this.astType = "null"; // lit, op, string, num, array, function, null, defun_args (! NOT usrdefun !) } -let literalTypes = ["string", "num", "bool", "array", "generator"]; +let literalTypes = ["string", "num", "bool", "array", "generator", "usrdefun"]; /* @param variable SyntaxTreeReturnObj, of which the 'troType' is defined in BasicAST. @return a value, if the input type if string or number, its literal value will be returned. Otherwise will search the @@ -271,6 +271,22 @@ let resolve = function(variable) { else throw Error("BasicIntpError: unknown variable with type "+variable.troType+", with value "+variable.troValue); } +let curryDefun = function(exprTree, value) { + bF._recurseApplyAST(exprTree, it => { + if (it.astType == "defun_args") { + // apply arg0 into the tree + if (it.astValue == 0) { + it.astType = JStoBASICtype(value); + it.astValue = value; + } + // shift down arg index + else { + it.astValue -= 1; + } + } + }); + return exprTree; +} let argCheckErr = function(lnum, o) { if (o === undefined || o.troType == "null") throw lang.refError(lnum, o); if (o.troType == "lit" && bStatus.vars[o.troValue] === undefined) throw lang.refError(lnum, o); @@ -503,11 +519,10 @@ bStatus.getDefunThunk = function(lnum, stmtnum, exprTree) { let argsIndex = it.astValue; let theArg = argsMap[argsIndex]; // instanceof theArg == resolved version of SyntaxTreeReturnObj - if (theArg === undefined) - throw lang.badFunctionCallFormat(lang.ord(argsIndex)+" argument was not given"); - - it.astValue = theArg; - it.astType = JStoBASICtype(theArg); + if (theArg !== undefined) { // this "forgiveness" is required to implement currying + it.astValue = theArg; + it.astType = JStoBASICtype(theArg); + } } }); @@ -1175,14 +1190,34 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length bStatus.vars[varname] = new BasicVar(keys, "array"); 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"); + curryDefun(fn, arg0); + return fn; + }); +}, +"TYPEOF" : 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) { + return oneArg(lnum, stmtnum, args, lh => { + if (lh.length === undefined) throw lang.illegalType(); + return lh.length; + }); +}, "OPTIONDEBUG" : function(lnum, stmtnum, args) { - return oneArgNum(lnum, stmtnum, args, (lh) => { + oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); DBGON = (1 == lh|0); }); }, "OPTIONTRACE" : function(lnum, stmtnum, args) { - return oneArgNum(lnum, stmtnum, args, (lh) => { + oneArgNum(lnum, stmtnum, args, (lh) => { if (lh != 0 && lh != 1) throw lang.syntaxfehler(line); TRACEON = (1 == lh|0); }); @@ -1197,10 +1232,12 @@ if no arg text were given (e.g. "10 NEXT"), args will have zero length throw lang.syntaxfehler(lnum); } }, -"RESOLVE0" : function(lnum, stmtnum, args) { +"RESOLVEVAR" : function(lnum, stmtnum, args) { if (DBGON) { return oneArg(lnum, stmtnum, args, (it) => { - println(Object.entries(it)); + let v = bStatus.vars[args[0].troValue]; + if (v === undefined) println("Undefined variable: "+args[0].troValue); + else println(`type: ${v.bvType}, value: ${v.bvLiteral}`); }); } else { @@ -1700,7 +1737,7 @@ stmt = | "(" , stmt , ")" | expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call e.g. NEXT for FOR loop *) - + expr = (* this basically blocks some funny attemps such as using DEFUN as anon function because everything is global in BASIC *) lit @@ -1711,16 +1748,16 @@ expr = (* this basically blocks some funny attemps such as using DEFUN as anon f | function_call | expr , op , expr | op_uni , expr ; - + expr_sans_asgn = ? identical to expr except errors out whenever "=" is found ? ; - + function_call = ident , "(" , [expr , {argsep , expr} , [argsep]] , ")" | ident , expr , {argsep , expr} , [argsep] ; kywd = ? words that exists on the list of predefined function that are not operators ? ; - -(* don't bother looking at these, because you already know the stuff *) - + +(* don't bother looking at these, because you already know the stuff *) + argsep = "," | ";" ; ident = alph , [digits] ; (* variable and function names *) lit = alph , [digits] | num | string ; (* ident + numbers and string literals *) @@ -1734,7 +1771,7 @@ digits = digit | digit , digits ; hexdigits = hexdigit | hexdigit , hexdigits ; bindigits = bindigit | bindigit , bindigits ; num = digits | digits , "." , [digits] | "." , digits - | ("0x"|"0X") , hexdigits + | ("0x"|"0X") , hexdigits | ("0b"|"0B") , bindigits ; (* sorry, no e-notation! *) visible = ? ASCII 0x20 to 0x7E ? ; string = '"' , (visible | visible , stringlit) , '"' ; @@ -2477,6 +2514,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 (Array.isArray(object)) return "array"; else if (!isNaN(object)) return "num"; else if (typeof object === "string" || object instanceof String) return "string"; @@ -2708,22 +2746,24 @@ bF._interpretLine = function(lnum, cmd) { // syntax tree pruning // turn UNARYMINUS(num) to -num syntaxTrees.forEach(syntaxTree => { - bF._recurseApplyAST(syntaxTree, tree => { - if (tree.astValue == "UNARYMINUS" && tree.astType == "op" && - tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" - ) { - tree.astValue = -(tree.astLeaves[0].astValue); - tree.astType = JStoBASICtype(tree.astValue); - tree.astLeaves = []; - } - else if (tree.astValue == "UNARYPLUS" && tree.astType == "op" && - tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" - ) { - tree.astValue = +(tree.astLeaves[0].astValue); - tree.astType = JStoBASICtype(tree.astValue); - tree.astLeaves = []; - } - }); + if (syntaxTree !== undefined) { + bF._recurseApplyAST(syntaxTree, tree => { + if (tree.astValue == "UNARYMINUS" && tree.astType == "op" && + tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" + ) { + tree.astValue = -(tree.astLeaves[0].astValue); + tree.astType = JStoBASICtype(tree.astValue); + tree.astLeaves = []; + } + else if (tree.astValue == "UNARYPLUS" && tree.astType == "op" && + tree.astLeaves[1] === undefined && tree.astLeaves[0] !== undefined && tree.astLeaves[0].astType == "num" + ) { + tree.astValue = +(tree.astLeaves[0].astValue); + tree.astType = JStoBASICtype(tree.astValue); + tree.astLeaves = []; + } + }); + } }); if (_debugprintHighestLevel) { diff --git a/assets/currytest.bas b/assets/currytest.bas new file mode 100644 index 0000000..111e928 --- /dev/null +++ b/assets/currytest.bas @@ -0,0 +1,6 @@ +10 DEFUN F(K,T)=ABS(T)==K +11 CF=CURRY(F,32) +20 PRINT TYPEOF(F):REM must be usrdefun +21 PRINT TYPEOF(CF):REM also must be usrdefun +30 PRINT CF(24):PRINT CF(-32) +31 REM Expected printout: false true diff --git a/assets/tbas/doc/tbasman.tex b/assets/tbas/doc/tbasman.tex index 81ae831..c187a17 100644 --- a/assets/tbas/doc/tbasman.tex +++ b/assets/tbas/doc/tbasman.tex @@ -161,6 +161,8 @@ \input{intro} \openany +\part{Language} + \chapter{Basic Concepts} \input{concepts} @@ -176,8 +178,10 @@ \chapter{Functions} \input{functions} +\part{Implementation} + \chapter{Technical Reference} -\input{implementation} +\input{technical} \chapter{Index} diff --git a/assets/tbas/doc/technical.tex b/assets/tbas/doc/technical.tex new file mode 100644 index 0000000..49946db --- /dev/null +++ b/assets/tbas/doc/technical.tex @@ -0,0 +1,57 @@ +\section{Resolving Variables} + +This section describes all use cases of \code{BasicVar}. + +When a variable is \code{resolve}d, an object with instance of \code{BasicVar} is returned. A \code{bvType} of Javascript value is determined using \code{JStoBASICtype}. + +\begin{tabulary}{\textwidth}{RLL} +Typical User Input & TYPEOF(\textbf{Q}) & Instanceof \\ +\hline +\code{\textbf{Q}=42.195} & {\ttfamily num} & \emph{primitive} \\ +\code{\textbf{Q}=42>21} & {\ttfamily boolean} & \emph{primitive} \\ +\code{\textbf{Q}="BASIC!"} & {\ttfamily string} & \emph{primitive} \\ +\code{\textbf{Q}=DIM(12)} & {\ttfamily array} & Array (JS) \\ +\code{\textbf{Q}=1 TO 9 STEP 2} & {\ttfamily generator} & ForGen \\ +\code{DEFUN \textbf{Q}(X)=X+3} & {\ttfamily usrdefun} & BasicAST \\ +\end{tabulary} + +\subsection*{Notes} +\begin{itemlist} +\item \code{TYPEOF(\textbf{Q})} is identical to the variable's bvType; the function simply returns \code{BasicVar.bvType}. +\item Do note that all resolved variables have \code{troType} of \code{Lit}, see next section for more information. +\end{itemlist} + +\section{Unresolved Values} + +Unresolved variables has JS-object of \code{troType}, with \emph{instanceof} \code{SyntaxTeeReturnObj}. Its properties are defined as follows: + +\begin{tabulary}{\textwidth}{RL} +Properties & Description \\ +\hline +{\ttfamily troType} & Type of the TRO (Tree Return Object) \\ +{\ttfamily troValue} & Value of the TRO \\ +{\ttfamily troNextLine} & Pointer to next instruction, array of: [\#line, \#statement] \\ +\end{tabulary} + +Following table shows which BASIC object can have which \code{troType}: + +\begin{tabulary}{\textwidth}{RLL} +BASIC Type & troType \\ +\hline +Any Variable & {\ttfamily lit} \\ +DEFUN'd Function & {\ttfamily function} \\ +Boolean & {\ttfamily bool} \\ +Generator & {\ttfamily generator} \\ +Array & {\ttfamily array} \\ +Number & {\ttfamily num} \\ +String & {\ttfamily string} \\ +Array Indexing & {\ttfamily internal\_arrindexing\_lazy} \\ +Assignment & {\ttfamily internal\_assignment\_object} \\ +\end{tabulary} + +\subsection*{Notes} +\begin{itemlist} +\item All type that is not \code{lit} only appear when the statement returns such values, e.g. \code{function} only get returned by DEFUN statements as the statement itself returns defined function as well as assign them to given BASIC variable. +\item As all variables will have \code{troType} of \code{lit} when they are not resolved, the property must not be used to determine the type of the variable; you must \code{resolve} it first. +\item The type string \code{function} should not appear outside of TRO and \code{astType}; if you do see them in the wild, please check your JS code because you probably meant \code{usrdefun}. +\end{itemlist}