% % BaSiX (with the emphasis on SICK!) by Andrew Marc Greene % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Andrew's Affiliations % % Copyright (C) 1990 by Andrew Marc Greene % % MIT Project Athena % Student Information Processing Board % All rights reserved. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % BaSiX's Beginnings % \def\flageol{\catcode13=13} \def\endflageol{\catcode13=5} \def\struncat{\catcode`\$=12} \def\strcat{\catcode`\$=11} \flageol\let\eol \endflageol \newif\ifresult %\newcount\xa\newcount\xb \def\iw{\immediate\write16} \def\empty{} \def\gobble#1{} \def\spc{ } \def\itstrue{tt} \def\itsfalse{tf} \def\isnull#1{\resultfalse \expandafter\ifx\csname empty#1\endcsname\empty\resulttrue\fi} \newcount\matha\newcount\mathb % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Character-string Calls % \newcount\strtmp \def\ascii#1{\strtmp`#1} \def\chr#1{\begingroup\uccode65=#1\uppercase{\gdef\tmp{A}}\endgroup} \def\strlen#1{\strtmp-2% don't count " " \iw tokens \expandafter\if\stringP #1\let\next\strIter\strIter #1\iw\fi} \def\strIter#1{\ifx\iw#1\let\next\relax\else\advance\strtmp by 1\relax \fi\next} \def\Flen{\expandafter\strlen\expandafter{\Pa}\return{\number\strtmp}} \strcat \def\F$chr${\expandafter\chr\expandafter{\Pa}\return{\tmp}} \struncat % first char only: % \def\Fasc{\expandafter\asc\expandafter{\Pa}\return{\number\strtmp}} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Debugging Definitions % \def\debug{\tracingmacros=2} \def\diw#1{} \def\Cdebug{\let\diw\iw\tracingmacros=2 \endeval} \def\Cnodebug{\def\diw##1{}\endeval} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Expression Evaluation % \def\expression{\let\afterexpression\afterscan\math} % % (math is a misnomer and should -> expr) % \newcount\parens\newcount\mathParams \def\math{\parens=0\mathParams96\mathInit\matheval} \def\mathRecurse{\advance\parens by 1\relax\mathParams96\mathInit\matheval} \def\mathInit{\begingroup \let\mathAcc\empty \let\mathOpRel\empty \let\mathOpAdd\empty \let\mathOpMul\empty \let\mathlhRef\empty} % \def\matheval{\after\mathbranch\scan} % \def\mathbranch{\diw{EXPRESS:\expandafter\noexpand\word:} \let\next\matherr \ifx\empty\word\let\next\mathHardEnd\else % Expr. end? \expandafter\if\numberP\word\let\next\mathLiteral\diw{@@}\fi % Number? \expandafter\if\stringP\word\let\next\mathLiteral\fi % String literal? \expandafter\if\identifierP\word\let\next\mathIdentifier\fi % Identifier? \expandafter\if\stringvarP\word\let\next\mathIdentifier\fi % :-( \expandafter\if\macroP\word\let\next\mathMacro\fi % Macro? \ifx\word\Oleft\let\next\mathRecurse\fi % Open paren? \ifx\word\Oright\let\next\mathEndRecurse\fi % Close paren? \ifx\word\Ocomma\let\next\mathComma\fi % Comma? % % Operator? % \ifx\word\Oplus\let\next\mathOp\diw{!+}\fi \ifx\word\Ominus\let\next\mathOp\diw{!-}\fi \ifx\word\Otimes\let\next\mathOp\diw{!*}\fi \ifx\word\Odiv\let\next\mathOp\diw{!/}\fi \ifx\word\Olt\let\next\mathOp\diw{!<}\fi \ifx\word\Oeq\let\next\mathOp\diw{!=}\fi \ifx\word\Ogt\let\next\mathOp\diw{!>}\fi % \fi\next} % \def\Oleft{(}\def\Oright{)}\def\Ocomma{,} \def\Oplus{+}\def\Ominus{-}\def\Otimes{*}\def\Odiv{/} \def\Olt{<}\def\Oeq{=}\def\Ogt{>} % % There's got to be a better way to do the above.... % \def\mathLiteral{\diw{MLIT}\ifx\empty\mathAcc\diw{ACCUMUL:\word:} \expandafter\def\expandafter\mathAcc\expandafter {\expandafter\expandafter\expandafter\empty\word} \else \diw{ACC has :\mathAcc: and word is :\word:} \errmessage{Syntax Error: Two values with no operator}\fi\matheval} % % Operator stuff: (Need to add string support / error checking) % \def\mathAdd{\advance\matha by \mathb} \def\mathSub{\advance\matha by -\mathb} \def\mathMul{\multiply\matha by \mathb} \def\mathDiv{\divide\matha by \mathb} \def\mathEQ{\ifnum\matha=\mathb\matha-1\else\matha0\fi} \def\mathGT{\ifnum\matha>\mathb\matha-1\else\matha0\fi} \def\mathLT{\ifnum\matha<\mathb\matha-1\else\matha0\fi} % \def\mathFlushRel{\mathFlushAdd\ifx\empty\mathOpRel\else \matha=\mathlhRel\relax\mathb=\mathAcc\relax\mathOpRel \edef\mathAcc{\number\matha}\let\mathOpRel\empty\fi} % \def\mathFlushAdd{\mathFlushMul\ifx\empty\mathOpAdd\else \matha=\mathlhAdd\relax\mathb=\mathAcc\relax\mathOpAdd \edef\mathAcc{\number\matha}\let\mathOpAdd\empty\fi} % \def\mathFlushMul{\mathFlushRef\ifx\empty\mathOpMul\else \matha=\mathlhMul\relax\mathb=\mathAcc\relax\mathOpMul \edef\mathAcc{\number\matha}\let\mathOpMul\empty\fi} % \def\mathFlushRef{\ifx\empty\mathlhRef\else \mathParam \mathlhRef\let\mathlhRef\empty\fi} % \def\mathOp{% \if\word+ \mathFlushAdd\let\mathlhAdd\mathAcc\let\mathOpAdd\mathAdd\fi \if\word- \mathFlushAdd\let\mathlhAdd\mathAcc\let\mathOpAdd\mathSub\fi \if\word* \mathFlushMul\let\mathlhMul\mathAcc\let\mathOpMul\mathMul\fi \if\word/ \mathFlushMul\let\mathlhMul\mathAcc\let\mathOpMul\mathDiv\fi \if\word= \mathFlushRel\let\mathlhRel\mathAcc\let\mathOpRel\mathEQ\fi \if\word> \mathFlushRel\let\mathlhRel\mathAcc\let\mathOpRel\mathGT\fi \if\word< \mathFlushRel\let\mathlhRel\mathAcc\let\mathOpRel\mathLT\fi \let\mathAcc\empty \matheval} % \def\mathIdentifier{% \expandafter\ifx\csname C\word\endcsname\relax \expandafter\ifx\csname F\word\endcsname\relax \expandafter\ifx\csname V\word\endcsname\relax \let\next\matherr\diw{LOSING:\word:} \else\let\next\mathVariable\fi \else\let\next\mathFunction\fi \else\let\next\mathCommand\fi\next} % \def\mathVariable{\expandafter\edef\expandafter\word\expandafter {\csname V\word\endcsname}\mathbranch} \def\mathCommand{\expandafter\mathHardEnd\word} \def\mathFunction{\expandafter\let\expandafter\mathlhRef \csname F\word\endcsname\matheval} % \def\mathParam{\advance\mathParams by 1\relax\chr\mathParams \diw{PARAM:\tmp:\mathAcc:} \expandafter\edef\csname P\tmp\endcsname{\mathAcc}} \def\mathComma{\mathEnd\mathParam\mathInit\matheval} \def\mathEndRecurse{\mathEnd\advance\parens by -1\matheval} \def\mathEnd{\diw{MATHEND: ACC=\mathAcc:}\mathFlushRel \xdef\mathtemp{\mathAcc}\endgroup\edef\mathAcc{\mathtemp}} \def\mathHardEnd{\ifnum\parens>0\errmessage{Insufficient closeparens.}\relax \let\next\endeval\else\let\next\mathFinal\fi\next} \def\mathFinal{\mathEnd\let\value\mathAcc\endexpression} \def\matherr{\errmessage{Syntax error: Unknown symbol \word}} \def\endexpression{\afterexpression} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Linked List % \def\gotofirstline{\edef\lpointer{\csname L0\endcsname}} \def\foreachline#1{\ifnum\lpointer<99999\edef\word{\lpointer}#1% \edef\lpointer{\csname L\word\endcsname}\foreachline{#1}\fi} % % gotopast{#1} where #1 is a line number, will set \lpointer to % the least value such that L(lpointer)>#1 % \def\gotopast#1{\def\lpointer{0}\def\target{#1}\gotopastloop} % \def\gotopastloop{\edef\tmp{\csname L\lpointer\endcsname}% \ifnum\tmp<\target% \edef\lpointer{\csname L\lpointer\endcsname}% \let\next=\gotopastloop\else\let\next=\relax\fi \next} % \flageol\def\addLineToLinkedList#1#2 {\def#1{#2}\diw{Just stored #2 in \noexpand #1}% % now put it into linked list... \expandafter\ifx\csname L\word\endcsname\relax% if it isn't already there, \gotopast{\word}% \def\lpointer{what-should-point-to-word} \expandafter\edef\csname L\word\endcsname{\csname L\lpointer\endcsname}% \expandafter\edef\csname L\lpointer\endcsname{\word}% \fi\endeval }\endflageol \expandafter\def\csname L0\endcsname{99999} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Program Parser % \def\evalline{%\iw{EVALLINE :\word:}% \csname C\word\endcsname} %error-checking? :-) \def\evalerror{\errmessage{Unkonwn command. Sorry.}} % % \mandatory takes one argument and checks to see if the next % non-whitespace token matches it. If not, an error is generated. % \def\mandatory#1{\def\tmp{#1}\mandatest} \def\mandatest#1{\def\tmpp{#1}\ifx\tmp\tmpp\let\next\afterscan\else \let\next\manderror\fi\next} \def\manderror{\errmessage{\tmpp\spc read when \tmp\spc expected.}% \afterscan} % % \parseline gets the first WORD of the next line. If it's a line % number, \scanandstoreline is called; otherwise the line is executed. % \def\parseline{\after\firsttest\scan} \def\firsttest{\expandafter\if\numberP\word \let\next\grabandstoreline\else\let\next\evalline\fi\next} \def\grabandstoreline{\diw{Grabbing line \word.}% \expandafter\addLineToLinkedList\csname/\word\endcsname} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Syntactic Scanner % % The \scan routine reads the next WORD and then calls \afterscan. % % As syntactic sugar, one can write \after\foo to set \afterscan to % \foo. % % Here are the rules governing WORD. Initial whitespace is % discarded. The word is the next single character, unless that % character is one of the following: % % A-Z or a-z: [A-Z,a-z][A-Z,a-z,0-9]*\$? % 0-9: [0-9]+ % ": "[^"]*" % <,=,>: [<=>][<=>]? (one or two; not the same if two) % % Note that the string literal ignores spaces but may be abnormally % terminated by an end-of-line. (I wasn't sure how to express that % as a regexp). % % \newif\ifscan % shall we continue scanning? % \def\scan{\def\word{}\futurelet\q\scanFirst} % \def\scanFirst{% Checks the first character to determine type. \let\next\scanIter \expandafter\if\spc\noexpand\q % Space -- ignore it \let\next\scanSpace\else \if\eol\noexpand\q % End of line -- no word here \let\next\scanEnd\else \ifcat A\noexpand\q % Then we have an identifier \let\scanTest\scanIdentifier\else \expandafter\if\digit\q \let\scanTest\scanNumericConstant\else \if"\noexpand\q \let\scanTest\scanStringConstant\else \expandafter\if\relationP\q \let\scanTest\scanRelation \else \let\scanTest\scanfalse \fi\fi\fi\fi\fi\fi\next} % \def\scanIter#1{\expandafter\def\expandafter\word\expandafter{\word #1} \futurelet\q\scanContinueP} \def\scanContinueP{\scanTest\ifscan\let\next\scanIter \else\let\next\scanEnd\fi\next} % \def\scanSpace#1{\scan}% If the first char is a space, gobble it and try again. \def\scanIdentifier{\ifcat A\noexpand\q\scantrue\else \expandafter\if\digit\q\scantrue \else\if$\noexpand\q\scantrue \expandafter\def\expandafter\word\expandafter{\expandafter$\word} \let\scanTest\scanfalse\else \scanfalse\fi\fi\fi} \def\scanEndString{\scanfalse} \def\scanNumericConstant{\expandafter\if\digit\q\scantrue\else\scanfalse\fi} \def\scanStringConstant{\scantrue\if"\q\let\scanTest\scanfalse\fi} \def\scanRelation{\if<\q\scantrue\else\if>\q\scantrue\else\if=\q\scantrue \else\scanfalse\fi\fi\fi} % \def\scanEnd#1{\relax\diw{SCANNED:\word:} \afterscan #1}% dumps trailing spaces. \def\after{\let\afterscan}% % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Type Tests (Predicates for type determination) % \def\relationP#1{tf} % for now, only single-char relations \def\identifierP#1{\expandafter\identifierTest #1\\} \def\identifierTest#1#2\\{\ifcat A#1\itstrue\else\itsfalse\fi} \def\stringvarP#1{\expandafter\stringvarTest #1\\} \def\stringvarTest#1#2\\{\if$#1\itstrue\else\itsfalse\fi} \def\stringP#1{\expandafter\stringTest #1\\} \def\stringTest#1#2\\{\if #1"\itstrue\else\itsfalse\fi} \def\numberP#1{\expandafter\numberTest #1\\} \def\numberTest#1#2\\{\expandafter\if\digit #1\itstrue\else\itsfalse\fi} \def\macroP#1{\expandafter\macroTest #1\\} \def\macroTest#1#2\\{\expandafter\ifx #1\relax\itstrue\else\itsfalse\fi} % % \digit tests its single-token argument and returns tt if true, % tf otherwise. % % \def\digit#1{% \if0\noexpand#1\itstrue\else \if1\noexpand#1\itstrue\else \if2\noexpand#1\itstrue\else \if3\noexpand#1\itstrue\else \if4\noexpand#1\itstrue\else \if5\noexpand#1\itstrue\else \if6\noexpand#1\itstrue\else \if7\noexpand#1\itstrue\else \if8\noexpand#1\itstrue\else \if9\noexpand#1\itstrue\else\itsfalse \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi % 9 8 7 6 5 4 3 2 1 0 } % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % User Utilities. These are the commands that are called by the % user. We could really use a better section name. :-) % % List (one line or all lines, for now) % \def\Clist{\after\listmain\scan} \def\listmain{\isnull{\word}\ifresult\let\next\listalllines \else\let\next\listoneline\fi\next} \def\listline{\iw{\word\spc\csname/\word\endcsname}} \def\listalllines{\gotofirstline\foreachline{\listline}\endeval} \def\listoneline{\listline\endeval} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Different degrees of ``stop execution'' % \def\Csystem{\end} % exits to the system \def\Cexit{}% \endflageol} % exits to TeX \flageol% \def\Cstop#1 {\iw{Stopped in \lineno.}\cleanstop}% \def\cleanstop{\diw{CLEANSTOP}\let\endeval\enduserline\endeval }\endflageol % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The command ``rem'' introduces a remark % \def\Crem{\endeval}% % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The ``let'' command allows variable assignments % \def\Clet{\after\letgetequals\scan} \def\letgetequals{\after\letgetvalue\mandatory{=}} \def\letgetvalue{\after\letdoit\expression} \def\letdoit{\expandafter\edef\csname V\word\endcsname{\value}% \endeval} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The ``print'' command takes a [list of] expression[s] and displays % it [them]. % \def\Cprint{\after\printit\expression} \def\printit{\iw{\value}\endeval} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % The ``if'' command takes an expression, the word ``then,'' and % another command. If the expression is non-zero, the command is % executed; otherwise it is ignored. % \def\Cif{\after\getift\expression} \def\Cthen{\errmessage{Syntax error: THEN without IF}} \def\getift{\after\getifh\mandatory t} \def\getifh{\after\getife\mandatory h} \def\getife{\after\getifn\mandatory e} \def\getifn{\after\consequent\mandatory n} \def\consequent{\ifnum\value=0\let\next=\endeval\else\let\next=\evalconsq\fi \next} \def\evalconsq{\after\evalline\scan} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Functions % % Functions may read the counter \mathParams to find out the number % of the top parameter. Parameters are in Pa Pb Pc etc. % \def\return{\expandafter\def\expandafter\mathAcc\expandafter} % \def\Finc{\matha=\Pa \advance\matha by 1 \return{\number\matha}} % \def\Fmin{\ifnum\Pa<\Pb\return{\Pa}\else\return{\Pb}\fi} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Program execution control % \def\Crun{\let\endeval\endincrline\def\lineno{0}\endeval} \def\Cgoto{\let\endeval\endgotoline\after\gotomain\scan} \def\gotomain{\edef\lineno{\word}\endeval} % \flageol% \def\execline{%\message{Executing line \lineno...}% \edef\theline{\csname/\lineno\endcsname}% %\message{THE LINE\theline}% \let\endeval\endincrline\after\evalline\expandafter\scan\theline }\endflageol % % Different varieties of what to do at the end of a command: % % get new line from user (enduserline) % get next line in order (endincrline) % get line in \lineno (endgotoline) % keep parsing current line (endcolonline tbi) % \flageol% \def\endincrline#1 {\diw{ENDINCRLINE}\edef\lineno{\csname L\lineno\endcsname}\execnextline} % \def\endgotoline#1 {\diw{ENDGOTOLINE}\let\endeval\execincrline\execnextline}% % \def\execnextline{\diw{Ready to execute line \lineno...}% \ifnum\lineno<99999\let\next\execline\else\let\next\cleanstop\fi\next}% % \def\enduserline #1 {\diw{ENDUSERLINE}\parseline}\endflageol % \let\endeval\enduserline % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Start your engines! % \iw{This is BaSiX, v0.3, emphasis on the SICK! by amgreene@mit.edu} \flageol \catcode32=12 \endeval