// -*- Mode:Common-Lisp;Package:mma; Base:10 -*-
// Mathematica(tm)-like evaluator
// copyright (c) 1990 Richard J. Fateman; pieces by Tak Yan, Derek Lai
// LTD: Function LOAD not yet implemented.
load("mma");
"(in-package mma)";
// (provide 'math-eval)(require "ucons1")(require 'math-parser "parser")
// (require "stack1") (require "disp1")(require "pf")(require "simp1")(require "rat1")(require "match")
"(in-package mma)";
define module mma export tl, mread1; end module mma;
// line number for top-level. (same as (meval $Line))
define variable count = 1;
#f;
// environment
#f;
// funnyvars is a hash table containing variables which, when set,
// cause function to be executed
define variable funnyvars = make(
, test: \==, size: 8);
define method tl ()
fluid-bind (*package*
= // LTD: Function FIND-PACKAGE not yet implemented.
find-package(#"mma"))
let h = #f;
let hs = #f;
let hin = #f;
let timesofar = 0;
let timeunit = 1.0 / $internal-time-units-per-second;
let env = make-stack(size: 50);
if (count = 1)
format-out("Mock-Mma (Sun-4) 2.0 (Oct. 3,, 1991) [With pre-loaded data]\n Possibly Copyright 1990-1991 U.C.Berkeley\n -- Terminal graphics initialized -- \n");
end if;
block (return)
while (#t)
timesofar
:= // LTD: Function GET-INTERNAL-RUN-TIME not yet implemented.
get-internal-run-time();
format-out("\nIn[%=] := ", count);
// actually In and Out are variables too.
#"setq"(hin: #"multiple-value-bind"(#"isnoerr"(#"val"),
#"errorset"(p(), #"t"),
#"if"(isnoerr: #"val",
#"clear-input"(#"t"))));
setqq(ulist(#"in", count), hin);
// (setq h (simp(meval hin))) ;; if you don't have errorset, try this.
#"setq"(h: #"multiple-value-bind"(#"isnoerr"(#"val"),
#"errorset"(#"meval"(#"hin"), #"t"),
#"if"(isnoerr: #"val",
(#"hold", #"hin"))));
timesofar
:= // LTD: Function GET-INTERNAL-RUN-TIME not yet implemented.
get-internal-run-time()
- timesofar;
// this is not the same as mathematica but I find it more convenient
// this way. We've also implementing "Timing", if you prefer.
if (#"true" == meval(#"$showtime"))
(method (s, #rest args)
apply(maybe-initiate-xp-printing,
method (xp, #rest args)
begin
pprint-newline+(unconditional: xp);
write-string++("time = ", xp, 0, 7);
using-format(xp, "~3,$", pop!(args));
write-string++(" secs.", xp, 0, 6);
end;
if (args) copy-sequence(args); end if;
end method,
s, args);
end method)(#t, timesofar * timeunit);
end if;
if (h == #"exit" | (instance?(h, ) & head(h) == #"quit"))
format-out("\nExited to Lisp\n");
return(#t);
else
setqq(ulist(#"out", count), h);
if (h == #"null")
#f;
// don't print nil-valued answers
else
hs := list(#"set", ulist(#"out", count), h);
disp(buildformat(hs));
end if;
end if;
// LTD: Function SET not yet implemented.
set(#"$line", count := count + 1);
end while;
end block;
end fluid-bind;
end method tl;
// this definition replaces the program in the parser file
define method mread1 ()
if (member?(pc(), #(' ', '\t', '\012'), test: \=))
rc();
mread1();
elseif (digit-char?(pc()))
// next character is a digit 0-9
collect-integer(char-to-int(read-element(stream, nil)), 10);
// radix 10 default
// for every alphabetic symbol, set up a hash table
else
chash(// LTD: Function READ-PRESERVING-WHITESPACE not yet implemented.
read-preserving-whitespace(stream, #f, #"e-o-l"))
| #"false";
end if;
end method mread1;
// enter a variable in the symbol table by making a hash
// table as its value.
define variable symtab = make(, test: \==, size: 150);
// It's plausible to change this to use defstruct ... then
// make every declared "symbol-table-entry" in lmath a structure
// with (at least) the following data (all now in same hashtable)
// (a) value for the symbol e.g. a=3
// (b) value for expressions with the symbol as head. e.g. a[45]=x+1
// (c) value for the collected attributes of the symbol.
// e.g. Attributes[a] ={Orderless, Protected, Listable}
// (d) value for each of the attributes
// (e) value for function definition "built-in" e.g. numeric cosine
// (f) value for user-defined function rules e.g. a[x_]:= ...
// (g) string for symbol printing (e.g. " + " for Plus)
// (h) formatting information (though could be on "Format")
// (i) left/right operator precedence information
// (j) messages/ documentation
// (k) package? context?
// possible types are (a): symbol; (b): hash-table; (c): list; (d) bit-vector;
// (e) lisp-function-value; (f) list? array? (g) string; (h) program?/
// (i) (two) integers
define method chash (m)
fluid-bind (*package*
= // LTD: Function FIND-PACKAGE not yet implemented.
find-package(#"mma"))
if (~ instance?(m, ))
m;
// ((null m)nil) do we need to check for nil or t? Maybe not.
else
begin
let _that = #f;
if (_that := symtab[m])
_that;
// either it's there or
else
symtab[m] := make(, test: \=, size: 4);
end if;
end;
end if;
m;
end fluid-bind;
end method chash;
// the following stuff is make-shift.
define method head (h)
if (instance?(h, )) head(h); else object-class(h); end if;
end method head;
// the semantics are probably OK as long as only global properties
// are being recorded.
// SetAttribute puts info in two places so that the Attributes can
// be gotten one at a time, and also collectively
// define ClearAttribute similarly
define method setattribute (h, att, #key val = #"true")
h := symtab[h];
h[att] := val;
h[#"attributes"] := add!(att, h[#"attributes"]);
pair(#"list", h[#"attributes"]);
end method setattribute;
define method attributes (h)
pair(#"list", symtab[h][#"attributes"]);
end method attributes;
// Assignment statements treat the lhs with partial evaluation.
// For a non-atomic Head, evaluate all the arguments, but not
// the head. Presumably this should check attributes of the Head
// like HoldAll, HoldFirst, etc. We don't do that yet.
// we evaluate the lhs partially and then the rhs.
define method holdallp (h)
member?(h, #(#"timing", #"setqq", #"setdelayed", #"if", #"quote"));
end method holdallp;
// for now
// we'd like to have a Quote operator, but the repeated evaluation rule
// makes it almost impossible to work unless we check for it specially..
// alternatively, we can set *evalonce* to t, and (vastly)
// change the semantics. Sometimes this vast change is no change at all....
define method holdfirstp (h) member?(h, #(#"set")); end method holdfirstp;
// for now
// evaluate args, depending on the hold-specs of the head
define method mevalargs (head, l)
if (holdallp(head))
l;
elseif (holdfirstp(head))
ucons(head(l), umapcar(meval, tail(l)));
else
umapcar(meval, l);
end if;
end method mevalargs;
// note that the name of this function conflicts with that
// of the lisp function set, unless
// (a) capitalization is observed OR
// (b) the package system is protecting it..
define method set (lhs, rhs)
// lhs=rhs
// the value associated with the lhs will be stored
// in the symbol table symtab, with the key h,
// which is either the head of the lhs,
// or the lhs itself. That is f=45 is stored in the
// hash table for f, under the indicator "f"
// and f[0]=7 is stored in the hash table for f, under
// the indicator (0).
if (instance?(lhs, ))
h := lhs;
else
lhs := mevalargs((h := head(lhs)), tail(lhs));
end if;
// (format t "Set ~s to ~s~%" h rhs)
// this stores the material in the hash table.
// QUESTION: M'ma doesn't do this, but we could, by storing
// stuff on a local environment... f[x_,y_]:=Block[{},x=y+1];
// what if (gethash h symtab) is a matrix, and this is a valid matrix
// setting? Then we should try to store the value in the array.
// This is insufficient error checking but...
if (instance?(h, )
& // is value of h an array?
matrix-p((fun := symtab[h][h]))
& // are subscripts integers in the right range?
every?(method (n, r)
(instance?(n, ) & n > 0 & n <= r);
end method,
lhs, matrix-dimensions(fun)))
// ok, then set the value in the array. Sorry it took so long.
matrix-a(fun)[head(lhs) - 1, second(lhs) - 1] := (rhs := meval(rhs));
else
// else
symtab[h][lhs] := (rhs := meval(rhs));
end if;
// Next, check for special variables which, when set, cause other
// things to happen. E.g. Precision= number means, in the
// bigfloat system, (bigfloat-init number) gets executed.
if (h := funnyvars[h]) h(rhs); end if;
rhs;
end method set;
// there is another file (nmatrix) that defines a matrix type..
// this should be tied in to the matrix stuff from franz, perhaps.
// also, we have to decide which bigfloat to use... mpfun or rjf's
// old bfstuff.
define method matrix-p (x) #f; end method matrix-p;
// for now, this will have to do.
define method setqq (lhs, rhs)
// lhs=rhs, but don't mevaluate either.
h
:= if (not(instance?(lhs, )))
lhs;
else
begin let _ = head(lhs); (lhs := tail(lhs)); _; end;
end if;
symtab[h][lhs] := rhs;
if (h := funnyvars[h]) h(rhs); end if;
rhs;
end method setqq;
define method setdelayed (lhs, rhs)
let spot = symtab[head(lhs)];
let rs = element(spot, #"setdelayed", default: #"emptyruleset");
let k = #f;
if (rs == #"emptyruleset")
spot[#"setdelayed"] := list(pair(lhs, rhs));
// check to see if lhs is equal to one of the other items already
// stored.
elseif (k := cl-assoc(lhs, rs, test: \=))
begin
let _that = #f;
if (_that := tail(k) = rhs)
_that;
// just redefining same rule
else
format-out("Replacing previous rule %= \n", lhs);
tail(k) := rhs;
end if;
end;
else
push!(pair(lhs, rhs), spot[#"setdelayed"]);
end if;
#"null";
end method setdelayed;
// this assumes the value of a mathematica symbol is its lisp value
// if it is simply a constant or non-hash-table. That means that
// a lisp dynamically bound variable could be used to block access
// to the globally bound variable of the same name. Better not
// use local-variable names like Sin or Cos unless you mean to
// inhibit access to the global names.
define method meval-atom (h)
if (env) h := sfind(env, h); elseif (nil); end if;
// if we find it here on the env stack, do we continue?
// For now, we continue evaluating..
if (~ instance?(h, ))
h;
else
begin
let r = element(symtab, h, default: h);
if (instance?(r, ))
element(r, h, default: h);
// h is default if missing
else
r;
end if;
end;
end if;
end method meval-atom;
// look up the value of e on e's hashtable, key e
// look up the value of e[10] on e's hashtable, key (e 10)
define method msymbol-value (h)
let tabentry = #f;
if (instance?(h, )
& instance?((tabentry := element(symtab, h, default: h)), ))
tabentry[h];
elseif (not(instance?(h, )))
h;
// an atom, not in the symbol table. Where did it come from?
// must be a cons
elseif (constant?(head(h)))
h;
elseif (tabentry := symtab[head(h)])
element(tabentry, h, default: h);
else
h;
end if;
end method msymbol-value;
define method msymbol-function (h)
symtab[h][#"setdelayed"];
end method msymbol-function;
// is this going to have the right scope?
//
// must check for HoldAll, HoldFirst etc.
// ----end of makeshift definitions
define method mapply (h, args, expr, env)
let fun = #f;
// get info on evaluating arguments and do it to args
args := mevalargs(h, args);
if (constant?(h))
expr;
// allows any lisp function, almost, to slip through
// check for constant values pre-stored
elseif (~ instance?(h, ))
expr := pair(h, args);
elseif (~ symtab[h])
expr := pair(h, args);
elseif (let (val, found) = symtab[h][args];
if (found) expr := val; else #f; end if)
#t;
// check for array...
elseif (instance?(h, ) & matrix-p((fun := symtab[h][h]))
& // is value of h an array
every?(method (n, r) (n > 0 & n <= r); end method, args,
matrix-dimensions(fun)))
expr := matrix-a(fun)[head(args) - 1, second(args) - 1];
// next check for user-defined function
elseif (fun := msymbol-function(h))
expr := rulesetapply(h, fun, args);
// next check for built-in LISP function
// (clearly not something that Mathematica does)
elseif (instance?(h, )
& // LTD: Function FBOUNDP not yet implemented.
fboundp(h))
// (format t "~% applying a lisp function ~s" h)
expr := apply(h, args);
else
expr := ucons(h, args);
end if;
// what next?
expr;
end method mapply;
#f;
define method rulesetapply (phead, rules, args)
// get attributes of phead and manipulate args
// for now, assume evaluate all args, ignore Hold* attributes
args := mevalargs(phead, args);
let ptr = stack-ptr(env);
let isflat = flatp(phead);
let isorderless = orderlessp(phead);
let isfol = isflat & isorderless;
let origfn = phead;
let res = #f;
if (isflat) #f; else phead := #"sequence"; end if;
block (return)
for (r = rules then cdr(rules), until empty?(r))
let thisrule = head(r);
let condit = #t;
let lhs = head(thisrule);
let rhs = tail(thisrule);
// Note: if the rule was
// f[a_,b_]:= g[a,b] /; a>b, the parsed result is
// (SetDelayed (f ..) (Condition (g a b) (Greater a b)))
// see if there is a Condition on the rhs of the rule
// e.g. (Condition (foo a b) (Greater a b))
if (instance?(rhs, ) & head(rhs) == #"condition")
condit := third(rhs);
// condit = (Greater a b)
rhs := second(rhs);
elseif (nil);
end if;
// rhs = (foo a b)
// (format t "~%lhs= ~s ~%rhs= ~s ~%condition =~s" lhs rhs condit)
// test for matching
if (if (isorderless)
mlistol(cdaar(r), args, 0, size(args), condit);
else
mlist(cdaar(r), args, #f, condit);
end if)
// if the appropriate matcher succeeded, then
// with the environment env from match, evaluate rhs.
res := meval(rhs);
// restore the environment
stack-ptr(env) := ptr;
// get out of the do-loop
return(res);
elseif (nil);
end if;
finally
// no more rules to try -- return original
ucons(origfn, args);
end for;
end block;
end method rulesetapply;
// Major evaluation function for an expression
// see Mathematica book p 568
define method meval-to-function (x) x; end method meval-to-function;
// don't evaluate function name
define variable *evalonce* = #f;
// should be t to make quote (etc etc) work
define method meval (e)
let saved = e;
e
:= if (not(instance?(e, )))
meval-atom(e);
// check off other constant expressions that don't evaluate.
// perhaps Messages?
// ((patternobjectp e) e) .. What about Patterns?
// (mapply (car foo)(cdr foo) foo) ==> foo with no conses...
else
(res := mapply(meval-to-function(head(e)), tail(e), e, env));
end if;
// note the 3rd arg to mapply, just in case you want to
// return the expression as the result without any change.
// next step --
//
// do we keep evaluating until there is no change???
if (*evalonce*)
e;
elseif (e = saved)
e;
//
else
meval(e);
end if;
end method meval;
// Each global object X is associated with a hash table
// and we can, for each,
// to get the value, do (gethash X X), (gethash 'rules X) etc.
// Local bindings hide everything.
// Do we want to do infinite evaluation?
// How do we keep single copies of items in memory?
// set up initial symbol-table entries for built-in stuff
// should also set attributes
begin do(chash, built-in-syms); built-in-syms; end;
// All system-level $-stuff can be initialized and stored this way
define method globinit (name, val)
chash(name);
// just in case it isn't already there
symtab[name][name] := val;
end method globinit;
globinit(#"$line", 1);
globinit(#"false", #f);
globinit(#"i", 0 + 1 * $i);
// imaginary unit I^2 = -1.
// simple debugging tool: examine contents of symtable entry
define method showhash (x)
do(method (key, val) format-out("\nkey=%=, val=%=", key, val); end method,
key-sequence(x), x);
end method showhash;
// Attributes that evaluation routines use:
// - Flattened [associative, flatten out nested expressions]
// - Orderless [commutative, put args in order]
// - HoldFirst [don't evaluate first arg yet]
// - HoldRest [only evaluate first arg now]
// - HoldAll [don't evaluate any args now]
// - Procedure [procedure to call to do actual evaluation]
// - Default
// [default value for optional variables]
setattribute(#"plus", #"flattened");
setattribute(#"plus", #"orderless");
setattribute(#"plus", #"default", 0);
setattribute(#"times", #"flattened");
setattribute(#"times", #"orderless");
setattribute(#"times", #"default", 1);
setattribute(#"power", #"default", 1);
setattribute(#"and", #"holdall");
// short-circuiting
setattribute(#"or", #"holdall");
setattribute(#"if", #"holdrest");
setattribute(#"condition", #"holdrest");
setattribute(#"set", #"holdfirst");
// we don't use this -- Set is in Lisp
setattribute(#"setdelayed", #"holdall");
setattribute(#"upset", #"holdfirst");
setattribute(#"upsetdelayed", #"holdall");
setattribute(#"tagset", #"holdfirst");
setattribute(#"tagsetdelayed", #"holdall");
setattribute(#"replaceall", #"holdfirst");
setattribute(#"replacerepeated", #"holdrest");
setattribute(#"rule", #"holdfirst");
setattribute(#"ruledelayed", #"holdall");
// convert all real numbers to exact rational numbers
define method real (a, b) a + b; end method real;
// this works only for integer x, x>0
define method decimalsin (x)
ceiling(// LTD: Function INTEGER-LENGTH not yet implemented.
integer-length(x)
* 0.30102999566398114D0);
end method decimalsin;
// handle %, %%, etc.
define method out (#rest n)
symtab[#"out"][ucons(if (empty?(n))
count - 1;
elseif (negative?(n := head(n)))
count + n;
else
n;
end if,
#f)];
end method out;
define method simp (x) simp(x); end method simp;
// rational simplification
define method rat (x) into-rat(x); end method rat;
// leave the answer in rational form.
define method unrat (x) outof-rat(x); end method unrat;
// convert the answer to list form.
// convert u to a rational with a single polynomial numerator
// and denominator. That is (x+1)^2 will be multiplied out.
// result in rational form.
define method ratexpand (u)
fluid-bind (*expand* = #t)
let x = into-rat(u);
make-rat(numerator: make-fpe(fpe-expand(rat-numerator(x)), 1),
denominator: make-fpe(fpe-expand(rat-denominator(x)), 1));
end fluid-bind;
end method ratexpand;
// pick out parts of an expression. x[[y]] parses to Part[x,y].
// (a+r+b^c) [[3,2]] is c.
// Generalizes somewhat in that (a+b^c)[[2,r]] returns (b^c)[[r]].
// Does not handle negative part-numbers or lists of parts as
// done in Mathematica. Also, won't decompose defstruct items
// unless we do something about it for each structure...
define method part (u, #rest k) part1(u, k); end method part;
define method part1 (u, kl)
if (empty?(kl))
u;
elseif (instance?(head(kl), ) & size(u) <= head(kl))
part1(u[head(kl)], tail(kl));
// leave unevaluated part if can't handle it.
else
ucons(#"part", ucons(u, kl));
end if;
end method part1;
// basic simplification of Times
define method times (#rest x)
block (return-from-times)
for (h in x)
// body
if (instance?(h, ))
nums := nums * h;
// collect CL numbers
// if you find a rat, break out !
elseif (instance?(h, ))
return-from-times(reduce-rat(rat*, into-rat(head(x)), tail(x)));
else
push!(h, oths);
end if;
finally
// iterate with h running over all args
// resultform
if (1 = nums)
if (empty?(oths))
1;
else
ucons(#"times", uniq(reverse!(oths)));
end if;
elseif (empty?(oths))
nums;
else
ucons(#"times", ucons(nums, uniq(reverse!(oths))));
end if;
end for;
end block;
end method times;
// this definition allows a=c; to take effect and return Null, so no display
define method compoundexpression (#rest x)
for (i = x then cdr(i), until empty?(i))
// evaluate each element in turn, return last.
ans := meval(head(i));
finally
ans;
end for;
end method compoundexpression;
define method plus (#rest x)
block (return-from-plus)
for (h in x)
// body
if (instance?(h, ))
inc!(nums, h);
// if a rat form, break out!
elseif (instance?(h, ))
return-from-plus(reduce-rat(rat+, into-rat(head(x)), tail(x)));
else
push!(h, oths);
end if;
finally
// iterate with h running over all args
// resultform
if (zero?(nums))
if (empty?(oths))
0;
else
ucons(#"plus", uniq(reverse!(oths)));
end if;
elseif (empty?(oths))
nums;
else
ucons(#"plus", ucons(nums, uniq(reverse!(oths))));
end if;
end for;
end block;
end method plus;
define method power (b, e)
if (instance?(b, ) & instance?(e, ))
b ^ e;
elseif (instance?(b, ) & instance?(e, ))
into-rat(list(#"power", b, e));
else
ulist(#"power", b, e);
end if;
end method power;
// note: Timing is a HoldAll function... otherwise the evaluation
// of the argument would come first, and the timing would
// be of an already evaluated expression.
define method timing (x)
let timeunit = 1.0 / $internal-time-units-per-second;
let timesofar
= // LTD: Function GET-INTERNAL-RUN-TIME not yet implemented.
get-internal-run-time();
let result = meval(x);
list(#"list",
apply(list, #"times",
(// LTD: Function GET-INTERNAL-RUN-TIME not yet implemented.
get-internal-run-time()
- timesofar)
* timeunit,
#(#"second")),
result);
end method timing;