// -*- Mode:Common-Lisp;Package:mma; Base:10 -*- // Lisp-mathematica (Lmath) parser for Mathematica (tm)-like language. // (c) copyright 1990, 1991 by Richard J. Fateman // Last revised 5/29/91 by RJF // Mathematica is described in S. Wolfram: Mathematica, a // System for Doing Mathematics By Computer, (Addison-Wesley). // this line is not quite enough. Need to do, prior to compiling this // file, (set-case-mode :case-sensitive-lower) // nil(nil(nil(#f), nil(#f))); // (provide 'math-parser) // LTD: Function LOAD not yet implemented. load("mma"); // get all the symbols from this file "(in-package mma)"; // (export '(p pc rc)) define variable mathbuffer = #f; define variable stream = #t; // if needed // The first section consists of readtable hacking for mathematica parser. // We set up a separate readtable for // mathematica input, and utilize it when scanning. // We use lisp atoms to store information on tokens. // For production, this could all be put in a Lisp package. define variable mathrt = // LTD: Function COPY-READTABLE not yet implemented. copy-readtable(#f); define variable si = // LTD: Function MAKE-SYNONYM-STREAM not yet implemented. make-synonym-stream(#"*standard-input*"); begin *print-level* := #f; *print-length* := #f; *print-pretty* := #t; end; define method pc () peek(stream); end method pc; define method rc () read-element(stream, nil); end method rc; define method char-to-int (c) let h = as(, c); if (h < 48) h - 7; // #\A=17 elseif (h < 58) h - 48; // #\0 is 48 in ascii. else h - 87; end if; end method char-to-int; define method collect-integer (val, r) if (pc() == '\n') val; elseif (digit-char?(pc(), r)) // r is radix collect-integer(char-to-int(rc()) + r * val, r); // ((eql (pc) #\`)(rc)(collect-integer val r)) ;;option 123`456 is 123456. else val; end if; end method collect-integer; // to test scanner, try typing // (mreadl) // most of these read-table entries were generated by macro expansion // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('/', method (stream, char) select (pc()) '\n' => #"/"; ':' => rc(); #"/:"; '.' => rc(); #"/."; '@' => rc(); #"/@"; ';' => rc(); #"/;"; '=' => rc(); #"/="; '/' => rc(); select (pc()) '\n' => #"//"; '@' => rc(); #"//@"; '.' => rc(); #"//."; otherwise => #"//"; end select; otherwise => #"/"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('^', method (stream, char) select (pc()) '\n' => #"^"; '=' => rc(); #"^="; '^' => rc(); #"^^"; ':' => rc(); select (pc()) '\n' => #"^:"; '=' => rc(); #"^:="; otherwise => #"^:"; end select; otherwise => #"^"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('&', method (stream, char) select (pc()) '\n' => #"&"; '&' => rc(); #"&&"; otherwise => #"&"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('|', method (stream, char) select (pc()) '\n' => #"|"; '|' => rc(); #"||"; otherwise => #"|"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('+', method (stream, char) select (pc()) '\n' => #"+"; '+' => rc(); #"++"; '=' => rc(); #"+="; otherwise => #"+"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('*', method (stream, char) select (pc()) '\n' => #"*"; '*' => rc(); #"**"; '=' => rc(); #"*="; otherwise => #"*"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('-', method (stream, char) select (pc()) '\n' => #"-"; '>' => rc(); #"->"; '=' => rc(); #"-="; '-' => rc(); #"--"; otherwise => #"-"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('[', method (stream, char) select (pc()) '\n' => #"["; '[' => rc(); #"[["; otherwise => #"["; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character(']', method (stream, char) select (pc()) '\n' => #"]"; ']' => rc(); #"]]"; otherwise => #"]"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('{', method (stream, char) #"{"; end method, // fixed 2/21/91 lvi@ida.liu.se #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('<', method (stream, char) select (pc()) '\n' => #"<"; '=' => rc(); #"<="; otherwise => #"<"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('>', method (stream, char) select (pc()) '\n' => #">"; '=' => rc(); #">="; '>' => rc(); select (pc()) '\n' => #">>"; '>' => rc(); #">>>"; otherwise => #">>"; end select; otherwise => #">"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('!', method (stream, char) select (pc()) '\n' => #"!"; '!' => rc(); #"!!"; '=' => rc(); #"!="; otherwise => #"!"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('#', method (stream, char) select (pc()) '\n' => #"#"; '#' => rc(); #"##"; otherwise => #"#"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('\\', method (stream, char) select (pc()) '\n' => rc(); mread1(); #"t" => as(, make(, size: 1, fill: rc())); end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('=', method (stream, char) select (pc()) '\n' => #"="; '=' => rc(); select (pc()) '\n' => #"=="; '=' => rc(); #"==="; otherwise => #"=="; end select; '!' => rc(); select (pc()) '\n' => #"=!"; '=' => rc(); #"=!="; otherwise => #"=!"; end select; otherwise => #"="; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('.', method (stream, char) select (pc()) '\n' => #"."; '.' => rc(); select (pc()) '\n' => #".."; '.' => rc(); #"..."; otherwise => #".."; end select; otherwise => #"."; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character(':', method (stream, char) select (pc()) '\n' => #":"; '>' => rc(); #":>"; ':' => rc(); select (pc()) '\n' => #"::"; '=' => rc(); #"::="; otherwise => #"::"; end select; '=' => rc(); #":="; otherwise => #":"; end select; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('\'', method (stream, char) #"'"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('@', method (stream, char) select (pc()) '\n' => #"@"; '@' => rc(); #"@@"; otherwise => #"@"; end select; end method, #f, mathrt); // above fixed by lvi@ida.liu 3/20/92 // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('~', method (stream, char) #"~"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('?', method (stream, char) #"?"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character(')', method (stream, char) #")"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('}', method (stream, char) #"}"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character(';', method (stream, char) #";"; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character(',', method (stream, char) #","; end method, #f, mathrt); // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('\n', method (stream, char) #"e-o-l"; end method, #f, mathrt); begin do(method (x) symbol-get-property(x, #"mathtoken") := #t; end method, #(#"/", #"/:", #"/.", #"/@", #"/;", #"/=", #"//", #"//@", #"//.", #"^", #"^=", #"^^", #"^:=", #"^:", #"&", #"&&", #"|", #"||", #"+", #"++", #"+=", #"**", #"*=", #"-", #"->", #"-=", #"--", #"[", #"[[", #"]", #"]]", #"{", #"}", #">", #">=", #">>", #">>>", #"<", #"<=", #"!", #"!!", #"!=", #"#", #"##", #":=", #":>", #"::", #"::=", #":", #"=", #"==", #"===", #"=!=", #".", #"..", #"...", #"\\", #"e-o-l", #"(", #")", #"'", #"@", #"~", #"?", #";", #",")); #(#"/", #"/:", #"/.", #"/@", #"/;", #"/=", #"//", #"//@", #"//.", #"^", #"^=", #"^^", #"^:=", #"^:", #"&", #"&&", #"|", #"||", #"+", #"++", #"+=", #"**", #"*=", #"-", #"->", #"-=", #"--", #"[", #"[[", #"]", #"]]", #"{", #"}", #">", #">=", #">>", #">>>", #"<", #"<=", #"!", #"!!", #"!=", #"#", #"##", #":=", #":>", #"::", #"::=", #":", #"=", #"==", #"===", #"=!=", #".", #"..", #"...", #"\\", #"e-o-l", #"(", #")", #"'", #"@", #"~", #"?", #";", #","); end; // Extension. This allows us to use foo[*,1]*bar[1,*] notationally. // also a * * means (Times a *) // (setf (get '* 'mathtoken t)) // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('_', method (stream, char) select (pc()) '\n' => #(#"blank"); '.' => rc(); #(#"optional", #(#"blank")); '_' => rc(); select (pc()) '\n' => #(#"blanksequence"); '_' => rc(); // ___ (3 of em) if (alpha-char?(pc()) & (next := rt())) list(#"blanknullsequence", next); else #(#"blanknullsequence"); end if; otherwise => // __ (2 of em) if (alpha-char?(pc()) & (next := rt())) list(#"blanksequence", next); else #(#"blanksequence"); end if; end select; otherwise => // _ (1 of em) if (alpha-char?(pc()) & (next := rt())) list(#"blank", next); else #(#"blank"); end if; end select; end method, #f, mathrt); // left paren could start a comment define method sawlpar (stream, char) select (pc()) '*' => // skip to end of comment rc(); commentskip(stream); otherwise => #"("; end select; end method sawlpar; // ) // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('(', sawlpar, #f, mathrt); // ) // the use of the % character is peculiar. // LTD: Function SET-MACRO-CHARACTER not yet implemented. set-macro-character('%', method (stream, char) if (pc() == '%') parse-outform1(2); elseif (digit-char?(pc())) list(#"out", collect-integer(0, 10)); else #(#"out"); end if; end method, #f, mathrt); define method parse-outform1 (counter) // saw more than one % rc(); if (pc() = '%') parse-outform1(1 + counter); // another % else list(#"out", - counter); end if; end method parse-outform1; define method commentskip (stream) block (return) while (#t) x := rc(); if (x == '(') sawlpar(stream, x); elseif (x == '*' & pc() == ')') rc(); // flush the last leftpar return(mread1()); end if; end while; end block; end method commentskip; // end of the lexical analysis part // ---------------------------------------------------------- // The Parser // You can use (p) to try out the parser by typing in from the // keyboard. It sets up the readtable and calls parse-comp. // Reading from lines is set up so that if a sentence ends at // an end-of-line, the parse is completed. Otherwise, the e-o-l // is absorbed and the reading continued. A continuation line // can be forced by a \. (This is Mathematica's usual operation) define variable interactive = #t; // t means 2 eol's ends a command. not for files. // ps will read from a Mathematica stream // print to std output // e.g. (ps (open "foo.text")) define method ps (stream) rt(); while (#t) res := block (endofparse) parse-comp(#t); end block; // end=t means a #\newline will end expr. print(if ('\n' == pc()) rc(); res; // proper ending elseif (z := rt()) begin let _that = #f; if (_that := z = #"e-o-l") _that; // may also be proper ending else format-out("\ngarbage at end of expression:%=\n", z); end if; end; res; end if, *standard-output*); end while; end method ps; define method psm (// (meval (parse ( stream-from-file))) stream(&aux, interactive(nil), res, *readtable*(mathrt), mathbuffer(nil), z)) rt(); while (#t) res := block (endofparse) parse-comp(#t); end block; // end=t means a #\newline will end expr. print(if ('\n' == pc()) rc(); res; // proper ending elseif (z := meval(rt())) // call meval on stuff read in. begin let _that = #f; if (_that := z = #"e-o-l") _that; // may also be proper ending else format-out("\ngarbage at end of expression:%=\n", z); end if; end; res; end if, *standard-output*); end while; end method psm; // mreadl is a debugging loop that just reads lexemes until it reads done define method mreadl () block (return) while (#t) next := mread1(); if (next == #"e-o-l") return(#"done"); end if; print(next, *standard-output*); end while; end block; end method mreadl; // LTD: No macros. #"rt"; // LTD: No macros. #"eolp"; // this function reads a token. Although it looks like it // just reads a lisp s-expression or number, it uses a different // read-table. If mread1 encounters a #\newline, it returns the // atom e-o-l, as specified in the read-table. define method mread1 () // debug (format t "~% next char = ~s" (pc)) if (member?(pc(), #(' ', '\t', '\012'), test: \=)) rc(); mread1(); // fix - 2x bug elseif (digit-char?(pc())) // next character is a digit 0-9 collect-integer(char-to-int(read-element(stream, nil)), 10); // radix 10 default else // LTD: Function READ-PRESERVING-WHITESPACE not yet implemented. read-preserving-whitespace(stream, #f, #"e-o-l") | #"false"; end if; end method mread1; define method p (#key stream = *standard-input*) // (rt) ;;get something in mathbuffer res := block (endofparse) parse-comp(#t); end block; // end=t means a #\newline will end expr. if (mathbuffer == #"e-o-l") if (res) res; else #"null"; end if; // proper ending else format-out("\nUnexpected token at end of expression:%=\n", mathbuffer); res; end if; end method p; define method peek-token () let _that = #f; if (_that := mathbuffer) _that; else mathbuffer := mread1(); end if; end method peek-token; define method parse-nary1 (res, tag) if (empty?(tail(res))) head(res); else pair(tag, reverse!(res)); end if; end method parse-nary1; define method guess-token (guess) if (guess == tok) #t; elseif (#"e-o-l" == tok) rt(); if (interactive & #"e-o-l" == peek-token()) // if two in-a-row; get outta here endofparse(#f); end if; end if; end method guess-token; // a variable is any symbol that looks like a lisp symbol and // is not an integer or string, or a pattern-var define method var-p (token) instance?(token, ) | // case of (blank) (~ instance?(token, ) & ~ (token == #"e-o-l") & (instance?(token, ) | ~ symbol-get-property(token, #"mathtoken"))); end method var-p; // is Head one of the pattern items "blank..." define method blankp (token) ~ not(instance?(token, )) & member?(head(token), #(#"blank", #"blanksequence", #"blanknullsequence", #"optional")); end method blankp; // parse a number define method parse-number (end) // reads floats and radix nums also if (x) if (pc() = '.') // is the very next character a "."? rc(); // remove exactly that character. // note: in Mathematica, 1. 2 is 1.0*2 = 2.0 // 1 .2 is 1*0.2 = 0.2 // 1 . 2 is Dot[1,2] // Now check: Is there a digit next? if (digit-char?(pc())) afterdot := parse-frac(end); if (afterdot) make-real(x, afterdot); // like 12.34 else x; end if; // not a float -> return integer else make-real(x, 0); end if; else x; end if; // x is an integer, but no "." follows // still, we must check for a number of the form .123 elseif (guess-token(#".")) rt(); // is there a digit next? if (digit-char?(pc())) afterdot := parse-frac(end); if (afterdot) make-real(0, afterdot); // like 0.34 else "what's a dot doing here?"; end if; elseif (nil); end if; // we could make it 0? else #f; end if; end method parse-number; // parse an integer, including radix define method parse-int (end) if (instance?(x, )) if (eolp(end)) x; elseif (rt() & pc() == '^' & // don't sop up extra spaces here. what if 1 .2 guess-token(#"^^")) // see if it is, e.g. 8^^101 =65 rt(); if (x > 10 | x < 2) format-out("radix %= ?\n", x); elseif (nil); end if; collect-integer(0, x); else x; end if; // ok, no radix stuff -- just return x else #f; end if; end method parse-int; // parse the fraction part of a decimal number .123 define method parse-frac (end) block (return) while (#t) // since all of the line termination chars are not digits, all we // need to check is for digits.. if (~ (x := digit-char?(pc()))) return(num / den); end if; rc(); // read past the char den := den * 10; num := 10 * num + x; end while; end block; end method parse-frac; // this is a stub until we decide what to really do here define method make-real (x, y) list(#"real", x, y); end method make-real; // parse lists delimited by [] [[]]{} tricky to handle f[g[x]]. define method parse-list (#key op) next := peek-token(); if (next = #"[[") rt(); parselist1(list(op, #"part"), #"]]"); elseif (next = #"[") rt(); parselist1(list(op), #"]"); elseif (next = #"{") rt(); parselist1(list(#"list"), #"}"); end if; end method parse-list; define method parselist1 (sofar, endmark) // we want to find an expression next := peek-token(); let _that = #f; if (next == #",") rt(); // get past the comma parselist1(pair(#f, sofar), endmark); elseif (next == endmark) rt(); // get past the endmark [a,b,] if (empty?(tail(sofar))) sofar; // f[] -> (f) else reverse!(pair(#f, sofar)); end if; elseif (_that := endmark == #"]" & // we might find a '|]]| parse-list-hack(next, pair(#f, sofar))) _that; elseif (next := parse-comp(#f)) // end=nil; can't end with just #\newline parselist2(pair(next, sofar), endmark); else error("parse-list: looking for a comma, expression or endmark"); end if; end method parselist1; define method parse-list-hack (next, sofar) // make f[g[h]] work ok by parsing as // f[g[h] ] if (next = #"]") rt(); reverse!(sofar); elseif (next = #"]]") mathbuffer := #"]"; // one '\] left over for f[g[h]] reverse!(sofar); end if; end method parse-list-hack; define method parselist2 (sofar, endmark) // we want to find , or close mark next := peek-token(); let _that = #f; if (next = #",") rt(); // get past the comma parselist1(sofar, endmark); elseif (next = endmark) rt(); reverse!(sofar); elseif (_that := endmark = #"]" & // we might find a '|]]| parse-list-hack(next, sofar)) _that; else error("parse-list: looking for a comma, expression or endmark"); end if; end method parselist2; // comparison operators symbol-get-property(#"==", #"compop") := #"equal"; symbol-get-property(#"!=", #"compop") := #"unequal"; symbol-get-property(#"<", #"compop") := #"less"; symbol-get-property(#"<=", #"compop") := #"lessequal"; symbol-get-property(#">", #"compop") := #"greater"; symbol-get-property(#">=", #"compop") := #"greaterequal"; symbol-get-property(#"===", #"sameop") := #"sameq"; symbol-get-property(#"=!=", #"sameop") := #"unsameq"; // sample parses. All comparisons of 3 or more items are questionable, // but this is what Mathematica does... // abb==c (Equal (Greater a b) c) ;--- associates to left // a==b==c (Equal a b c) ; meaning (And (Equal a b)(Equal b c)) // but no duplicate evaluation of b; yet // (a==b)==c (Equal (Equal a b) c) ;; not the same -- a==b is True or False // a==(b==c) (Equal a (Equal b c)) // a==b!=c (Unequal (Equal a b) c) // a!=b==c (Equal (Unequal a b) c) // a+b==c (Equal (Plus a b) c) define method parse-or (end) // E::=e1||e2 n-ary if (eolp(end)) temp; elseif (temp) if (guess-token(#"||")) // check first to avoid consing res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"or")); elseif (guess-token(#"||")) rt(); res := pair(parse-and(end), res); else return(parse-nary1(res, #"or")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-or; define method parse-and (end) // E::=e1 && e2 n-ary (And) if (eolp(end)) temp; elseif (temp) if (guess-token(#"&&")) // check first to avoid consing res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"and")); elseif (guess-token(#"&&")) rt(); res := pair(parse-not(end), res); else return(parse-nary1(res, #"and")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-and; define method parse-not (end) if (eolp(end)) #f; elseif (guess-token(#"!")) // Not rt(); list(#"not", parse-not(end)); else parse-same(end); end if; end method parse-not; // this definition does not handle 3-way or more comparisons quite // the same as Mathematica. // a===b is (SameQ a b) but a=!=b===c is (Inequality a SameQ b SameQ c) // rather than (Sameq (UnSameQ a b) c). // reason: probably Mathematica is wrong; probably the feature is unused // and hence un-noticed. define method parse-same (end) // E::=e1 ===e2 etc if (eolp(end)) temp; elseif (temp) op := peek-token(); if (not(instance?(op, )) & symbol-get-property(op, #"sameop")) // check before cons // SameQ res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(patch-equal(parse-nary1(res, #"inequality"))); elseif (not(instance?((op := peek-token()), )) & (op := symbol-get-property(op, #"sameop"))) rt(); res := pair(parse-equal(end), pair(op, res)); else return(patch-equal(parse-nary1(res, #"inequality"))); end if; end while; end block; else temp; end if; else #f; end if; end method parse-same; define method parse-equal (end) // E::=e1 compop e2 n-ary (==, etc) if (eolp(end)) temp; elseif (temp) op := peek-token(); if (not(instance?(op, )) & symbol-get-property(op, #"compop")) // check before cons // Unequal, for example res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(patch-equal(parse-nary1(res, #"inequality"))); elseif (not(instance?((op := peek-token()), )) & (op := symbol-get-property(op, #"compop"))) rt(); res := pair(parse-plus(end), pair(op, res)); else return(patch-equal(parse-nary1(res, #"inequality"))); end if; end while; end block; else temp; end if; else #f; end if; end method parse-equal; define method patch-equal (h) if (size(h) = 4) list(third(h), second(h), cadddr(h)); else h; end if; end method patch-equal; // arithmetic expression define method parse-plus (end) // E::=T1{+T2} | T1{-T2} if (temp) if (eolp(end)) temp; elseif (guess-token(#"+") | guess-token(#"-")) res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"plus")); elseif (guess-token(#"+")) rt(); res := pair(parse-times(end), res); elseif (guess-token(#"-")) rt(); res := pair(begin let h = parse-times(end); if (instance?(h, )) - h; else list(#"times", -1, h); end if; end, res); else return(parse-nary1(res, #"plus")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-plus; define method parse-comp (end) // E::=E;E; | E; if (temp := parse-put(end)) if (eolp(end)) temp; elseif (guess-token(#";")) // check first to avoid consing res := pair(if (temp) temp; else #"null"; end if, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"compoundexpression")); elseif (guess-token(#";")) rt(); res := pair(parse-put(end) | #"null", res); else return(parse-nary1(res, #"compoundexpression")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-comp; define method parse-put (end) // e >> file or e>>>file if (temp) if (eolp(end)) temp; elseif (guess-token(#">>")) rt(); list(#"put", temp, rt()); elseif (guess-token(#">>>")) rt(); list(#"putappend", temp, rt()); else temp; end if; else #f; end if; end method parse-put; // replace is left-assoc e /. e | e//.e // 11/18/94 RJF define method parse-replace (end) if (temp) parse-replace1(temp, end); else #f; end if; end method parse-replace; // formerly // (defun parse-replace( end &aux(temp(parse-rule end))) // (cond (temp (parse-replace1 temp end)) // (t nil))) // define method parse-replace1 (temp, end) if (eolp(end)) temp; elseif (guess-token(#"/.")) rt(); parse-replace1(list(#"replaceall", temp, parse-replace(end)), end); elseif (guess-token(#"//.")) rt(); parse-replace1(list(#"replacerepeated", temp, parse-replace(end)), end); else temp; end if; end method parse-replace1; // added 11/18/94; RJF // I do not know if the precedence implied by this // is entirely accurate wrt Mathematica. define method parse-alternatives (end) // E::=e1 \| e2 n-ary (Alternatives) if (eolp(end)) temp; elseif (temp) if (guess-token(#"|")) // check first to avoid consing res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"alternatives")); elseif (guess-token(#"|")) rt(); res := pair(parse-rule(end), res); else return(parse-nary1(res, #"alternatives")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-alternatives; define method parse-rule (end) // e->(e->e) etc if (temp) if (eolp(end)) temp; elseif (guess-token(#"->")) rt(); list(#"rule", temp, parse-rule(end)); elseif (guess-token(#":>")) rt(); list(#"ruledelayed", temp, parse-rule(end)); else temp; end if; else #f; end if; end method parse-rule; // condition is left-assoc define method parse-condition (end) if (temp) parse-condition1(temp, end); else #f; end if; end method parse-condition; define method parse-condition1 (temp, end) if (eolp(end)) temp; elseif (guess-token(#"/;")) rt(); parse-condition1(list(#"condition", temp, parse-repeated(end)), end); else temp; end if; end method parse-condition1; define method parse-repeated (end) if (temp) if (eolp(end)) temp; elseif (guess-token(#"..")) rt(); list(#"repeated", temp); elseif (guess-token(#"...")) rt(); list(#"repeatednull", temp); else temp; end if; else #f; end if; end method parse-repeated; define method parse-addto (end) // bug noticed by /fixed by lvi@ida.liu.se if (temp) if (eolp(end)) temp; elseif (guess-token(#"+=")) rt(); list(#"addto", temp, parse-addto(end)); elseif (guess-token(#"*=")) rt(); list(#"timesby", temp, parse-addto(end)); elseif (guess-token(#"-=")) rt(); list(#"subtractfrom", temp, parse-addto(end)); elseif (guess-token(#"/=")) rt(); list(#"divideby", temp, parse-addto(end)); else temp; end if; else #f; end if; end method parse-addto; define method parse-set (end) if (temp) if (eolp(end)) temp; elseif (guess-token(#"=")) rt(); if (guess-token(#".")) rt(); list(#"unset", temp); else list(#"set", temp, parse-set(end)); end if; elseif (guess-token(#":=")) rt(); list(#"setdelayed", temp, parse-set(end)); elseif (guess-token(#"^=")) rt(); list(#"upset", temp, parse-set(end)); elseif (guess-token(#"^:=")) rt(); list(#"upsetdelayed", temp, parse-set(end)); elseif (guess-token(#"/:")) rt(); list(#"tagset", temp, parse-set(end)); // actually, Mathematica uses TagSet Delayed, Un. elseif (guess-token(#"::=")) rt(); if (guess-token(#".")) rt(); list(#"unalias", temp); else list(#"alias", temp, parse-set(end)); end if; else temp; end if; else #f; end if; end method parse-set; // f&[a,b] --> ((Function f) a b) define method parse-ampersand (end) if (temp := parse-addto(end)) if (eolp(end)) temp; elseif (peek-token() == #"&") rt(); parse-fun1(list(#"function", temp), end); else temp; end if; else #f; end if; end method parse-ampersand; // left associative e1//e2 define method parse-// (end) if (temp) if (eolp(end)) temp; elseif (guess-token(#"//")) rt(); parse-//1(list(parse-ampersand(end), temp), end); else temp; end if; else #f; end if; end method parse-//; define method parse-//1 (sofar, end) if (eolp(end)) sofar; elseif (guess-token(#"//")) rt(); parse-//1(list(parse-ampersand(end), sofar), end); else sofar; end if; end method parse-//1; define method parse-times (end) // // t::=f1{*f2} | f1{/f2} | f1 f2 if (eolp(end)) temp; elseif (temp) res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"times")); elseif (guess-token(#"*")) rt(); // a * !b+c is (Times a (Not (Plus b c))) res := pair(parse-unary(end), res); elseif (guess-token(#"/")) rt(); res := pair(apply(list, #"power", parse-unary(end), #(-1)), res); // note that a / b c = (a * b^-1 *c) not (a* (b*c)^-1) // this implements the kludge a x = a*x // can't tolerate a +b ==> (Times a b), and +b is b... // hence use parse-power, not parse-not elseif (temp := parse-power(end)) res := pair(temp, res); else return(parse-nary1(res, #"times")); end if; end while; end block; else #f; end if; end method parse-times; define method parse-unary (end) // E::=+T | -T if (guess-token(#"+")) rt(); parse-unary(end); // unary + elseif (guess-token(#"-")) rt(); begin let h = parse-unary(end); if (instance?(h, )) - h; else list(#"times", -1, h); end if; end; elseif (guess-token(#"!")) parse-not(end); // extra added attraction!! 'foo -> (Quote foo) elseif (guess-token(#"'")) rt(); list(#"quote", parse-unary(end)); else parse-power(end); end if; end method parse-unary; define method parse-power (end) // f ::= p^f | p if (temp) if (eolp(end)) temp; elseif (guess-token(#"^")) rt(); list(#"power", temp, parse-unary(end)); // really going up the precedence else temp; end if; else #f; end if; end method parse-power; define method parse-dot (end) // E::=e1 . e2 n-ary dot if (temp) if (eolp(end)) temp; elseif (guess-token(#".")) // check first to avoid consing res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"dot")); elseif (guess-token(#".")) rt(); res := pair(parse-ncm(end), res); else return(parse-nary1(res, #"dot")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-dot; define method parse-ncm (end) // E::=e1 ** e2 n-ary if (temp) if (eolp(end)) temp; elseif (guess-token(#"**")) // check first to avoid consing res := pair(temp, #f); block (return) while (#t) if (eolp(end)) return(parse-nary1(res, #"noncommutativemultiply")); elseif (guess-token(#"**")) rt(); res := pair(parse-fact(end), res); else return(parse-nary1(res, #"noncommutativemultiply")); end if; end while; end block; else temp; end if; else #f; end if; end method parse-ncm; // factorial is left-associative a ! ! means (a!)! define method parse-fact (end) // d ::= m | m! | m!! if (temp) parse-fact1(temp, end); else #f; end if; end method parse-fact; define method parse-fact1 (temp, end) // d ::= m | m! | m!! if (eolp(end)) temp; elseif (guess-token(#"!")) rt(); parse-fact1(list(#"factorial", temp), end); elseif (guess-token(#"!!")) rt(); parse-fact1(list(#"factorial2", temp), end); else temp; end if; end method parse-fact1; define method parse-map (end) // d ::= t | t /@ expr if (eolp(end)) temp; elseif (temp) if (guess-token(#"/@")) rt(); list(#"map", temp, parse-map(end)); elseif (guess-token(#"//@")) rt(); list(#"mapall", temp, parse-map(end)); elseif (guess-token(#"@@")) rt(); list(#"apply", temp, parse-map(end)); else temp; end if; else #f; end if; end method parse-map; define method parse-tilde (sofar, end) if (empty?(sofar)) #f; elseif (eolp(end)) sofar; else if (guess-token(#"~") & rt() & (op := parse-at(#f)) & guess-token(#"~") & rt() & (last := parse-at(end))) parse-tilde(list(op, sofar, last), end); else sofar; end if; end if; end method parse-tilde; define method parse-precrement (end) // look for ++a or --a ;lvi fix for ++ ++ a if (guess-token(#"++")) rt(); list(#"preincrement", parse-precrement(end)); elseif (guess-token(#"--")) rt(); list(#"predecrement", parse-precrement(end)); else parse-fun(end); end if; end method parse-precrement; define method parse-pattest (end) // patterntest is e1?e2 if (temp) if (eolp(end)) temp; elseif (guess-token(#"?")) rt(); list(#"patterntest", temp, parse-var(end)); else temp; end if; else #f; end if; end method parse-pattest; define variable rpar = #")"; define variable lpar = #"("; // parse-optional looks for Optional a_:v is (Optional(Pattern a (Blank)) v) define method parse-optional (end) if (temp) if (eolp(end)) temp; elseif (guess-token(#":")) rt(); if (empty?(temp2 := parse-comp(end))) // 10/28/94 RJF list(#"optional", temp); else list(#"optional", temp, temp2); end if; else temp; end if; else temp; end if; end method parse-optional; // var ::= var_ etc| #var | _ | __ | ___ | patternstuff | var :: string // ( stuff ) | ( a , ....) | { a , ...} | number define method parse-var (end) if (next == #"e-o-l") rt(); next := peek-token(); if (next == #"e-o-l") #f; else parse-var(end); end if; elseif (var-p(next)) rt(); if (eolp(end)) next; elseif (blankp(peek-token())) if (head(peek-token()) == #"optional") // 10/28/94 RJF list(#"optional", apply(list, #"pattern", next, tail(rt()))); else list(#"pattern", next, rt()); end if; elseif (guess-token(#"::")) rt(); list(#"messagename", next, rt()); elseif (guess-token(#":")) rt(); list(#"pattern", next, parse-repeated(end)); else next; end if; elseif (next = lpar) // look for (expr) // actually (a,b,..), a Sequence is not accepted in 2.0, but in 1.2 rt(); next := parse-comp(#f); begin let _that = #f; if (guess-token(rpar)) rt(); next; elseif (_that := parselist2(list(next, #"sequence"), rpar)) _that; else error("too few rpars"); end if; end; elseif (next = #"{") rt(); // look for List if (guess-token(#"}")) rt(); list(#"list"); elseif (next := parse-comp(#f)) // lvi 8/29 parselist2(list(next, #"list"), #"}"); else error("too few right-}"); end if; elseif (next = #"#") parse-slotform(#"slot", end); elseif (next = #"##") parse-slotform(#"slotsequence", end); elseif (next := parse-number(end)) // (if (atom next) (list 'Integer next) next);;tags integers specifically next; else #f; end if; end method parse-var; // # means (Slot 1) ## means (SlotSequence 1) // #2 means (Slot 2) etc. define method parse-slotform (head, end) rt(); // sop up # or ## if (empty?(var := parse-int(end))) pair(head, #(1)); else list(head, var); end if; end method parse-slotform; define method parse-at (end) // collect e1 @ e2 | e++ | e-- if (var) if (eolp(end)) var; elseif (guess-token(#"@")) rt(); list(var, parse-at(end)); elseif (guess-token(#"++")) rt(); list(#"increment", var); elseif (guess-token(#"--")) rt(); list(#"decrement", var); else var; end if; else #f; end if; end method parse-at; // parse-fun collects f[x] or similar; also a++ // it is left-assoc. f[x]=(f x); f[x][y] = ((f x) y) define method parse-fun (end) if (temp) parse-fun1(temp, end); else #f; end if; end method parse-fun; // parser must handle the following cases: // f' --> ((Derivative 1) f) // f'x --> (Times ((Derivative 1) f) x) // f'[x] --> (((Derivative 1) f) x) // f'' --> ((Derivative 2) f) define method parse-fun1 (sofar, end) if (eolp(end)) sofar; // handle the derivative cases elseif (peek-token() == #"'") for (i = 0 then 1+(i), until eolp(end) | ~ guess-token(#"'")) rt(); finally parse-fun1(list(list(#"derivative", i), sofar), end); end for; // handle the function invocation f[x] and part .. f[[1]] elseif (member?(peek-token(), #(#"[", #"[["))) parse-fun1(parse-list(sofar), end); // f[], f[x] or maybe (f[x])[y] etc. else sofar; end if; end method parse-fun1; // some extensions/ modifications // 1. we parse a==b>c as (Inequality a Equal b Greater c) // 2. integers are parsed as (for example) 4, not (Integer 4) ;;optional // (we could do this so we can eventually tag integers with other info // like precision, accuracy, base) // 3. integer args to % and # are just lisp integers. // 4. real numbers like 1.20 are simply (Real 1 20) for the // same reason as for integers. // (Mathematica has such info stashed away in secret) // 5. within " " we allow any number of newlines even interactively. M allows 2 // 6. we count lines consisting only of (*comments*) as newlines // 7 optional.. (commented out) 123`456`789 syntax for long bignumber input // known bugs or features(?) 1/90 // we support radix only between 2 and 10; blame it on laziness // we do not support non-decimal radix flt. pt; blame it on ditto. // we do TagSet slightly differently; ditto // fixed bugs/new features 1/91 -- RJF // typing nil provides the symbol False, not nil. I don't know if // this is a bug or a feature, though. It means that the parser will // not think it has failed to parse a subexpression when it merely // has parsed the symbol nil, so it is convenient, anyway. // Mma has the symbol Null, perhaps for similar reasons. // fixed 1/28/91 // fixed the parsing a_:v of which is now // (Optional (Pattern a (Blank)) v). // fixed the parsing of #1+#2&[a,b] to ((Function (Plus (Slot 1)(Slot 2))) a b) // fixed 2/15/91 parsing of a**b followed by eol // added 2/3/91 // 'a is same as Quote[a]. f' is derivative, though. 'f'a is // (Times (Quote ((Derivative 1)f)) a). This is not in conflict with mma. // added 2/15/91 // the symbol * can be used, in some circumstances, as a variable name. // In those circumstances where it cannot be confused with an operator, // it can be used as a symbol. In some cases it can be used as a symbol // even if YOU confuse it. Advantages: you can use it as a regular-expression // tag like foo[*,3] to denote the 3rd column of a matrix. // You can use * * * to mean (Times * *) although *^2 (Power * 2) also // works. The expressions x * * y and x * * * y mean (Times x * y). // The expression ( * * ) means (Times * *) // BUT NOTE THAT (* ANYTHING *) is A COMMENT !!!! :) // fixed 5/29/91 from lvi@ida.liu.se // fixed parsing of a+=b;c from a=+(b;c) to (a=+b);c. // fixed ++ ++ a also. // 8/29/91 bug fix from lars viklund (lvi@ida,liu.se) // in parse var, replace parse-set by parse-comp (twice) // 11/23/91 bug fix to repair parsing of 1.004 (was same as 1.4) using // parse-frac. This was pointed out by gotoda@is.s.u-tokyo.ac.jp // this next item allows one to do, in lisp, (setq r #mx^2-1 // ) // 10/28/94. Optionals in patterns were not parsing right c_. was // parsing as (Times c (Optional (Blank))). It should be // (Optional(Pattern C (Blank))) // a_: was parsing as (Optional (Pattern a (Blank))nil) instead of // (Optional (Pattern a (Blank))) . fixed. set-dispatch-macro-character('#', 'm', method (stream, sub-char, infix-argument) list(#"quote", p(stream)); end method); // 11/18/94 // had to recompile for new version of allegro common lisp // the Alternatives form was added to mathematica... how about for // this parser? // remove the special meaning of "|" from the emacs reader of lisp code // and change it to inherit from the standard syntax table // "eof";