mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-09 22:54:03 +09:00
moving everything neatly into the assets directory
This commit is contained in:
3102
assets/disk0/tbas/basic.js
Normal file
3102
assets/disk0/tbas/basic.js
Normal file
File diff suppressed because it is too large
Load Diff
3
assets/disk0/tbas/doc/bibliography.tex
Normal file
3
assets/disk0/tbas/doc/bibliography.tex
Normal file
@@ -0,0 +1,3 @@
|
||||
\begin{itemlist}
|
||||
\item \emph{PC-BASIC 2.0.3 Documentation} \\ \url{https://robhagemans.github.io/pcbasic/doc/2.0/}
|
||||
\end{itemlist}
|
||||
38
assets/disk0/tbas/doc/concepts.tex
Normal file
38
assets/disk0/tbas/doc/concepts.tex
Normal file
@@ -0,0 +1,38 @@
|
||||
\quad
|
||||
\chapterprecishere{``Caution! Under no circumstances confuse the adjective \emph{basic} with the noun \emph{BASIC}, except under confusing circumstances!''\par\raggedleft --- \textup{\tbas\ Reference Manual, \theedition}\footnote{Original quotation is from \emph{The INTERCAL Programming Language Reference Manual} by Donald R. Woods and James M. Lyon}}
|
||||
|
||||
This chapter describes the basic concepts of the \tbas\ language.
|
||||
|
||||
|
||||
\section{Values and Types}
|
||||
\label{valuesandtypes}
|
||||
|
||||
BASIC is a \emph{Dynamically Typed Language}, which means variables do not know which group they should barge in; only values of the variable do. In fact, there is no type definition in the language: \emph{we do want our variables to feel themselves awkward.}
|
||||
|
||||
There are six basic types: \emph{number}, \emph{boolean}, \emph{string}, \emph{array}, \emph{generator} and \emph{function}.
|
||||
|
||||
\emph{Number} represents real (double-precision floating-point or \emph{actually rational}) numbers. Operations on numbers follow the same rules of the underlying virtual machine\footnote{if you are not a computer person, just disregard}, and such machines must follow the IEEE 754 standard\footnote{ditto.}.
|
||||
|
||||
\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{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.
|
||||
|
||||
\section{Control Flow}
|
||||
|
||||
A program is executed starting with its lowest line number. Statements on a line are executed from left to right. When all Statements are finished execution, next lowest line number will be executed. Control flow functions can modify this normal flow.
|
||||
|
||||
You can dive into other line in the middle of the program with \code{GOTO}. The program flow will continue normally at the new line \emph{and it will never know ya just did that}.
|
||||
|
||||
If you want less insane jumping, \code{GOSUB} is used to jump to a subroutine. Subroutine is a little section of a code that serves as a tiny program inside of a program. \code{GOSUB} will remember from which statement in the line you have came from, and will return your program flow to that line when \code{RETURN} statement is encountered. (of course, if \code{RETURN} is used without \code{GOSUB}, program will raise some error) Do note that while you can reserve some portion of a program line as a \code{subroutine}, \tbas\ will not provide local variables and whatnot as all variables in \tbas\ are global, and you can just \code{GOTO} out of a subroutine to anywhere you desire and wreak havoc \emph{if you really want to}.
|
||||
|
||||
The \code{ON} statement provides alternative branching construct. You can enter multiple line numbers and let your variable (or mathematical expression) to choose which index of line numbers to \code{GOTO}- or \code{GOSUB} into.
|
||||
|
||||
The \code{IF-THEN-ELSE} lets you to conditionally select which of the two branches to execute.
|
||||
|
||||
The \code{FOR-NEXT} lets you to loop a portion of a program while automatically counting your chosen variable up or down.
|
||||
3
assets/disk0/tbas/doc/facmap.bas
Normal file
3
assets/disk0/tbas/doc/facmap.bas
Normal file
@@ -0,0 +1,3 @@
|
||||
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
|
||||
20 K=MAP FAC, 1 TO 10
|
||||
30 PRINT K
|
||||
4
assets/disk0/tbas/doc/facrec.bas
Normal file
4
assets/disk0/tbas/doc/facrec.bas
Normal file
@@ -0,0 +1,4 @@
|
||||
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
|
||||
20 FOR K=1 TO 6
|
||||
30 PRINT FAC(K)
|
||||
40 NEXT
|
||||
4
assets/disk0/tbas/doc/fib.bas
Normal file
4
assets/disk0/tbas/doc/fib.bas
Normal file
@@ -0,0 +1,4 @@
|
||||
10 DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
|
||||
20 FOR K=1 TO 12
|
||||
30 PRINT FIB(K);" ";
|
||||
40 NEXT
|
||||
241
assets/disk0/tbas/doc/functions.tex
Normal file
241
assets/disk0/tbas/doc/functions.tex
Normal file
@@ -0,0 +1,241 @@
|
||||
\label{functions}
|
||||
|
||||
Functions are a form of expression that may taks input arguments surrounded by parentheses. Most of the traditional BASIC \emph{statements} that does not return a value are \emph{functions} in \tbas , and like those, while \tbas\ functions can be called without parentheses, it is highly \emph{discouraged} because of the ambiguities in syntax. \textbf{Always use parentheses on function call!}
|
||||
|
||||
\section{Mathematical}
|
||||
|
||||
\subsection{ABS}
|
||||
\codeline{Y \textbf{= ABS(}X\textbf{)}}\par
|
||||
Returns absolute value of \code{X}.
|
||||
\subsection{ACO}
|
||||
\codeline{Y \textbf{= ACO(}X\textbf{)}}\par
|
||||
Returns inverse cosine of \code{X}.
|
||||
\subsection{ASN}
|
||||
\codeline{Y \textbf{= ASN(}X\textbf{)}}\par
|
||||
Returns inverse sine of \code{X}.
|
||||
\subsection{ATN}
|
||||
\codeline{Y \textbf{= ATN(}X\textbf{)}}\par
|
||||
Returns inverse tangent of \code{X}.
|
||||
\subsection{CBR}
|
||||
\codeline{Y \textbf{= CBR(}X\textbf{)}}\par
|
||||
Returns cubic root of \code{X}.
|
||||
\subsection{CEIL}
|
||||
\codeline{Y \textbf{= CEIL(}X\textbf{)}}\par
|
||||
Returns integer value of \code{X}, truncated towards positive infinity.
|
||||
\subsection{COS}
|
||||
\codeline{Y \textbf{= COS(}X\textbf{)}}\par
|
||||
Returns cosine of \code{X}.
|
||||
\subsection{COSH}
|
||||
\codeline{Y \textbf{= COSH(}X\textbf{)}}\par
|
||||
Returns hyperbolic cosine of \code{X}.
|
||||
\subsection{EXP}
|
||||
\codeline{Y \textbf{= EXP(}X\textbf{)}}\par
|
||||
Returns exponential of \code{X}, i.e. $e^X$.
|
||||
\subsection{FIX}
|
||||
\codeline{Y \textbf{= FIX(}X\textbf{)}}\par
|
||||
Returns integer value of \code{X}, truncated towards zero.
|
||||
\subsection{FLOOR, INT}
|
||||
\codeline{Y \textbf{= FLOOR(}X\textbf{)}}
|
||||
\codeline{Y \textbf{= INT(}X\textbf{)}}\par
|
||||
Returns integer value of \code{X}, truncated towards negative infinity.
|
||||
\subsection{LEN}
|
||||
\codeline{Y \textbf{= LEN(}X\textbf{)}}\par
|
||||
Returns length of \code{X}. \code{X} can be either a string or an array.
|
||||
\subsection{LOG}
|
||||
\codeline{Y \textbf{= LOG(}X\textbf{)}}\par
|
||||
Returns natural logarithm of \code{X}.
|
||||
\subsection{ROUND}
|
||||
\codeline{Y \textbf{= ROUND(}X\textbf{)}}\par
|
||||
Returns closest integer value of \code{X}, rounding towards positive infinity.
|
||||
\subsection{RND}
|
||||
\codeline{Y \textbf{= RND(}X\textbf{)}}\par
|
||||
Returns a random number within the range of $[0..1)$. If \code{X} is zero, previous random number will be returned; otherwise new random number will be returned.
|
||||
\subsection{SIN}
|
||||
\codeline{Y \textbf{= SIN(}X\textbf{)}}\par
|
||||
Returns sine of \code{X}.
|
||||
\subsection{SINH}
|
||||
\codeline{Y \textbf{= SINH(}X\textbf{)}}\par
|
||||
Returns hyperbolic sine of \code{X}.
|
||||
\subsection{SGN}
|
||||
\codeline{Y \textbf{= SGN(}X\textbf{)}}\par
|
||||
Returns sign of \code{X}: 1 for positive, -1 for negative, 0 otherwise.
|
||||
\subsection{SQR}
|
||||
\codeline{Y \textbf{= SQR(}X\textbf{)}}\par
|
||||
Returns square root of \code{X}.
|
||||
\subsection{TAN}
|
||||
\codeline{Y \textbf{= TAN(}X\textbf{)}}\par
|
||||
Returns tangent of \code{X}.
|
||||
\subsection{TANH}
|
||||
\codeline{Y \textbf{= TANH(}X\textbf{)}}\par
|
||||
Returns hyperbolic tangent of \code{X}.
|
||||
|
||||
\section{Input}
|
||||
|
||||
\subsection{CIN}
|
||||
\codeline{S \textbf{= CIN()}}\par
|
||||
Waits for the user input and returns it.
|
||||
\subsection{DATA}
|
||||
\codeline{\textbf{DATA} CONST0 [\textbf{,} CONST1]\ldots}\par
|
||||
Adds data that can be read by \code{READ} function. \code{DATA} declarations need not be reacheable in the program flow.
|
||||
\subsection{DGET}
|
||||
\codeline{S \textbf{= DGET()}}\par
|
||||
Fetches a data declared from \code{DATA} statements and returns it, incrementing the \code{DATA} position.
|
||||
\subsection{DIM}
|
||||
\codeline{Y \textbf{= DIM(}X\textbf{)}}\par
|
||||
Returns array with size of \code{X}, all filled with zero.
|
||||
\subsection{GETKEYSDOWN}
|
||||
\codeline{K \textbf{= GETKEYSDOWN()}}\par
|
||||
Stores array that contains keycode of keys held down into the given variable.\par
|
||||
Actual keycode and the array length depends on the machine: in \thismachine , array length will be fixed to 8. For the list of available keycodes, see \ref{implementation}.
|
||||
\subsection{INPUT}
|
||||
\codeline{\textbf{INPUT} VARIABLE}\par
|
||||
Prints out \code{? } to the console and waits for user input. Input can be any length and terminated with return key. The input will be stored to given variable.\par
|
||||
This behaviour is to keep the compatibility with the traditional BASIC. For function-like usage, use \code{CIN} instead.
|
||||
\subsection{READ}
|
||||
\codeline{\textbf{READ} VARIABLE}\par
|
||||
Assigns data declared from \code{DATA} statements to given variable. Reading starts at the current \code{DATA} position, and the data position will be incremented by one. The position is reset to the zero by the \code{RUN} command.\par
|
||||
This behaviour is to keep the compatibility with the traditional BASIC. For function-like usage, use \code{DGET} instead.
|
||||
|
||||
\section{Output}
|
||||
|
||||
\subsection{EMIT}
|
||||
\codeline{\textbf{EMIT(}EXPR [\{\textbf{,}|\textbf{;}\} EXPR]\ldots\textbf{)}}\par
|
||||
Prints out characters corresponding to given number on the code page being used.\par
|
||||
\code{EXPR} is numeric expression.
|
||||
\subsection{PRINT}
|
||||
\codeline{\textbf{PRINT(}EXPR [\{\textbf{,}|\textbf{;}\} EXPR]\ldots\textbf{)}}\par
|
||||
Prints out given string expressions.\par
|
||||
\code{EXPR} is a string, numeric expression, or array.\par
|
||||
\code{PRINT} is one of the few function that differentiates two style of argument separator: \codebf{;} will simply concatenate two expressions (unlike traditional BASIC, numbers will not have surrounding spaces), \codebf{,} tabulates the expressions.
|
||||
|
||||
\section{Program Manipulation}
|
||||
|
||||
\subsection{CLEAR}
|
||||
\codeline{\textbf{CLEAR}}\par
|
||||
Clears all declared variables.
|
||||
\subsection{END}
|
||||
\codeline{\textbf{END}}\par
|
||||
Stops program execution and returns control to the user.
|
||||
\subsection{FOR}
|
||||
\codeline{\textbf{FOR} LOOPVAR \textbf{=} START \textbf{TO} STOP [\textbf{STEP} STEP]}
|
||||
\codeline{\textbf{FOR} LOOPVAR \textbf{=} GENERATOR}\par
|
||||
Starts a \code{FOR-NEXT} loop.\par
|
||||
Initially, \code{LOOPVAR} is set to \code{START} then statements between the \code{FOR} statement and corresponding \code{NEXT} statements are executed and \code{LOOPVAR} is incremented by \code{STEP}, or by 1 if \code{STEP} is not specified. The program flow will continue to loop around until \code{LOOPVAR} is outside the range of \code{START}--\code{STOP}. The value of the \code{LOOPVAR} is equal to \code{STOP}$+$\code{STEP} when the looping finishes.
|
||||
\subsection{FOREACH}
|
||||
\codeline{\textbf{FOREACH} LOOPVAR \textbf{IN} ARRAY}\par
|
||||
Same as \code{FOR} but fetches \code{LOOPVAR} from given \code{ARRAY}.
|
||||
\subsection{GOSUB}
|
||||
\codeline{\textbf{GOSUB} LINENUM}\par
|
||||
Jumps to a subroutine at \code{LINENUM}. The next \code{RETURN} statements makes program flow to jump back to the statement after the \code{GOSUB}.\par
|
||||
\code{LINENUM} can be either a numeric expression or a Label.
|
||||
\subsection{GOTO}
|
||||
\codeline{\textbf{GOTO} LINENUM}\par
|
||||
Jumps to \code{LINENUM}.\par
|
||||
\code{LINENUM} can be either a numeric expression or a Label.
|
||||
\subsection{LABEL}
|
||||
\codeline{\textbf{LABEL} NAME}\par
|
||||
Puts a name onto the line numeber the statement is located. Jumping to the \code{NAME}
|
||||
\subsubsection*{Notes}
|
||||
\begin{itemlist}
|
||||
\item \code{NAME} must be a valid variable name.
|
||||
\end{itemlist}
|
||||
\subsection{NEXT}
|
||||
\codeline{\textbf{NEXT}}\par
|
||||
Iterates \code{FOR-NEXT} loop and increments the loop variable from the most recent \code{FOR} statement and jumps to that statement.
|
||||
\subsection{RESTORE}
|
||||
\codeline{\textbf{RESTORE}}\par
|
||||
Resets the \code{DATA} pointer.
|
||||
\subsection{RETURN}
|
||||
\codeline{\textbf{RETURN}}\par
|
||||
Returns from the \code{GOSUB} statement.
|
||||
|
||||
\section{String Manipulation}
|
||||
|
||||
\subsection{CHR}
|
||||
\codeline{CHAR \textbf{= CHR(}X\textbf{)}}\par
|
||||
Returns the character with code point of \code{X}. Code point is a numeric expression in the range of $[0-255]$.
|
||||
\subsection{LEFT}
|
||||
\codeline{SUBSTR \textbf{= LEFT(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
|
||||
Returns the leftmost \code{NUM\_CHARS} characters of \code{STR}.
|
||||
\subsection{MID}
|
||||
\codeline{SUBSTR \textbf{= MID(}STR \textbf{,} POSITION \textbf{,} LENGTH\textbf{)}}\par
|
||||
Returns a substring of \code{STR} starting at \code{POSITION} with specified \code{LENGTH}.\par
|
||||
When \code{OPTIONBASE 1} is specified, the position starts from 1; otherwise it will start from 0.
|
||||
\subsection{RIGHT}
|
||||
\codeline{SUBSTR \textbf{= RIGHT(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
|
||||
Returns the rightmost \code{NUM\_CHARS} characters of \code{STR}.
|
||||
\subsection{SPC}
|
||||
\codeline{STR \textbf{= SPC(}STR \textbf{,} NUM\_CHARS\textbf{)}}\par
|
||||
Returns a string of \code{NUM\_CHARS} spaces.
|
||||
|
||||
\section{Array Manipulation}
|
||||
|
||||
\subsection{HEAD}
|
||||
\codeline{K \textbf{= HEAD(}ARRAY\textbf{)}}\par
|
||||
Returns the head element of the given array.
|
||||
\subsection{INIT}
|
||||
\codeline{K \textbf{= INIT(}ARRAY\textbf{)}}\par
|
||||
Returns the new array that has its last element removed.
|
||||
\subsection{LAST}
|
||||
\codeline{K \textbf{= LAST(}ARRAY\textbf{)}}\par
|
||||
Returns the last element of the given array.
|
||||
\subsection{TAIL}
|
||||
\codeline{K \textbf{= TAIL(}ARRAY\textbf{)}}\par
|
||||
Returns the new array that has its head element removed.
|
||||
|
||||
\section{Graphics}
|
||||
|
||||
\subsection{PLOT}
|
||||
\codeline{\textbf{PLOT(}X\_POS \textbf{,} Y\_POS \textbf{,} COLOUR\textbf{)}}\par
|
||||
Plots a pixel to the framebuffer of the display, at XY-position of \code{X\_POS} and \code{Y\_POS}, with colour of \code{COLOUR}.\par
|
||||
Top-left corner of the pixel will be 1 if \code{OPTIONBASE 1} is specified; otherwise it will be 0.
|
||||
|
||||
\section{Meta}
|
||||
|
||||
\subsection{OPTIONBASE}
|
||||
\codeline{\textbf{OPTIONBASE} \{\textbf{0}|\textbf{1}\}}\par
|
||||
Specifies at which number the array/string/pixel indices begin.
|
||||
\subsection{OPTIONDEBUG}
|
||||
\codeline{\textbf{OPTIONDEBUG} \{\textbf{0}|\textbf{1}\}}\par
|
||||
Specifies whether or not the debugging messages should be printed out. The messages will be printed out to the \emph{serial debugging console}, or to the stdout.
|
||||
\subsection{OPTIONTRACE}
|
||||
\codeline{\textbf{OPTIONTRACE} \{\textbf{0}|\textbf{1}\}}\par
|
||||
Specifies whether or not the line numbers should be printed out. The messages will be printed out to the \emph{serial debugging console}, or to the stdout.
|
||||
|
||||
\section{System}
|
||||
|
||||
\subsection{PEEK}
|
||||
\codeline{BYTE \textbf{= PEEK(}MEM\_ADDR\textbf{)}}\par
|
||||
Returns whatever the value stored in the \code{MEM\_ADDR} of the Scratchpad Memory.\par
|
||||
Address mirroring, illegal access, etc. are entirely up to the virtual machine which the BASIC interpreter is running on.
|
||||
\subsection{POKE}
|
||||
\codeline{\textbf{POKE(}MEM\_ADDR \textbf{,} BYTE\textbf{)}}\par
|
||||
Puts a \code{BYTE} into the \code{MEM\_ADDR} of the Scratchpad Memory.
|
||||
|
||||
\section{Higher-order Function}
|
||||
|
||||
\subsection{DO}
|
||||
\codeline{\textbf{DO(}EXPR0 [\textbf{;} EXPR1]\ldots\textbf{)}}\par
|
||||
Executes \code{EXPRn}s sequentially.
|
||||
\subsection{FILTER}
|
||||
\codeline{NEWLIST \textbf{= FILTER(}FUNCTION \textbf{,} ITERABLE\textbf{)}}\par
|
||||
Returns an array of values from the \code{ITERABLE} that passes the given function. i.e. values that makes \code{FUNCTION(VALUE\_FROM\_ITERABLE)} true.
|
||||
\subsubsection*{Parameters}
|
||||
\begin{itemlist}
|
||||
\item \code{FUNCTION} is a user-defined function with single parameter.
|
||||
\item \code{ITERABLE} is either an array or a generator.
|
||||
\end{itemlist}
|
||||
\subsection{FOLD}
|
||||
\codeline{NEWVALUE \textbf{= FOLD(}FUNCTION \textbf{,} INIT\_VALUE \textbf{,} ITERABLE\textbf{)}}\par
|
||||
Iteratively applies given function with accumulator and the value from the \code{ITERABLE}, returning the final accumulator. Accumulator will be set to \code{INIT\_VALUE} before iterating over the iterable. In the first execution, the accumulator will be set to \code{ACC=FUNCTION(ACC,ITERABLE(0))}, and the execution will continue to remaining values within the iterable until all values are consumed. The \code{ITERABLE} will not be modified after the execution.
|
||||
\subsubsection*{Parameters}
|
||||
\begin{itemlist}
|
||||
\item \code{FUNCTION} is a user-defined function with two parameters: first parameter being accumulator and second being a value.
|
||||
\end{itemlist}
|
||||
\subsection{MAP}
|
||||
\codeline{NEWLIST \textbf{= MAP(}FUNCTION \textbf{,} ITERABLE\textbf{)}}\par
|
||||
Applies given function onto the every element in the iterable, and returns an array that contains such items. i.e. returns tranformation of \code{ITERABLE} of which the transformation is \code{FUNCTION}. The \code{ITERABLE} will not be modified after the execution.
|
||||
\subsubsection*{Parameters}
|
||||
\begin{itemlist}
|
||||
\item \code{FUNCTION} is a user-defined function with single parameter.
|
||||
\end{itemlist}
|
||||
123
assets/disk0/tbas/doc/implementation.tex
Normal file
123
assets/disk0/tbas/doc/implementation.tex
Normal file
@@ -0,0 +1,123 @@
|
||||
\label{implementation}
|
||||
|
||||
This chapter explains implementation details of \tbas\ running on \thismachine.
|
||||
|
||||
\section{Keycodes}
|
||||
|
||||
This is a keycodes recognised by LibGDX, a framework that \thismachine\ runs on.
|
||||
|
||||
\begin{longtable}{*{2}{m{\textwidth}}}\hline
|
||||
\endfirsthead
|
||||
\endhead
|
||||
|
||||
\endfoot
|
||||
\hline
|
||||
\endlastfoot
|
||||
\centering
|
||||
\begin{tabulary}{\textwidth}{rl}
|
||||
Key & Code \\
|
||||
\hline
|
||||
\ttfamily{1} & 8 \\
|
||||
\ttfamily{2} & 9 \\
|
||||
\ttfamily{3} & 10 \\
|
||||
\ttfamily{4} & 11 \\
|
||||
\ttfamily{5} & 12 \\
|
||||
\ttfamily{6} & 13 \\
|
||||
\ttfamily{7} & 14 \\
|
||||
\ttfamily{8} & 15 \\
|
||||
\ttfamily{9} & 16 \\
|
||||
\ttfamily{0} & 17 \\
|
||||
$\hookleftarrow$ & 66 \\
|
||||
\condensedfont{BkSp} & 67 \\
|
||||
\condensedfont{Tab} & 61 \\
|
||||
\ttfamily{`} & 68 \\
|
||||
\ttfamily{'} & 75 \\
|
||||
\ttfamily{;} & 43 \\
|
||||
\ttfamily{,} & 55 \\
|
||||
\ttfamily{.} & 56 \\
|
||||
\ttfamily{/} & 76 \\
|
||||
\ttfamily{[}\hspace*{0.083em} & 71 \\
|
||||
\ttfamily{]}\hspace*{-0.083em} & 72 \\
|
||||
\ttfamily{-} & 69 \\
|
||||
\end{tabulary}
|
||||
\begin{tabulary}{\textwidth}{rl}
|
||||
Key & Code \\
|
||||
\hline
|
||||
\ttfamily{+} & 70 \\
|
||||
\ttfamily{A} & 29 \\
|
||||
\ttfamily{B} & 30 \\
|
||||
\ttfamily{C} & 31 \\
|
||||
\ttfamily{D} & 32\\
|
||||
\ttfamily{E} & 33 \\
|
||||
\ttfamily{F} & 34 \\
|
||||
\ttfamily{G} & 35 \\
|
||||
\ttfamily{H} & 36 \\
|
||||
\ttfamily{I} & 37 \\
|
||||
\ttfamily{J} & 38 \\
|
||||
\ttfamily{K} & 39 \\
|
||||
\ttfamily{L} & 40 \\
|
||||
\ttfamily{M} & 41 \\
|
||||
\ttfamily{N} & 42 \\
|
||||
\ttfamily{O} & 43 \\
|
||||
\ttfamily{P} & 44 \\
|
||||
\ttfamily{Q} & 45 \\
|
||||
\ttfamily{R} & 46 \\
|
||||
\ttfamily{S} & 47 \\
|
||||
\ttfamily{T} & 48 \\
|
||||
\ttfamily{U} & 49 \\
|
||||
\end{tabulary}
|
||||
\begin{tabulary}{\textwidth}{rl}
|
||||
Key & Code \\
|
||||
\hline
|
||||
\ttfamily{V} & 50 \\
|
||||
\ttfamily{W} & 51 \\
|
||||
\ttfamily{X} & 52 \\
|
||||
\ttfamily{Y} & 53 \\
|
||||
\ttfamily{Z} & 54 \\
|
||||
\condensedfont{LCtrl} & 57 \\
|
||||
\condensedfont{RCtrl} & 58 \\
|
||||
\condensedfont{LShift} & 59 \\
|
||||
\condensedfont{RShift} & 60 \\
|
||||
\condensedfont{LAlt} & 129 \\
|
||||
\condensedfont{RAlt} & 130 \\
|
||||
$\uparrow$ & 19 \\
|
||||
$\downarrow$ & 20 \\
|
||||
$\leftarrow$ & 21 \\
|
||||
$\rightarrow$ & 22 \\
|
||||
\condensedfont{Ins} & 133 \\
|
||||
\condensedfont{Del} & 112 \\
|
||||
\condensedfont{PgUp} & 92 \\
|
||||
\condensedfont{PgDn} & 93 \\
|
||||
\condensedfont{Home} & 3 \\
|
||||
\condensedfont{End} & 132 \\
|
||||
F1 & 244 \\
|
||||
\end{tabulary}
|
||||
\begin{tabulary}{\textwidth}{rl}
|
||||
Key & Code \\
|
||||
\hline
|
||||
F2 & 245 \\
|
||||
F3 & 246 \\
|
||||
F4 & 247 \\
|
||||
F5 & 248 \\
|
||||
F6 & 249 \\
|
||||
F7 & 250 \\
|
||||
F8 & 251 \\
|
||||
F9 & 252 \\
|
||||
F10 & 253 \\
|
||||
F11 & 254 \\
|
||||
\condensedfont{Num} \ttfamily{0} & 144 \\
|
||||
\condensedfont{Num} \ttfamily{1} & 145 \\
|
||||
\condensedfont{Num} \ttfamily{2} & 146 \\
|
||||
\condensedfont{Num} \ttfamily{3} & 147 \\
|
||||
\condensedfont{Num} \ttfamily{4} & 148 \\
|
||||
\condensedfont{Num} \ttfamily{5} & 149 \\
|
||||
\condensedfont{Num} \ttfamily{6} & 150 \\
|
||||
\condensedfont{Num} \ttfamily{7} & 151 \\
|
||||
\condensedfont{Num} \ttfamily{8} & 152 \\
|
||||
\condensedfont{Num} \ttfamily{9} & 153 \\
|
||||
\condensedfont{NumLk} & 78 \\
|
||||
\ttfamily{*} & 17 \\
|
||||
\end{tabulary}
|
||||
\end{longtable}
|
||||
|
||||
Keys not listed on the table may not be available depending on the system, for example, F12 may not be recognised.
|
||||
7
assets/disk0/tbas/doc/intro.tex
Normal file
7
assets/disk0/tbas/doc/intro.tex
Normal file
@@ -0,0 +1,7 @@
|
||||
\tbas\ is a BASIC dialect and its interpreter. \tbas\ emulates most of the common BASIC syntax while adds more advanced and up-to-date concepts gracefully, such as user-defined function that can \emph{actually} recurse, arbitrary list construction using CONS-operator and some of the features in the realm of functional programming from \code{MAP} and \code{FOLD} to \code{CURRY}-ing a function.
|
||||
|
||||
This is the documentation for \tbas\ \tbasver.
|
||||
|
||||
\vfill
|
||||
|
||||
\small\emph{Henceforward this documentation will use more friendly and sometimes vulgar language because that's more fun to write!}
|
||||
99
assets/disk0/tbas/doc/langguide.tex
Normal file
99
assets/disk0/tbas/doc/langguide.tex
Normal file
@@ -0,0 +1,99 @@
|
||||
\quad
|
||||
\chapterprecishere{``Begin at the beginning'', the King said gravely, ``and go on till you come to the end: then stop.''\par\raggedleft --- \textup{Lewis Carroll, } Alice in Wonderland}
|
||||
|
||||
We'll begin at the beginning; how beginning? This:
|
||||
|
||||
\begin{lstlisting}
|
||||
10 PRINT 2+2
|
||||
run
|
||||
4
|
||||
Ok
|
||||
\end{lstlisting}
|
||||
|
||||
Oh \emph{boy} we just did a computation! It printed out \code{4} which is a correct answer for $2+2$ and it didn't crash!
|
||||
|
||||
\section[GOTO]{GOTO here and there}
|
||||
\section[When GOTO Is Bad]{Severe Acute Spaghettification Syndrome}
|
||||
\section[Subroutine with GOSUB]{GOSUB to the rescue!}
|
||||
|
||||
\section[FOR-NEXT Loop]{FOR ever loop NEXT}
|
||||
|
||||
\begin{lstlisting}
|
||||
10 FOR I = 1 TO 20
|
||||
20 PRINT SPC(20-I);
|
||||
30 FOR J = 1 TO I*2-1
|
||||
40 PRINT "*";
|
||||
50 NEXT:PRINT
|
||||
60 NEXT
|
||||
\end{lstlisting}
|
||||
|
||||
\section[Get User INPUT]{Isn't It Nice To Have a Computer That Will Question You?}
|
||||
|
||||
\section[Recursion]{BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad RecursionBRB: Bad Recursion BRBRangeError: Maximum call stack size exceeded}
|
||||
|
||||
\begin{lstlisting}
|
||||
10 DEFUN ENDLESS(SHIT)=ENDLESS(SHIT)
|
||||
20 ENDLESS(1)
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}
|
||||
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
|
||||
20 FOR K=1 TO 6
|
||||
30 PRINT FAC(K)
|
||||
40 NEXT
|
||||
\end{lstlisting}
|
||||
|
||||
\begin{lstlisting}
|
||||
10 DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
|
||||
20 K=MAP FAC, 1 TO 10
|
||||
30 PRINT K
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\begin{lstlisting}
|
||||
10 DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
|
||||
20 FOR K=1 TO 12
|
||||
30 PRINT FIB(K);" ";
|
||||
40 NEXT
|
||||
\end{lstlisting}
|
||||
|
||||
\section[Currying]{Haskell Curry Wants to Know Your Location}
|
||||
\label{currying101}
|
||||
|
||||
So what the fsck is currying? Consider the following code:
|
||||
|
||||
\begin{lstlisting}
|
||||
10 DEFUN F(K,T)=ABS(T)==K
|
||||
20 CF=F<~32
|
||||
30 PRINT CF(24) : REM will print 'false'
|
||||
40 PRINT CF(-32) : REM will print 'true'
|
||||
\end{lstlisting}
|
||||
|
||||
% 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}
|
||||
|
||||
Using all the knowledge we have learned, it should be trivial\footnote{/s} to write a Quicksort function in \tbas, like this:
|
||||
|
||||
\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(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 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 by taking the head-element of the array \code{XS}\footnote{stands for \emph{X's}} with \code{HEAD(XS)}, then utilises curried version of \code{LESS} and \code{GTEQ} to move lesser-than-pivot values to the left and greater to the right (the head element itself does not get recursed, here \code{TAIL(XS)} is applied to make head-less copy of the array), and these two separated \emph{chunks} are recursively sorted using the same \code{QSORT} function. Currying is exploited 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)!NIL} creates a single-element array contains head-element of the \code{XS}.
|
||||
|
||||
%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 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}
|
||||
258
assets/disk0/tbas/doc/langref.tex
Normal file
258
assets/disk0/tbas/doc/langref.tex
Normal file
@@ -0,0 +1,258 @@
|
||||
\newcommand{\intrange}{\hl{[$0..2^{53}-1$]}}
|
||||
|
||||
This chapter describes the \tbas\ language.
|
||||
|
||||
\section{Metasyntax}
|
||||
|
||||
In the descriptions of BASIC syntax, these conventions apply.
|
||||
|
||||
\begin{itemlist}
|
||||
\item \codebf{VERBATIM} --- Type exactly as shown
|
||||
\item \code{IDENTIFIER} --- Replace \emph{identifier} with appropriate metavariable
|
||||
\item \code{[a]} --- Words within square brackets are optional
|
||||
\item \code{\{a|b\}} --- Choose either \code{a} or \code{b}
|
||||
\item \code{[a|b]} --- Optional version of above
|
||||
\item \code{a\ldots} --- The preceding entity can be repeated
|
||||
\end{itemlist}
|
||||
|
||||
\section{Definitions}
|
||||
|
||||
A \emph{Program Line} consists of a line number followed by a \emph{Statements}. Program Lines are terminated by a line break or by the end-of-the-file.
|
||||
|
||||
A \emph{Line Number} is an integer within the range of \intrange.
|
||||
|
||||
A \emph{Statement} is special form of code which has special meaning. A program line can be composed of 1 or more statements, separated by colons. For the details of statements available in \tbas , see \ref{statements}.
|
||||
|
||||
\codeline{STATEMENT [: STATEMENT]\ldots}
|
||||
|
||||
An \emph{Expression} is rather normal program lines, e.g. mathematical equations and function calles. The expression takes one of the following forms. For the details of functions available in \tbas , see \ref{functions}.
|
||||
|
||||
\codeline{VARIABLE\_OR\_FUNCTION}\\
|
||||
\codeline{( EXPRESSION )}\\
|
||||
\codeline{\textbf{IF} EXPRESSION \textbf{THEN} EXPRESSION [\textbf{ELSE} EXPRESSION]}\\
|
||||
\codeline{FUNCTION \textbf{(} [EXPRESSION \{\textbf{,}|\textbf{;}\} [\{\textbf{,}|\textbf{;}\}]] \textbf{)}}\\
|
||||
\codeline{FUNCTION [EXPRESSION \{\textbf{,}|\textbf{;}\} [\{\textbf{,}|\textbf{;}\}]]}\\
|
||||
\codeline{EXPRESSION BINARY\_OPERATOR EXPRESSION}\\
|
||||
\codeline{UNARY\_OPERATOR EXPRESSION}
|
||||
|
||||
An \emph{Array} takes following form:
|
||||
|
||||
\codeline{ARRAY\_NAME \textbf{(} EXPRESSION [\textbf{,} EXPRESSION]\ldots\ \textbf{)}}
|
||||
|
||||
\section{Literals}
|
||||
\subsection{String Literals}
|
||||
|
||||
String literals take the following form:
|
||||
|
||||
\codeline{\textbf{"} [CHARACTERS] \textbf{"}}
|
||||
|
||||
where \code{CHARACTERS} is a 0 or more repetition of ASCII-printable letters.\footnote{In other words, \code{0x20..0x7E}}
|
||||
|
||||
To print out graphical letters outside of ASCII-printable, use string concatenation with \code{CHR} function, or use \code{EMIT} function.
|
||||
|
||||
\subsection{Numeric Literals}
|
||||
|
||||
Numeric literals take one of the following forms:
|
||||
|
||||
\codeline{[\textbf{+}|\textbf{-}][\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots\ [\textbf{.}][\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}\\
|
||||
\codeline{\textbf{0}\{\textbf{x}|\textbf{X}\}[\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}\\
|
||||
\codeline{\textbf{0}\{\textbf{b}|\textbf{B}\}[\textbf{0}|\textbf{1}|\textbf{2}|\textbf{3}|\textbf{4}|\textbf{5}|\textbf{6}|\textbf{7}|\textbf{8}|\textbf{9}]\ldots}
|
||||
|
||||
Hexadecimal and binary literals are always interpreted as \emph{unsigned} integers. They must range between \intrange.
|
||||
|
||||
\subsection{Variables}
|
||||
|
||||
Variable names must start with a letter and all characters of the name must be letters \code{A-Z}, figures \code{0-9}. Variable names must not be identical to reserved words, but may \emph{contain} one. Variable names are case-insensitive.
|
||||
|
||||
Unlike conventional BASIC dialects (especially GW-BASIC), name pool of variables are shared between all the types. For example, if you have a numeric variable \code{A}, and define an array named \code{A} later in the program, the new array will overwrite your numeric \code{A}.
|
||||
|
||||
Furthermore, \emph{sigils} are not used in the \tbas\ and attempting to use one will raise syntax-error or undefined behaviour.
|
||||
|
||||
\subsection{Types}
|
||||
|
||||
Types of data recognised by \tbas\ are distinguished by some arcane magic of Javascript auto-casing mumbo-jumbo
|
||||
|
||||
\begin{tabulary}{\textwidth}{rCL}
|
||||
Type & Range & Precision \\
|
||||
\hline
|
||||
String & As many as the machine can handle & \, \\
|
||||
Integer & $ \pm 2^{53}-1 $ & exact within the range \\
|
||||
Float & $ \pm 4.9406564584124654 \times 10^{-324} $ -- $ \pm 1.7976931348623157 \times 10^{308} $ & about 16 significant figures \\
|
||||
\end{tabulary}
|
||||
|
||||
\section{Operators}
|
||||
\subsection{Order of Precedence}
|
||||
|
||||
The order of precedence of the operators is as shown below, lower numbers means they have higher precedence (more tightly bound)
|
||||
|
||||
\begin{longtable}{*{2}{m{\textwidth}}}\hline
|
||||
\endfirsthead
|
||||
\endhead
|
||||
|
||||
\endfoot
|
||||
\hline
|
||||
\endlastfoot
|
||||
\centering
|
||||
\begin{tabulary}{\textwidth}{cCc}
|
||||
Order & Op & Associativity \\
|
||||
\hline
|
||||
1 & \basicexp & Right \\
|
||||
2 & \ast\quad$/$\quad$\backslash$ & Left \\
|
||||
3 & \condensedfont{MOD} & Left \\
|
||||
4 & $+$\quad$-$ & Left \\
|
||||
5 & \condensedfont{NOT}\quad\condensedfont{BNOT} & Left \\
|
||||
6 & <\!<\quad>\!> & Left \\
|
||||
7 & <\enskip>\enskip=\!<\enskip<\!=\enskip=\!>\enskip>\!= & Left \\
|
||||
8 & ==\quad<\!>\quad>\!< & Left \\
|
||||
9 & \condensedfont{MIN}\quad\condensedfont{MAX} & Left \\
|
||||
10 & \condensedfont{BAND} & Left \\
|
||||
\end{tabulary}
|
||||
\begin{tabulary}{\textwidth}{cCc}
|
||||
Order & Op & Associativity \\
|
||||
\hline
|
||||
11 & \condensedfont{BXOR} & Left \\
|
||||
12 & \condensedfont{BOR} & Left \\
|
||||
13 & \condensedfont{AND} & Left \\
|
||||
14 & \condensedfont{OR} & Left \\
|
||||
15 & \condensedfont{TO}\quad\condensedfont{STEP} & Left \\
|
||||
16 & ! & Right \\
|
||||
17 & \sim & Left\\
|
||||
18 & \# & Left \\
|
||||
19 & \basiccurry & Right \\
|
||||
20 & = & Right \\
|
||||
\end{tabulary}
|
||||
\end{longtable}
|
||||
|
||||
\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}
|
||||
\item Exponentiation is right-associative: \code{4\basicexp 3\basicexp 2 == 4\basicexp (3\basicexp 2) == 262144}. This behaviour is \emph{different} from GW-BASIC in which its exponentiation is left-associative.
|
||||
\end{itemlist}
|
||||
|
||||
\subsection{Mathematical Operators}
|
||||
|
||||
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}
|
||||
Code & Operation & Result \\
|
||||
\hline
|
||||
\emph{x} $=$ \emph{y} & Assignment & Assigns \emph{y} into \emph{x} \\
|
||||
\emph{x} $\basicexp$ \emph{y} & Exponentiation & \emph{x} raised to the \emph{y}th power \\
|
||||
\emph{x} $\ast$ \emph{y} & Multiplication & Product of \emph{x} and \emph{y} \\
|
||||
\emph{x} $/$ \emph{y} & Division & Quotient of \emph{x} and \emph{y} \\
|
||||
\emph{x} $\backslash$ \emph{y} & Truncated Division & Integer quotient of \emph{x} and \emph{y} \\
|
||||
\emph{x} \condensedfont{MOD} \emph{y} & Modulo & Integer remainder of \emph{x} and \emph{y} with sign of \emph{x} \\
|
||||
\emph{x} $+$ \emph{y} & Addition & Sum of \emph{x} and \emph{y} \\
|
||||
\emph{x} $-$ \emph{y} & Subtraction & Difference of \emph{x} and \emph{y} \\
|
||||
$+$ \emph{x} & Unary Plus & Value of \emph{x} \\
|
||||
$-$ \emph{x} & Unary Minus & Negative value of \emph{x} \\
|
||||
\emph{x} \condensedfont{MIN} \emph{y} & Minimum & Lesser value of two \\
|
||||
\emph{x} \condensedfont{MAX} \emph{y} & Maximum & Greater value of two \\
|
||||
|
||||
\end{tabulary}
|
||||
|
||||
\subsubsection*{Notes}
|
||||
\begin{itemlist}
|
||||
\item Type conversion rule follows underlying Javascript implementation. In other words, \emph{only the god knows.}
|
||||
\item The expression \code{0\basicexp 0} will return \code{1}, even though the expression is indeterminant.
|
||||
\end{itemlist}
|
||||
|
||||
\subsubsection*{Errors}
|
||||
\begin{itemlist}
|
||||
\item Any expression that results \code{NaN} or \code{Infinity} in Javascript will return some kind of errors, mainly \code{Division by zero}.
|
||||
\item If \code{\emph{x}<0} and \code{\emph{y}} is not integer, \code{\emph{x}\basicexp\emph{y}} will raise \code{Illegal function call}.
|
||||
\end{itemlist}
|
||||
|
||||
\subsection{Comparison Operators}
|
||||
|
||||
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}
|
||||
Code & Operation & Result \\
|
||||
\hline
|
||||
\emph{x} == \emph{y} & Equal & True if \emph{x} equals \emph{y} \\
|
||||
\emph{x} <\!> \emph{y} \quad \emph{x} >\!< \emph{y} & Not equal & False if \emph{x} equals \emph{y} \\
|
||||
\emph{x} < \emph{y} & Less than & True if \emph{x} is less than \emph{y} \\
|
||||
\emph{x} > \emph{y} & Greater than & True if \emph{x} is greater than \emph{y} \\
|
||||
\emph{x} <\!= \emph{y} \quad \emph{x} =\!< \emph{y} & Less than or equal & False if \emph{x} is greater than \emph{y} \\
|
||||
\emph{x} >\!= \emph{y} \quad \emph{x} =\!> \emph{y} & Greater than or equal & False if \emph{x} is less than \emph{y} \\
|
||||
\end{tabulary}
|
||||
|
||||
When comparing strings, the ordering is as follows:
|
||||
|
||||
\begin{itemlist}
|
||||
\item Two strings are equal only when they are of the same length and every codepoint of the first string is identical to that of the second. This includes any whitespace or unprintable characters.
|
||||
\item Each character position of the string is compared starting from the leftmost character. When a pair of different characters is encountered, the string with the character of lesser codepoint is less than the string with the character of greater codepoint.
|
||||
\item If the strings are of different length, but equal up to the length of the shorter string, then the shorter string is less than the longer string.
|
||||
\end{itemlist}
|
||||
|
||||
\subsection{Bitwise Operators}
|
||||
|
||||
Bitwise operators operate on unsigned integers only. Floating points are truncated\footnote{truncated towards zero} to integers.
|
||||
|
||||
\begin{tabulary}{\textwidth}{clL}
|
||||
Code & Operation & Result \\
|
||||
\hline
|
||||
\emph{x} <\!< \emph{y} & Bitwise Shift Left & Shifts entire bits of \emph{x} by \emph{y} \\
|
||||
\emph{x} >\!> \emph{y} & Bitwise Shift Right & Shift entire bits \emph{x} by \emph{y}, including sign bit \\
|
||||
\condensedfont{BNOT} \emph{x} & Ones' complement & $-\emph{x}-1$ \\
|
||||
\emph{x} \condensedfont{BAND} \emph{y} & Bitwise conjunction & Bitwise AND of \emph{x} and \emph{y} \\
|
||||
\emph{x} \condensedfont{BOR} \emph{y} & Bitwise disjunction & Bitwise OR of \emph{x} and \emph{y} \\
|
||||
\emph{x} \condensedfont{BXOR} \emph{y} & Bitwise add-with-no-carry & Bitwise XOR of \emph{x} and \emph{y} \\
|
||||
\end{tabulary}
|
||||
|
||||
\subsection{Boolean Operators}
|
||||
|
||||
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}
|
||||
Code & Operation & Result \\
|
||||
\hline
|
||||
\condensedfont{NOT} \emph{x} & Logical negation & True if \emph{x} is false and vice versa \\
|
||||
\emph{x} \condensedfont{AND} \emph{y} & Bitwise conjunction & True if \emph{x} and \emph{y} are both true \\
|
||||
\emph{x} \condensedfont{OR} \emph{y} & Bitwise disjunction & True if \emph{x} or \emph{y} is true, or both are true \\
|
||||
\end{tabulary}
|
||||
|
||||
\subsection{Generator Operators}
|
||||
|
||||
Generator operators operate on numeric values and generators to create and modify a generator.
|
||||
|
||||
\begin{tabulary}{\textwidth}{clL}
|
||||
Code & Result \\
|
||||
\hline
|
||||
\emph{x} \condensedfont{TO} \emph{y} & Creates an generator that counts from \emph{x} to \emph{y} \\
|
||||
\emph{x} \condensedfont{STEP} \emph{y} & Modifies an counting stride of the generator \emph{x} into \emph{y} \\
|
||||
\end{tabulary}
|
||||
|
||||
\subsection{Array Operators}
|
||||
|
||||
Array operators operate on arrays and numeric values.
|
||||
|
||||
\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} \\
|
||||
\emph{x} $\sim$ \emph{y} & Push & Appends a value of \emph{y} into an array of \emph{x} \\
|
||||
\emph{x} $\#$ \emph{y} & Concat & Concatenates two arrays \\
|
||||
\end{tabulary}
|
||||
|
||||
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}
|
||||
|
||||
\emph{Currying} is an operation that returns new function that has given value applied to the original function's first parameter. See \ref{currying101} for tutorials.
|
||||
|
||||
\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!
|
||||
|
||||
\verbatiminput{syntax.txt}
|
||||
47
assets/disk0/tbas/doc/statements.tex
Normal file
47
assets/disk0/tbas/doc/statements.tex
Normal file
@@ -0,0 +1,47 @@
|
||||
\label{statements}
|
||||
|
||||
A Program line is composed of a line number and one or more statements. Multiple statements are separated by colons \code{:}.
|
||||
|
||||
\section{IF}
|
||||
|
||||
\codeline{\textbf{IF} TRUTH\_VALUE \textbf{THEN} TRUE\_EXPRESSION [\textbf{ELSE} FALSE\_EXPERSSION]}
|
||||
|
||||
If \code{TRUTH\_VALUE} is truthy, executes \code{TRUE\_EXPRESSION}. If \code{TRUTH\_VALUE} is falsy and \code{FALSE\_EXPERSSION} is specified, executes that expression; otherwise next line or next statement will be executed.
|
||||
|
||||
\subsubsection*{Notes}
|
||||
|
||||
\begin{itemlist}
|
||||
\item \codebf{IF} is both statement and expression. You can use IF-clause after \codebf{ELSE}, or within functions as well, for example.
|
||||
\item \codebf{THEN} is \emph{not} optional, this behaviour is different from most of the BASIC dialects.
|
||||
\item Also unlike the most dialects, \codebf{GOTO} cannot be omitted; doing so will make the number be returned to its parent expression.
|
||||
\end{itemlist}
|
||||
|
||||
\section{ON}
|
||||
|
||||
\codeline{\textbf{ON} INDEX\_EXPRESSION \{\textbf{GOTO}|\textbf{GOSUB}\} LINE0 [\textbf{,} LINE1]\ldots}
|
||||
|
||||
Jumps to the line number returned by the \code{INDEX\_EXPRESSION}. If the result is outside of range of the arguments, no jump will be performed.
|
||||
|
||||
\subsubsection*{Parameters}
|
||||
|
||||
\begin{itemlist}
|
||||
\item \code{LINEn} can be a number, numeric expression (aka equations) or a line label.
|
||||
\item When \code{OPTIONBASE 1} is used within the program, \code{LINEn} starts from 1 instead of 0.
|
||||
\end{itemlist}
|
||||
|
||||
\section{DEFUN}
|
||||
|
||||
\emph{There it is, the} DEFUN. \emph{All those new-fangled parser\footnote{a computer program that translates program code entered by you into some data bits that only it can understand} and paradigms\footnote{a guidance to in which way you must think to assimilate your brain into the computer-overlord} are tied to this very statement on \tbas, and only Wally knows its secrets\ldots}
|
||||
|
||||
\codeline{\textbf{DEFUN} NAME \textbf{(} [ARGS0 [\textbf{,} ARGS1]\ldots] \textbf{)} \textbf{=} EXPRESSION }
|
||||
|
||||
With the aid of other statements\footnote{Actually, only the IF is useful, unless you want to \emph{transcend} from the \emph{dung} of mortality by using DEFUN within DEFUN (a little modification of the source code is required)} and functions, DEFUN will allow you to ascend from traditional BASIC and do godly things such as \emph{recursion}\footnote{see recursion} and \emph{functional programming}.
|
||||
|
||||
Oh, and you can define your own function, in traditional \code{DEF FN} sense.
|
||||
|
||||
\subsubsection*{Parameters}
|
||||
|
||||
\begin{itemlist}
|
||||
\item \code{NAME} must be a valid variable name.
|
||||
\item \code{ARGSn} must be valid variable names, but can be a name of variables already used within the BASIC program; their value will not be affected nor be used.
|
||||
\end{itemlist}
|
||||
94
assets/disk0/tbas/doc/syntax.txt
Normal file
94
assets/disk0/tbas/doc/syntax.txt
Normal file
@@ -0,0 +1,94 @@
|
||||
(* quick reference to EBNF *)
|
||||
(* { word } = word is repeated 0 or more times *)
|
||||
(* [ word ] = word is optional (repeated 0 or 1 times) *)
|
||||
|
||||
line =
|
||||
linenumber , stmt , {":" , stmt}
|
||||
| linenumber , "REM" , ? basically anything ? ;
|
||||
linenumber = digits ;
|
||||
|
||||
stmt =
|
||||
"REM" , ? anything ?
|
||||
| "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
|
||||
| "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
|
||||
| "ON" , expr_sans_asgn , ("GOTO" | "GOSUB") , expr_sans_asgn , {"," , expr_sans_asgn}
|
||||
| "(" , 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
|
||||
| "(" , expr , ")"
|
||||
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
|
||||
| kywd , expr - "(" (* also deals with FOR statement *)
|
||||
(* at this point, if OP is found in paren-level 0, skip function_call *)
|
||||
| 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 *)
|
||||
|
||||
argsep = "," | ";" ;
|
||||
ident = alph , [digits] ; (* variable and function names *)
|
||||
lit = alph , [digits] | num | string ; (* ident + numbers and string literals *)
|
||||
op = "^" | "*" | "/" | "MOD" | "+" | "-" | "<<" | ">>" | "<" | ">" | "<="
|
||||
| "=<" | ">=" | "=>" | "==" | "<>" | "><" | "BAND" | "BXOR" | "BOR"
|
||||
| "AND" | "OR" | "TO" | "STEP" | "!" | "~" | "#" | "=" ;
|
||||
op_uni = "-" | "+" ;
|
||||
|
||||
alph = letter | letter , alph ;
|
||||
digits = digit | digit , digits ;
|
||||
hexdigits = hexdigit | hexdigit , hexdigits ;
|
||||
bindigits = bindigit | bindigit , bindigits ;
|
||||
num = digits | digits , "." , [digits] | "." , digits
|
||||
| ("0x"|"0X") , hexdigits
|
||||
| ("0b"|"0B") , bindigits ; (* sorry, no e-notation! *)
|
||||
visible = ? ASCII 0x20 to 0x7E ? ;
|
||||
string = '"' , (visible | visible , stringlit) , '"' ;
|
||||
|
||||
letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N"
|
||||
| "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b"
|
||||
| "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p"
|
||||
| "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "_" ;
|
||||
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
||||
hexdigit = "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f" | "0" | "1"
|
||||
| "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
|
||||
bindigit = "0" | "1" ;
|
||||
|
||||
(* all possible token states: lit num op bool qot paren sep *)
|
||||
(* below are schematic of trees generated after parsing the statements *)
|
||||
|
||||
IF (type: function, value: IF)
|
||||
1. cond
|
||||
2. true
|
||||
[3. false]
|
||||
|
||||
FOR (type: function, value: FOR)
|
||||
1. expr (normally (=) but not necessarily)
|
||||
|
||||
DEFUN (type: function, value: DEFUN)
|
||||
1. funcname
|
||||
1. arg0
|
||||
[2. arg1]
|
||||
[3. argN...]
|
||||
2. stmt
|
||||
|
||||
ON (type: function, value: ON)
|
||||
1. testvalue
|
||||
2. functionname (type: lit)
|
||||
3. arg0
|
||||
[4. arg1]
|
||||
[5. argN...]
|
||||
|
||||
FUNCTION_CALL (type: function, value: PRINT or something)
|
||||
1. arg0
|
||||
2. arg1
|
||||
[3. argN...]
|
||||
211
assets/disk0/tbas/doc/tbasman.tex
Normal file
211
assets/disk0/tbas/doc/tbasman.tex
Normal file
@@ -0,0 +1,211 @@
|
||||
|
||||
% !TEX TS-program = LuaLaTeX
|
||||
|
||||
%% Copyright (c) 2020 CuriousTorvald and the contributors.
|
||||
|
||||
\documentclass[10pt, stock, openany, chapter]{memoir}
|
||||
|
||||
|
||||
\usepackage{fontspec}
|
||||
\setmainfont[Ligatures=TeX]{TeX Gyre Heros}
|
||||
\newfontfamily\condensedfont{TeX Gyre Heros Cn}
|
||||
\newfontfamily\monofont{TeX Gyre Cursor}
|
||||
|
||||
|
||||
|
||||
|
||||
\usepackage{fapapersize}
|
||||
\usefapapersize{148mm,210mm,15mm,15mm,20mm,15mm} % A5 paper
|
||||
\usepackage{afterpage}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{tabulary}
|
||||
\usepackage{longtable}
|
||||
\usepackage[table]{xcolor}
|
||||
\usepackage{ltablex}
|
||||
\usepackage{parskip}
|
||||
\usepackage{multicol}
|
||||
\usepackage{soul}
|
||||
\usepackage{verbatim}
|
||||
\usepackage{etoolbox}
|
||||
\usepackage[most]{tcolorbox}
|
||||
\usepackage{listings}
|
||||
\usepackage{amsmath,amssymb}
|
||||
|
||||
\usepackage{lineno} % debug
|
||||
|
||||
|
||||
\makeatletter
|
||||
\newlength{\mytextsize}
|
||||
\setlength{\mytextsize}{\f@size pt}
|
||||
\newlength{\mybaselineskip}
|
||||
\setlength{\mybaselineskip}{1.3\mytextsize}
|
||||
\patchcmd{\verbatim@input}{\@verbatim}{\scriptsize\@verbatim}{}{}
|
||||
\makeatother
|
||||
\setlength{\baselineskip}{\mybaselineskip}
|
||||
|
||||
\frenchspacing
|
||||
\setlength{\parindent}{0pt}
|
||||
\setlength{\parskip}{\mytextsize}
|
||||
\setsecnumdepth{subsection}
|
||||
|
||||
%% More compact itemize %%
|
||||
\newenvironment{itemlist}{\vspace{0pt}\itemize}{\enditemize}
|
||||
|
||||
%% Idioms %%
|
||||
\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}}
|
||||
\posttitle{\par\end{flushright}}
|
||||
\preauthor{\begin{flushright}}
|
||||
\postauthor{\par\end{flushright}}
|
||||
|
||||
% new sections are new page
|
||||
%\let\oldsection\chapter
|
||||
%\renewcommand\chapter{\clearpage\oldsection}
|
||||
|
||||
% shorten spaces before section header
|
||||
\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
|
||||
\setlength{\afterchapskip}{\baselineskip} % reduce space after chapter title
|
||||
\makeatletter
|
||||
\renewcommand\memendofchapterhook{%
|
||||
\m@mindentafterchapter\@afterheading}
|
||||
\makeatother
|
||||
|
||||
|
||||
\definecolor{lgrey}{HTML}{eeeeee}
|
||||
\sethlcolor{lgrey}
|
||||
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
|
||||
\newcommand{\code}[1]{{\monofont\hl{\,#1\,}}}
|
||||
\newcommand{\codebf}[1]{{\monofont \textbf{\hl{\,#1\,}}}}
|
||||
%%\newcommand{\codeline}[1]{{\monofont\hl{\,#1\,}}}
|
||||
\newcommand{\codeline}[1]{%
|
||||
\colorbox{lgrey}{%
|
||||
\begin{tabular*}{\textwidth}{l}%
|
||||
\monofont #1 \\% TODO fill the cell with \hl colour
|
||||
\end{tabular*}%
|
||||
}}
|
||||
|
||||
\newtcolorbox{lgreybox}[1][]{%
|
||||
breakable,
|
||||
enhanced,
|
||||
colback=lgrey,
|
||||
attach title to upper,
|
||||
fontupper=\monofont,
|
||||
#1
|
||||
}
|
||||
|
||||
\definecolor{sourcecomment}{HTML}{888888}
|
||||
|
||||
\lstset{frame=tb,
|
||||
language=[Visual]Basic,
|
||||
aboveskip=3mm,
|
||||
belowskip=3mm,
|
||||
showstringspaces=false,
|
||||
columns=flexible,
|
||||
basicstyle={\small\ttfamily},
|
||||
numbers=none,
|
||||
numberstyle=\textbf,
|
||||
keywordstyle=,
|
||||
commentstyle=\color{sourcecomment},
|
||||
stringstyle=\textbf,
|
||||
breaklines=true,
|
||||
breakatwhitespace=true,
|
||||
tabsize=3
|
||||
}
|
||||
|
||||
\addtocontents{toc}{\protect\thispagestyle{empty}} % no page number for the TOC header page
|
||||
\aliaspagestyle{part}{empty} % aliasing PART as empty so that page number would not be printed
|
||||
\aliaspagestyle{chapter}{section} % aliasing CHAPTER as section so that page numbering style would be the same as section
|
||||
|
||||
|
||||
% The title
|
||||
\newcommand{\tbas}{Terran BASIC}
|
||||
\newcommand{\thismachine}{TSVM}
|
||||
\newcommand{\tbasver}{1.0}
|
||||
\newcommand{\theedition}{First Edition}
|
||||
\newcommand{\oreallypress}{\large\textbf{O'REALLY\raisebox{1ex}{\scriptsize ?}} \normalsize Press}
|
||||
|
||||
\title{\HUGE\textbf{\MakeUppercase{\tbas} \\ REFERENCE MANUAL} \\ \Large \vspace{1em} For Language Version \tbasver \\ \vspace{7mm} \theedition}
|
||||
\date{}
|
||||
\author{}
|
||||
\hypersetup{
|
||||
pdfauthor={CuriousTorvald},
|
||||
pdftitle={\tbas\ Reference Manual For Language Version \tbasver, \theedition},
|
||||
unicode=true,
|
||||
pdfkeywords={BASIC} {Functional Programming} {Improper Hierarchy},
|
||||
pdfcreator=\oreallypress
|
||||
}
|
||||
|
||||
\begin{document}
|
||||
\begin{titlingpage}
|
||||
\maketitle{}
|
||||
\vfill
|
||||
\oreallypress
|
||||
\end{titlingpage}
|
||||
|
||||
\setcounter{page}{3}
|
||||
\tableofcontents*
|
||||
|
||||
|
||||
%\linenumbers % debug
|
||||
|
||||
\openright
|
||||
\chapter{Introduction}
|
||||
\input{intro}
|
||||
|
||||
\openany
|
||||
\part{Language}
|
||||
|
||||
\chapter{Basic Concepts}
|
||||
\input{concepts}
|
||||
|
||||
\chapter{Language Guide}
|
||||
\input{langguide}
|
||||
|
||||
\chapter{Language Reference}
|
||||
\input{langref}
|
||||
|
||||
\chapter{Statements}
|
||||
\input{statements}
|
||||
|
||||
\chapter{Functions}
|
||||
\input{functions}
|
||||
|
||||
\part{Implementation}
|
||||
|
||||
\chapter{Technical Reference}
|
||||
\input{technical}
|
||||
|
||||
\chapter{Implementation Details}
|
||||
\input{Implementation}
|
||||
|
||||
\chapter{Bibliography}
|
||||
\input{bibliography}
|
||||
|
||||
|
||||
\chapter*{Disclaimers}
|
||||
|
||||
\oreallypress{} is entirely fictional publishing entity; \oreallypress{} has no affiliation whatsoever with any of the real-world publishers.
|
||||
|
||||
|
||||
\afterpage{\pagestyle{empty}\null\newpage}
|
||||
|
||||
\end{document}
|
||||
57
assets/disk0/tbas/doc/technical.tex
Normal file
57
assets/disk0/tbas/doc/technical.tex
Normal file
@@ -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}{R|LL}
|
||||
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{SyntaxTreeReturnObj}. 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} \\
|
||||
Boolean & {\ttfamily bool} \\
|
||||
Generator & {\ttfamily generator} \\
|
||||
Array & {\ttfamily array} \\
|
||||
Number & {\ttfamily num} \\
|
||||
String & {\ttfamily string} \\
|
||||
DEFUN'd Function & {\ttfamily internal\_lambda} \\
|
||||
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{internal\_lambda} 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}
|
||||
117
assets/disk0/tbas/doc/tokeniser.gv
Normal file
117
assets/disk0/tbas/doc/tokeniser.gv
Normal file
@@ -0,0 +1,117 @@
|
||||
digraph g {
|
||||
concentrate=true;
|
||||
splines=true;
|
||||
newrank=true;
|
||||
overlap=false;
|
||||
|
||||
LITERAL [shape=box]
|
||||
QUOTE [shape=box]
|
||||
PAREN [shape=box]
|
||||
SEP [shape=diamond]
|
||||
OPERATOR [shape=diamond]
|
||||
OPERATOR2 [shape=diamond] // needs second pass to rename it as "OPERATOR"
|
||||
NUMBER [shape=diamond]
|
||||
numbersep [style=filled]
|
||||
NUMBER2 [shape=diamond] // needs second pass to rename it as "NUMBER"
|
||||
limbo [style=filled]
|
||||
escape [style=filled]
|
||||
quote_end [style=filled]
|
||||
start [shape=Mdiamond]
|
||||
error [shape=Msquare,style=filled]
|
||||
|
||||
|
||||
subgraph clusternum {
|
||||
shape=none;
|
||||
NUMBER; NUMBER2; numbersep;
|
||||
}
|
||||
|
||||
subgraph clusterops {
|
||||
shape=none;
|
||||
OPERATOR; OPERATOR2;
|
||||
}
|
||||
|
||||
|
||||
start -> LITERAL
|
||||
|
||||
LITERAL -> QUOTE [label="\""]
|
||||
LITERAL -> PAREN [label="()[]"]
|
||||
LITERAL -> limbo [label=_]
|
||||
LITERAL -> SEP [label=","]
|
||||
LITERAL -> OPERATOR [label="^*/+->=<"]
|
||||
LITERAL -> LITERAL [label=otherwise]
|
||||
|
||||
NUMBER -> NUMBER [label="0..9_"]
|
||||
NUMBER -> numbersep [label="xXbB."]
|
||||
NUMBER -> QUOTE [label="\""]
|
||||
NUMBER -> limbo [label=" "]
|
||||
NUMBER -> PAREN [label="()[]"]
|
||||
NUMBER -> SEP [label=","]
|
||||
NUMBER -> OPERATOR [label="^*/+->=<"]
|
||||
NUMBER -> LITERAL [label=otherwise]
|
||||
|
||||
numbersep -> NUMBER2 [label="0..9_"]
|
||||
numbersep -> error [label=otherwise]
|
||||
|
||||
NUMBER2 -> NUMBER2 [label="0..9_"]
|
||||
NUMBER2 -> QUOTE [label="\""]
|
||||
NUMBER2 -> limbo [label=" "]
|
||||
NUMBER2 -> PAREN [label="()[]"]
|
||||
NUMBER2 -> SEP [label=","]
|
||||
NUMBER2 -> OPERATOR [label="^*/+->=<"]
|
||||
NUMBER2 -> LITERAL [label=otherwise]
|
||||
|
||||
OPERATOR -> OPERATOR2 [label=">=<"]
|
||||
OPERATOR -> error [label="^*/+-"]
|
||||
OPERATOR -> NUMBER [label="0..9"]
|
||||
OPERATOR -> QUOTE [label="\""]
|
||||
OPERATOR -> limbo [label=" "]
|
||||
OPERATOR -> PAREN [label="()[]"]
|
||||
OPERATOR -> SEP [label=","]
|
||||
OPERATOR -> LITERAL [label=otherwise]
|
||||
|
||||
OPERATOR2 -> error [label="^*/+->=<"]
|
||||
OPERATOR2 -> NUMBER [label="0..9"]
|
||||
OPERATOR2 -> QUOTE [label="\""]
|
||||
OPERATOR2 -> limbo [label=" "]
|
||||
OPERATOR2 -> PAREN [label="()[]"]
|
||||
OPERATOR2 -> SEP [label=","]
|
||||
OPERATOR2 -> LITERAL [label=otherwise]
|
||||
|
||||
QUOTE -> quote_end [label="\""]
|
||||
QUOTE -> escape [label="\\"]
|
||||
QUOTE -> QUOTE [label=otherwise]
|
||||
|
||||
escape -> QUOTE [label=any]
|
||||
|
||||
quote_end -> limbo [label=_]
|
||||
quote_end -> QUOTE [label="\""]
|
||||
quote_end -> PAREN [label="()[]"]
|
||||
quote_end -> SEP [label=","]
|
||||
quote_end -> NUMBER [label="0..9"]
|
||||
quote_end -> OPERATOR [label="^*/+->=<"]
|
||||
quote_end -> LITERAL [label=otherwise]
|
||||
|
||||
limbo -> limbo [label=_]
|
||||
limbo -> QUOTE [label="\""]
|
||||
limbo -> PAREN [label="()[]"]
|
||||
limbo -> SEP [label=","]
|
||||
limbo -> NUMBER [label="0..9"]
|
||||
limbo -> OPERATOR [label="^*/+->=<"]
|
||||
limbo -> LITERAL [label=otherwise]
|
||||
|
||||
PAREN -> limbo [label=_]
|
||||
PAREN -> QUOTE [label="\""]
|
||||
PAREN -> PAREN [label="()[]"]
|
||||
PAREN -> SEP [label=","]
|
||||
PAREN -> NUMBER [label="0..9"]
|
||||
PAREN -> OPERATOR [label="^*/+->=<"]
|
||||
PAREN -> LITERAL [label=otherwise]
|
||||
|
||||
SEP -> limbo [label=_]
|
||||
SEP -> QUOTE [label="\""]
|
||||
SEP -> PAREN [label="()[]"]
|
||||
SEP -> SEP [label=","]
|
||||
SEP -> NUMBER [label="0..9"]
|
||||
SEP -> OPERATOR [label="^*/+->=<"]
|
||||
SEP -> LITERAL [label=otherwise]
|
||||
}
|
||||
848
assets/disk0/tbas/parser_wip.js
Normal file
848
assets/disk0/tbas/parser_wip.js
Normal file
@@ -0,0 +1,848 @@
|
||||
class ParserError extends Error {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
Error.captureStackTrace(this, ParserError);
|
||||
}
|
||||
}
|
||||
var DEFUNS_BUILD_DEFUNS = false;
|
||||
let bF = {};
|
||||
bF.parserPrintdbg = any => serial.println(any);
|
||||
bF.parserPrintdbg2 = function(icon, lnum, tokens, states, recDepth) {
|
||||
let treeHead = String.fromCharCode(0x2502,32).repeat(recDepth);
|
||||
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${tokens.join(' ')}`);
|
||||
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${states.join(' ')}`);
|
||||
}
|
||||
bF.parserPrintdbgline = function(icon, msg, lnum, recDepth) {
|
||||
let treeHead = String.fromCharCode(0x2502,32).repeat(recDepth);
|
||||
bF.parserPrintdbg(`${icon}${lnum} ${treeHead}${msg}`);
|
||||
}
|
||||
let lang = {};
|
||||
lang.syntaxfehler = function(line, reason) {
|
||||
return Error("Syntax error" + ((line !== undefined) ? (" in "+line) : "") + ((reason !== undefined) ? (": "+reason) : ""));
|
||||
};
|
||||
|
||||
/**
|
||||
* @return ARRAY of BasicAST
|
||||
*/
|
||||
bF._parseTokens = function(lnum, tokens, states) {
|
||||
bF.parserPrintdbg2('Line ', lnum, tokens, states, 0);
|
||||
|
||||
if (tokens.length !== states.length) throw lang.syntaxfehler(lnum);
|
||||
if (tokens[0].toUpperCase() == "REM" && states[0] != "qot") return;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let parenDepth = 0;
|
||||
let parenStart = -1;
|
||||
let parenEnd = -1;
|
||||
let seps = [];
|
||||
|
||||
// scan for parens and (:)s
|
||||
for (let k = 0; k < tokens.length; k++) {
|
||||
// increase paren depth and mark paren start position
|
||||
if (tokens[k] == "(" && states[k] != "qot") {
|
||||
parenDepth += 1;
|
||||
if (parenStart == -1 && parenDepth == 1) parenStart = k;
|
||||
}
|
||||
// decrease paren depth
|
||||
else if (tokens[k] == ")" && states[k] != "qot") {
|
||||
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
|
||||
parenDepth -= 1;
|
||||
}
|
||||
|
||||
if (parenDepth == 0 && tokens[k] == ":" && states[k] == "seq")
|
||||
seps.push(k);
|
||||
}
|
||||
|
||||
let startPos = [0].concat(seps.map(k => k+1));
|
||||
let stmtPos = startPos.map((s,i) => {return{start:s, end:(seps[i] || tokens.length)}}); // use end of token position as separator position
|
||||
|
||||
return stmtPos.map((x,i) => {
|
||||
if (stmtPos.length > 1)
|
||||
bF.parserPrintdbgline('Line ', 'Statement #'+(i+1), lnum, 0);
|
||||
|
||||
// check for empty tokens
|
||||
if (x.end - x.start <= 0) throw new ParserError("Malformed Line");
|
||||
|
||||
let tree = bF._parseStmt(lnum,
|
||||
tokens.slice(x.start, x.end),
|
||||
states.slice(x.start, x.end),
|
||||
1
|
||||
);
|
||||
|
||||
bF.parserPrintdbgline('Tree in ', '\n'+astToString(tree), lnum, 0);
|
||||
|
||||
return tree;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/** Parses following EBNF rule:
|
||||
stmt =
|
||||
"REM" , ? anything ?
|
||||
| "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
|
||||
| "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
|
||||
| "ON" , expr_sans_asgn , ident , expr_sans_asgn , {"," , expr_sans_asgn}
|
||||
| "(" , stmt , ")"
|
||||
| expr ; (* if the statement is 'lit' and contains only one word, treat it as function_call e.g. NEXT for FOR loop *)
|
||||
* @return: BasicAST
|
||||
*/
|
||||
bF._parseStmt = function(lnum, tokens, states, recDepth) {
|
||||
bF.parserPrintdbg2('$', lnum, tokens, states, recDepth);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// case for: single word (e.g. NEXT for FOR loop)
|
||||
if (tokens.length == 1 && states.length == 1) {
|
||||
bF.parserPrintdbgline('$', "Single Word Function Call", lnum, recDepth);
|
||||
return bF._parseLit(lnum, tokens, states, recDepth + 1, true);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let headTkn = tokens[0].toUpperCase();
|
||||
let headSta = states[0];
|
||||
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// case for: "REM" , ? anything ?
|
||||
if (headTkn == "REM" && headSta != "qot") return;
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let parenDepth = 0;
|
||||
let parenStart = -1;
|
||||
let parenEnd = -1;
|
||||
let onGoPos = -1;
|
||||
let sepsZero = [];
|
||||
let sepsOne = [];
|
||||
|
||||
// scan for parens that will be used for several rules
|
||||
// also find nearest THEN and ELSE but also take parens into account
|
||||
for (let k = 0; k < tokens.length; k++) {
|
||||
// increase paren depth and mark paren start position
|
||||
if (tokens[k] == "(" && states[k] != "qot") {
|
||||
parenDepth += 1;
|
||||
if (parenStart == -1 && parenDepth == 1) parenStart = k;
|
||||
}
|
||||
// decrease paren depth
|
||||
else if (tokens[k] == ")" && states[k] != "qot") {
|
||||
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
|
||||
parenDepth -= 1;
|
||||
}
|
||||
|
||||
if (parenDepth == 0 && states[k] == "sep")
|
||||
sepsZero.push(k);
|
||||
if (parenDepth == 1 && states[k] == "sep")
|
||||
sepsOne.push(k);
|
||||
|
||||
if (parenDepth == 0) {
|
||||
let tok = tokens[k].toUpperCase();
|
||||
if (-1 == onGoPos && ("GOTO" == tok || "GOSUB" == tok) && "lit" == states[k])
|
||||
onGoPos = k;
|
||||
}
|
||||
}
|
||||
|
||||
// unmatched brackets, duh!
|
||||
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
|
||||
try {
|
||||
bF.parserPrintdbgline('$', "Trying IF Statement...", lnum, recDepth);
|
||||
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false);
|
||||
}
|
||||
// if ParserError is raised, continue to apply other rules
|
||||
catch (e) {
|
||||
bF.parserPrintdbgline('$', 'It was NOT!', lnum, recDepth);
|
||||
if (!(e instanceof ParserError)) throw e;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | "DEFUN" , [ident] , "(" , [ident , {" , " , ident}] , ")" , "=" , expr
|
||||
if ("DEFUN" == headTkn && "lit" == headSta &&
|
||||
parenStart == 2 && tokens[parenEnd + 1] == "=" && states[parenEnd + 1] == "op"
|
||||
) {
|
||||
bF.parserPrintdbgline('$', 'DEFUN Stmt', lnum, recDepth);
|
||||
|
||||
treeHead.astValue = "DEFUN";
|
||||
treeHead.astType = "function";
|
||||
|
||||
// parse function name
|
||||
if (tokens[1] == "(") {
|
||||
// anonymous function
|
||||
treeHead.astLeaves[0] = new BasicAST();
|
||||
treeHead.astLeaves[0].astLnum = lnum;
|
||||
treeHead.astLeaves[0].astType = "lit";
|
||||
}
|
||||
else {
|
||||
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Name:', lnum, recDepth);
|
||||
treeHead.astLeaves[0] = bF._parseIdent(lnum, [tokens[1]], [states[1]], recDepth + 1);
|
||||
}
|
||||
|
||||
// parse function arguments
|
||||
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments -- ', lnum, recDepth);
|
||||
let defunArgDeclSeps = sepsOne.filter((i) => i < parenEnd + 1).map(i => i-1).concat([parenEnd - 1]);
|
||||
bF.parserPrintdbgline('$', 'DEFUN Stmt Function Arguments comma position: '+defunArgDeclSeps, lnum, recDepth);
|
||||
|
||||
treeHead.astLeaves[0].astLeaves = defunArgDeclSeps.map(i=>bF._parseIdent(lnum, [tokens[i]], [states[i]], recDepth + 1));
|
||||
|
||||
// parse function body
|
||||
let parseFunction = (DEFUNS_BUILD_DEFUNS) ? bF._parseStmt : bF._parseExpr;
|
||||
treeHead.astLeaves[1] = parseFunction(lnum,
|
||||
tokens.slice(parenEnd + 2, tokens.length),
|
||||
states.slice(parenEnd + 2, states.length),
|
||||
recDepth + 1
|
||||
);
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | "ON" , if_equation , ident , if_equation , {"," , if_equation}
|
||||
if ("ON" == headTkn && "lit" == headSta) {
|
||||
bF.parserPrintdbgline('$', 'ON Stmt', lnum, recDepth);
|
||||
|
||||
if (onGoPos == -1) throw ParserError("Malformed ON Statement");
|
||||
|
||||
treeHead.astValue = "ON";
|
||||
treeHead.astType = "function";
|
||||
|
||||
// parse testvalue
|
||||
let testvalue = bF._parseExpr(lnum,
|
||||
tokens.slice(1, onGoPos),
|
||||
states.slice(1, onGoPos),
|
||||
recDepth + 1,
|
||||
true
|
||||
);
|
||||
|
||||
// parse functionname
|
||||
let functionname = bF._parseExpr(lnum,
|
||||
[tokens[onGoPos]],
|
||||
[states[onGoPos]],
|
||||
recDepth + 1,
|
||||
true
|
||||
);
|
||||
|
||||
// parse arguments
|
||||
// get list of comma but filter ones appear before GOTO/GOSUB
|
||||
let onArgSeps = sepsZero.filter(i => (i > onGoPos));
|
||||
let onArgStartPos = [onGoPos + 1].concat(onArgSeps.map(k => k + 1));
|
||||
let onArgPos = onArgStartPos.map((s,i) => {return{start:s, end: (onArgSeps[i] || tokens.length)}}); // use end of token position as separator position
|
||||
|
||||
// recursively parse expressions
|
||||
treeHead.astLeaves = [testvalue, functionname].concat(onArgPos.map((x,i) => {
|
||||
bF.parserPrintdbgline('$', 'ON GOTO/GOSUB Arguments #'+(i+1), lnum, recDepth);
|
||||
|
||||
// check for empty tokens
|
||||
if (x.end - x.start <= 0) throw new ParserError("Malformed ON arguments");
|
||||
|
||||
return bF._parseExpr(lnum,
|
||||
tokens.slice(x.start, x.end),
|
||||
states.slice(x.start, x.end),
|
||||
recDepth + 1,
|
||||
true
|
||||
);
|
||||
}));
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | "(" , stmt , ")"
|
||||
if (parenStart == 0 && parenEnd == tokens.length - 1) {
|
||||
bF.parserPrintdbgline('$', '( Stmt )', lnum, recDepth);
|
||||
return bF._parseStmt(lnum,
|
||||
tokens.slice(parenStart + 1, parenEnd),
|
||||
states.slice(parenStart + 1, parenEnd),
|
||||
recDepth + 1
|
||||
);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | expr ;
|
||||
try {
|
||||
bF.parserPrintdbgline('$', 'Trying Expression Call...', lnum, recDepth);
|
||||
return bF._parseExpr(lnum, tokens, states, recDepth + 1);
|
||||
}
|
||||
catch (e) {
|
||||
bF.parserPrintdbgline('$', 'Error!', lnum, recDepth);
|
||||
throw new ParserError("Statement cannot be parsed in "+lnum+": "+e.stack);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
throw new ParserError("Statement cannot be parsed in "+lnum);
|
||||
} // END of STMT
|
||||
|
||||
|
||||
/** Parses following EBNF rule:
|
||||
expr = (* this basically blocks some funny attemps such as using DEFUN as anon function because everything is global in BASIC *)
|
||||
lit
|
||||
| "(" , expr , ")"
|
||||
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
|
||||
| kywd , expr (* also deals with FOR statement; kywd = ? words that exists on the list of predefined function that are not operators ? ; *)
|
||||
| function_call
|
||||
| expr , op , expr
|
||||
| op_uni , expr ;
|
||||
|
||||
* @return: BasicAST
|
||||
*/
|
||||
bF._parseExpr = function(lnum, tokens, states, recDepth, ifMode) {
|
||||
bF.parserPrintdbg2('E', lnum, tokens, states, recDepth);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## special case for virtual dummy element (e.g. phantom element on "PRINT SPC(20);")
|
||||
if (tokens[0] === undefined && states[0] === undefined) {
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
treeHead.astValue = undefined;
|
||||
treeHead.astType = "null";
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let headTkn = tokens[0].toUpperCase();
|
||||
let headSta = states[0];
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// lit
|
||||
if (!bF._EquationIllegalTokens.includes(headTkn) && tokens.length == 1) {
|
||||
bF.parserPrintdbgline('E', 'Literal Call', lnum, recDepth);
|
||||
return bF._parseLit(lnum, tokens, states, recDepth + 1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// scan for operators with highest precedence, use rightmost one if multiple were found
|
||||
let topmostOp;
|
||||
let topmostOpPrc = 0;
|
||||
let operatorPos = -1;
|
||||
|
||||
// find and mark position of parentheses
|
||||
// properly deal with the nested function calls
|
||||
let parenDepth = 0;
|
||||
let parenStart = -1;
|
||||
let parenEnd = -1;
|
||||
|
||||
// Scan for unmatched parens and mark off the right operator we must deal with
|
||||
// every function_call need to re-scan because it is recursively called
|
||||
for (let k = 0; k < tokens.length; k++) {
|
||||
// increase paren depth and mark paren start position
|
||||
if (tokens[k] == "(" && states[k] != "qot") {
|
||||
parenDepth += 1;
|
||||
if (parenStart == -1 && parenDepth == 1) parenStart = k;
|
||||
}
|
||||
// decrease paren depth
|
||||
else if (tokens[k] == ")" && states[k] != "qot") {
|
||||
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
|
||||
parenDepth -= 1;
|
||||
}
|
||||
|
||||
// determine the right operator to deal with
|
||||
if (parenDepth == 0) {
|
||||
if (states[k] == "op" && bF.isSemanticLiteral(tokens[k-1], states[k-1]) &&
|
||||
((bF._opPrc[tokens[k].toUpperCase()] > topmostOpPrc) ||
|
||||
(!bF._opRh[tokens[k].toUpperCase()] && bF._opPrc[tokens[k].toUpperCase()] == topmostOpPrc))
|
||||
) {
|
||||
topmostOp = tokens[k].toUpperCase();
|
||||
topmostOpPrc = bF._opPrc[tokens[k].toUpperCase()];
|
||||
operatorPos = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unmatched brackets, duh!
|
||||
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | "(" , expr , ")"
|
||||
if (parenStart == 0 && parenEnd == tokens.length - 1) {
|
||||
bF.parserPrintdbgline('E', '( Expr )', lnum, recDepth);
|
||||
|
||||
return bF._parseExpr(lnum,
|
||||
tokens.slice(parenStart + 1, parenEnd),
|
||||
states.slice(parenStart + 1, parenEnd),
|
||||
recDepth + 1
|
||||
);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
|
||||
try {
|
||||
bF.parserPrintdbgline('E', "Trying IF Expression...", lnum, recDepth);
|
||||
return bF._parseIfMode(lnum, tokens, states, recDepth + 1, false);
|
||||
}
|
||||
// if ParserError is raised, continue to apply other rules
|
||||
catch (e) {
|
||||
bF.parserPrintdbgline('E', 'It was NOT!', lnum, recDepth);
|
||||
if (!(e instanceof ParserError)) throw e;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | kywd , expr (* kywd = ? words that exists on the list of predefined function that are not operators ? ; *)
|
||||
if (bStatus.builtin[headTkn] && headSta == "lit" && !bF._opPrc[headTkn] &&
|
||||
states[1] != "paren"
|
||||
) {
|
||||
bF.parserPrintdbgline('E', 'Builtin Function Call w/o Paren', lnum, recDepth);
|
||||
|
||||
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// (* at this point, if OP is found in paren-level 0, skip function_call *)
|
||||
// | function_call ;
|
||||
if (topmostOp === undefined) { // don't remove this IF statement!
|
||||
try {
|
||||
bF.parserPrintdbgline('E', "Trying Function Call...", lnum, recDepth);
|
||||
return bF._parseFunctionCall(lnum, tokens, states, recDepth + 1);
|
||||
}
|
||||
// if ParserError is raised, continue to apply other rules
|
||||
catch (e) {
|
||||
bF.parserPrintdbgline('E', 'It was NOT!', lnum, recDepth);
|
||||
if (!(e instanceof ParserError)) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// | expr , op, expr
|
||||
// | op_uni , expr
|
||||
// if operator is found, split by the operator and recursively parse the LH and RH
|
||||
if (topmostOp !== undefined) {
|
||||
bF.parserPrintdbgline('E', 'Operators', lnum, recDepth);
|
||||
|
||||
if (ifMode && topmostOp == "=") throw lang.syntaxfehler(lnum, "'=' used on IF, did you mean '=='?");
|
||||
if (ifMode && topmostOp == ":") throw lang.syntaxfehler(lnum, "':' used on IF");
|
||||
|
||||
|
||||
// this is the AST we're going to build up and return
|
||||
// (other IF clauses don't use this)
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
treeHead.astValue = topmostOp;
|
||||
treeHead.astType = "op";
|
||||
|
||||
// BINARY_OP?
|
||||
if (operatorPos > 0) {
|
||||
let subtknL = tokens.slice(0, operatorPos);
|
||||
let substaL = states.slice(0, operatorPos);
|
||||
let subtknR = tokens.slice(operatorPos + 1, tokens.length);
|
||||
let substaR = states.slice(operatorPos + 1, tokens.length);
|
||||
|
||||
treeHead.astLeaves[0] = bF._parseExpr(lnum, subtknL, substaL, recDepth + 1);
|
||||
treeHead.astLeaves[1] = bF._parseExpr(lnum, subtknR, substaR, recDepth + 1);
|
||||
}
|
||||
else {
|
||||
if (topmostOp === "-") treeHead.astValue = "UNARYMINUS"
|
||||
else if (topmostOp === "+") treeHead.astValue = "UNARYPLUS"
|
||||
else if (topmostOp === "NOT") treeHead.astValue = "UNARYLOGICNOT"
|
||||
else if (topmostOp === "BNOT") treeHead.astValue = "UNARYBNOT"
|
||||
else throw new ParserError(`Unknown unary op '${topmostOp}'`)
|
||||
|
||||
treeHead.astLeaves[0] = bF._parseExpr(lnum,
|
||||
tokens.slice(operatorPos + 1, tokens.length),
|
||||
states.slice(operatorPos + 1, states.length),
|
||||
recDepth + 1
|
||||
);
|
||||
}
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
throw new ParserError("Expression cannot be parsed in "+lnum);
|
||||
} // END of EXPR
|
||||
|
||||
|
||||
/** Parses following EBNF rule:
|
||||
"IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
|
||||
| "IF" , expr_sans_asgn , "THEN" , expr , ["ELSE" , expr]
|
||||
if exprMode is true, only the latter will be used; former otherwise
|
||||
* @return: BasicAST
|
||||
*/
|
||||
bF._parseIfMode = function(lnum, tokens, states, recDepth, exprMode) {
|
||||
bF.parserPrintdbg2('/', lnum, tokens, states, recDepth);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let headTkn = tokens[0].toUpperCase();
|
||||
let headSta = states[0];
|
||||
|
||||
let parseFunction = (exprMode) ? bF._parseExpr : bF._parseStmt
|
||||
|
||||
let thenPos = -1;
|
||||
let elsePos = -1;
|
||||
let parenDepth = 0;
|
||||
let parenStart = -1;
|
||||
let parenEnd = -1;
|
||||
|
||||
// scan for parens that will be used for several rules
|
||||
// also find nearest THEN and ELSE but also take parens into account
|
||||
for (let k = 0; k < tokens.length; k++) {
|
||||
// increase paren depth and mark paren start position
|
||||
if (tokens[k] == "(" && states[k] != "qot") {
|
||||
parenDepth += 1;
|
||||
if (parenStart == -1 && parenDepth == 1) parenStart = k;
|
||||
}
|
||||
// decrease paren depth
|
||||
else if (tokens[k] == ")" && states[k] != "qot") {
|
||||
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
|
||||
parenDepth -= 1;
|
||||
}
|
||||
|
||||
if (parenDepth == 0) {
|
||||
if (-1 == thenPos && "THEN" == tokens[k].toUpperCase() && "lit" == states[k])
|
||||
thenPos = k;
|
||||
else if (-1 == elsePos && "ELSE" == tokens[k].toUpperCase() && "lit" == states[k])
|
||||
elsePos = k;
|
||||
}
|
||||
}
|
||||
|
||||
// unmatched brackets, duh!
|
||||
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
|
||||
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
|
||||
// ## case for:
|
||||
// "IF" , expr_sans_asgn , "THEN" , stmt , ["ELSE" , stmt]
|
||||
if ("IF" == headTkn && "lit" == headSta) {
|
||||
|
||||
// "THEN" not found, raise error!
|
||||
if (thenPos == -1) throw lang.syntaxfehler(lnum, "IF without THEN");
|
||||
|
||||
treeHead.astValue = "IF";
|
||||
treeHead.astType = "function";
|
||||
|
||||
treeHead.astLeaves[0] = bF._parseExpr(lnum,
|
||||
tokens.slice(1, thenPos),
|
||||
states.slice(1, thenPos),
|
||||
recDepth + 1,
|
||||
true // if_equation mode
|
||||
);
|
||||
treeHead.astLeaves[1] = parseFunction(lnum,
|
||||
tokens.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
|
||||
states.slice(thenPos + 1, (elsePos != -1) ? elsePos : tokens.length),
|
||||
recDepth + 1
|
||||
);
|
||||
if (elsePos != -1)
|
||||
treeHead.astLeaves[2] = parseFunction(lnum,
|
||||
tokens.slice(elsePos + 1, tokens.length),
|
||||
states.slice(elsePos + 1, tokens.length),
|
||||
recDepth + 1
|
||||
);
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
throw new ParserError("not an IF "+(exprMode) ? "expression" : "statement");
|
||||
} // END of IF
|
||||
|
||||
|
||||
/** Parses following EBNF rule:
|
||||
function_call =
|
||||
ident , "(" , [expr , {argsep , expr} , [argsep]] , ")"
|
||||
| ident , expr , {argsep , expr} , [argsep] ;
|
||||
* @return: BasicAST
|
||||
*/
|
||||
bF._parseFunctionCall = function(lnum, tokens, states, recDepth) {
|
||||
bF.parserPrintdbg2(String.fromCharCode(0x192), lnum, tokens, states, recDepth);
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
let parenDepth = 0;
|
||||
let parenStart = -1;
|
||||
let parenEnd = -1;
|
||||
let _argsepsOnLevelZero = []; // argseps collected when parenDepth == 0
|
||||
let _argsepsOnLevelOne = []; // argseps collected when parenDepth == 1
|
||||
|
||||
// Scan for unmatched parens and mark off the right operator we must deal with
|
||||
// every function_call need to re-scan because it is recursively called
|
||||
for (let k = 0; k < tokens.length; k++) {
|
||||
// increase paren depth and mark paren start position
|
||||
if (tokens[k] == "(" && states[k] != "qot") {
|
||||
parenDepth += 1;
|
||||
if (parenStart == -1 && parenDepth == 1) parenStart = k;
|
||||
}
|
||||
// decrease paren depth
|
||||
else if (tokens[k] == ")" && states[k] != "qot") {
|
||||
if (parenEnd == -1 && parenDepth == 1) parenEnd = k;
|
||||
parenDepth -= 1;
|
||||
}
|
||||
|
||||
if (parenDepth == 0 && states[k] == "sep")
|
||||
_argsepsOnLevelZero.push(k);
|
||||
if (parenDepth == 1 && states[k] == "sep")
|
||||
_argsepsOnLevelOne.push(k);
|
||||
}
|
||||
|
||||
// unmatched brackets, duh!
|
||||
if (parenDepth != 0) throw lang.syntaxfehler(lnum, lang.unmatchedBrackets);
|
||||
let parenUsed = (parenStart == 1);
|
||||
// && parenEnd == tokens.length - 1);
|
||||
// if starting paren is found, just use it
|
||||
// this prevents "RND(~~)*K" to be parsed as [RND, (~~)*K]
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
// ## case for:
|
||||
// ident , "(" , [expr , {argsep , expr} , [argsep]] , ")"
|
||||
// | ident , expr , {argsep , expr} , [argsep] ;
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), `Function Call (parenUsed: ${parenUsed})`, lnum, recDepth);
|
||||
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
|
||||
// set function name and also check for syntax by deliberately parsing the word
|
||||
treeHead.astValue = bF._parseIdent(lnum, [tokens[0]], [states[0]], recDepth + 1).astValue; // always UPPERCASE
|
||||
|
||||
// 5 8 11 [end]
|
||||
let argSeps = parenUsed ? _argsepsOnLevelOne : _argsepsOnLevelZero; // choose which "sep tray" to use
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), "argSeps = "+argSeps, lnum, recDepth);
|
||||
// 1 6 9 12
|
||||
let argStartPos = [1 + (parenUsed)].concat(argSeps.map(k => k+1));
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), "argStartPos = "+argStartPos, lnum, recDepth);
|
||||
// [1,5) [6,8) [9,11) [12,end)
|
||||
let argPos = argStartPos.map((s,i) => {return{start:s, end:(argSeps[i] || (parenUsed ? parenEnd : tokens.length) )}}); // use end of token position as separator position
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), "argPos = "+argPos.map(it=>`${it.start}/${it.end}`), lnum, recDepth);
|
||||
|
||||
// check for trailing separator
|
||||
|
||||
|
||||
// recursively parse function arguments
|
||||
treeHead.astLeaves = argPos.map((x,i) => {
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), 'Function Arguments #'+(i+1), lnum, recDepth);
|
||||
|
||||
// check for empty tokens
|
||||
if (x.end - x.start < 0) throw new ParserError("not a function call because it's malformed");
|
||||
|
||||
return bF._parseExpr(lnum,
|
||||
tokens.slice(x.start, x.end),
|
||||
states.slice(x.start, x.end),
|
||||
recDepth + 1
|
||||
)}
|
||||
);
|
||||
treeHead.astType = "function";
|
||||
treeHead.astSeps = argSeps.map(i => tokens[i]);
|
||||
bF.parserPrintdbgline(String.fromCharCode(0x192), "astSeps = "+treeHead.astSeps, lnum, recDepth);
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
|
||||
bF._parseIdent = function(lnum, tokens, states, recDepth) {
|
||||
bF.parserPrintdbg2('i', lnum, tokens, states, recDepth);
|
||||
|
||||
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array");
|
||||
if (tokens.length != 1 || states[0] != "lit") throw new ParserError(`illegal tokens '${tokens}' with states '${states}' in ${lnum}`);
|
||||
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
treeHead.astValue = tokens[0].toUpperCase();
|
||||
treeHead.astType = "lit";
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
/**
|
||||
* @return: BasicAST
|
||||
*/
|
||||
bF._parseLit = function(lnum, tokens, states, recDepth, functionMode) {
|
||||
bF.parserPrintdbg2(String.fromCharCode(0xA2), lnum, tokens, states, recDepth);
|
||||
|
||||
if (!Array.isArray(tokens) && !Array.isArray(states)) throw new ParserError("Tokens and states are not array");
|
||||
if (tokens.length != 1) throw new ParserError("parseLit 1");
|
||||
|
||||
let treeHead = new BasicAST();
|
||||
treeHead.astLnum = lnum;
|
||||
treeHead.astValue = ("qot" == states[0]) ? tokens[0] : tokens[0].toUpperCase();
|
||||
treeHead.astType = ("qot" == states[0]) ? "string" :
|
||||
("num" == states[0]) ? "num" :
|
||||
(functionMode) ? "function" : "lit";
|
||||
|
||||
return treeHead;
|
||||
}
|
||||
|
||||
|
||||
bF._EquationIllegalTokens = ["IF","THEN","ELSE","DEFUN","ON"];
|
||||
bF.isSemanticLiteral = function(token, state) {
|
||||
return undefined == token || "]" == token || ")" == token ||
|
||||
"qot" == state || "num" == state || "bool" == state || "lit" == state;
|
||||
}
|
||||
|
||||
|
||||
/////// TEST/////////
|
||||
let astToString = function(ast, depth) {
|
||||
let l__ = String.fromCharCode(0x2502,32);
|
||||
let recDepth = depth || 0;
|
||||
if (ast === undefined || ast.astType === undefined) return "";
|
||||
var sb = "";
|
||||
var marker = ("lit" == ast.astType) ? "i" :
|
||||
("op" == ast.astType) ? String.fromCharCode(0xB1) :
|
||||
("string" == ast.astType) ? String.fromCharCode(0xB6) :
|
||||
("num" == ast.astType) ? String.fromCharCode(0xA2) :
|
||||
("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";
|
||||
for (var k = 0; k < ast.astLeaves.length; k++) {
|
||||
sb += astToString(ast.astLeaves[k], recDepth + 1);
|
||||
sb += l__.repeat(recDepth+1) + " " + ast.astSeps[k] + "\n";
|
||||
}
|
||||
sb += l__.repeat(recDepth)+String.fromCharCode(0x2570)+String.fromCharCode(0x2500).repeat(13)+'\n';
|
||||
return sb;
|
||||
}
|
||||
let BasicAST = function() {
|
||||
this.astLnum = 0;
|
||||
this.astLeaves = [];
|
||||
this.astSeps = [];
|
||||
this.astValue = undefined;
|
||||
this.astType = "null"; // lit, op, string, num, array, function, null, defun_args (! NOT usrdefun !)
|
||||
}
|
||||
bF._opPrc = {
|
||||
// function call in itself has highest precedence
|
||||
"^":1,
|
||||
"*":2,"/":2,"\\":2,
|
||||
"MOD":3,
|
||||
"+":4,"-":4,
|
||||
"NOT":5,"BNOT":5,
|
||||
"<<":6,">>":6,
|
||||
"<":7,">":7,"<=":7,"=<":7,">=":7,"=>":7,
|
||||
"==":8,"<>":8,"><":8,
|
||||
"MIN":10,"MAX":10,
|
||||
"BAND":20,
|
||||
"BXOR":21,
|
||||
"BOR":22,
|
||||
"AND":30,
|
||||
"OR":31,
|
||||
"TO":40,
|
||||
"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,"<~":1,"~>":1};
|
||||
let bStatus = {};
|
||||
bStatus.builtin = {};
|
||||
["PRINT","NEXT","SPC","CHR","ROUND","SQR","RND","GOTO","GOSUB","DEFUN","FOR","MAP"].forEach(w=>{ bStatus.builtin[w] = 1 });
|
||||
let lnum = 10;
|
||||
|
||||
// if s<2 then (nop1) else (if s < 9999 then nop2 else nop3)
|
||||
let tokens1 = ["if","s","<","2","then","(","nop1",")","else","(","if","s","<","9999","then","nop2","else","nop3",")"];
|
||||
let states1 = ["lit","lit","op","num","lit","paren","lit","paren","lit","paren","lit","lit","op","num","lit","lit","lit","lit","paren"];
|
||||
|
||||
// DEFUN HYPOT(X,Y) = SQR(X*X+Y*Y)
|
||||
let tokens2 = ["defun","HYPOT","(","X",",","Y",")","=","SQR","(","X","*","X","+","Y","*","Y",")"];
|
||||
let states2 = ["lit","lit","paren","lit","sep","lit","paren","op","lit","paren","lit","op","lit","op","lit","op","lit","paren"];
|
||||
|
||||
// DEFUN SINC(X) = SIN(X) / X
|
||||
let tokens3 = ["DEFUN","SINC","(","X",")","=","SIN","(","X",")","/","X"];
|
||||
let states3 = ["lit","lit","paren","lit","paren","op","lit","paren","lit","paren","op","lit"];
|
||||
|
||||
// PRINT(IF S<2 THEN "111" ELSE IF S<3 THEN "222" ELSE "333")
|
||||
let tokens4 = ["PRINT","(","IF","S","<","2","THEN","111","ELSE","IF","S","<","3","THEN","222","ELSE","333",")"];
|
||||
let states4 = ["lit","paren","lit","lit","op","lit","lit","qot","lit","lit","lit","op","lit","lit","qot","lit","qot","paren"];
|
||||
|
||||
// ON 6*SQR(X-3) GOTO X+1, X+2, X+3
|
||||
let tokens5 = ["ON","6","*","SQR","(","X","-","3",")","GOTO","X","+","1",",","X","+","2",",","X","+","3"];
|
||||
let states5 = ["lit","num","op","lit","paren","lit","op","num","paren","lit","lit","op","num","sep","lit","op","num","sep","lit","op","num"];
|
||||
|
||||
// FOR K=10 TO 1 STEP -1
|
||||
let tokens6 = ["FOR","K","=","10","TO","1","STEP","-","1"];
|
||||
let states6 = ["lit","lit","op","num","op","num","op","op","num"];
|
||||
|
||||
// print(chr(47+round(rnd(1))*45);)
|
||||
let tokens7 = ["PRINT","(","CHR","(","47","+","ROUND","(","RND","(","1",")",")","*","45",")",";",")"];
|
||||
let states7 = ["lit","paren","lit","paren","num","op","lit","paren","lit","paren","num","paren","paren","op","num","paren","sep","paren"];
|
||||
|
||||
// PRINT 4 - 5 * 9
|
||||
let tokens8 = ["PRINT","4","-","5","*","9"];
|
||||
let states8 = ["lit","num","op","num","op","num"];
|
||||
|
||||
// NEXT
|
||||
let tokens9 = ["NEXT"];
|
||||
let states9 = ["lit"];
|
||||
|
||||
// PRINT -3
|
||||
let tokens10 = ["PRINT","-","3"];
|
||||
let states10 = ["lit","op","num"];
|
||||
|
||||
// PRINT SPC(20-I);
|
||||
let tokens11 = ["PRINT","SPC","(","20","-","I",")",";"];
|
||||
let states11 = ["lit","lit","paren","num","op","lit","paren","sep"];
|
||||
|
||||
// DEFUN FAC(N)=IF N==0 THEN 1 ELSE N*FAC(N-1)
|
||||
let tokens12 = ["DEFUN","FAC","(","N",")","=","IF","N","==","0","THEN","1","ELSE","N","*","FAC","(","N","-","1",")"];
|
||||
let states12 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","num","lit","lit","op","lit","paren","lit","op","num","paren"];
|
||||
|
||||
// K = MAP FAC , 1 TO 10
|
||||
let tokens13 = ["K","=","MAP","FAC",",","1","TO","10"];
|
||||
let states13 = ["lit","op","lit","lit","sep","num","op","num"];
|
||||
|
||||
// DEFUN FIB(N)=IF N==0 THEN 0 ELSE IF N==1 THEN 1 ELSE FIB(N-1)+FIB(N-2)
|
||||
let tokens14 = ["DEFUN","FIB","(","N",")","=","IF","N","==","0","THEN","0",
|
||||
"ELSE","IF","N","==","1","THEN","1",
|
||||
"ELSE","FIB","(","N","-","1",")","+","FIB","(","N","-","2",")"];
|
||||
let states14 = ["lit","lit","paren","lit","paren","op","lit","lit","op","num","lit","num",
|
||||
"lit","lit","lit","op","num","lit","num",
|
||||
"lit","lit","paren","lit","op","num","paren","op","lit","paren","lit","op","num","paren"];
|
||||
|
||||
// PRINT(MAP FIB, 1 TO 10) is not broken, it's obvious syntax error
|
||||
// use "PRINT MAP(FIB, 1 TO 10)" instead
|
||||
let tokens15 = ["PRINT","MAP","(","FIB",",","1","TO","10",")"];
|
||||
let states15 = ["lit","lit","paren","lit","sep","num","op","num","paren"];
|
||||
|
||||
// DEFUN KA(X)=IF X>2 THEN DO(PRINT(HAI);PRINT(X)) ELSE DO(PRINT(BYE);PRINT(X))
|
||||
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,
|
||||
tokens17,
|
||||
states17
|
||||
);
|
||||
trees.forEach((t,i) => {
|
||||
serial.println("\nParsed Statement #"+(i+1));
|
||||
serial.println(astToString(t));
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
serial.printerr(e);
|
||||
serial.printerr(e.stack || "stack trace undefined");
|
||||
}
|
||||
Reference in New Issue
Block a user