717 lines
16 KiB
CoffeeScript
717 lines
16 KiB
CoffeeScript
# Evaluate an expression, for example...
|
|
#
|
|
# push(p1)
|
|
# Eval()
|
|
# p2 = pop()
|
|
|
|
|
|
|
|
Eval = ->
|
|
check_esc_flag()
|
|
save()
|
|
p1 = pop()
|
|
if !p1?
|
|
debugger
|
|
|
|
if !evaluatingAsFloats and isfloating(p1)
|
|
willEvaluateAsFloats = true
|
|
evaluatingAsFloats++
|
|
|
|
switch (p1.k)
|
|
when CONS
|
|
Eval_cons()
|
|
when NUM
|
|
if evaluatingAsFloats
|
|
push_double(convert_rational_to_double(p1))
|
|
else
|
|
push(p1)
|
|
when DOUBLE, STR
|
|
push(p1)
|
|
when TENSOR
|
|
Eval_tensor()
|
|
when SYM
|
|
Eval_sym()
|
|
else
|
|
stop("atom?")
|
|
|
|
if willEvaluateAsFloats
|
|
evaluatingAsFloats--
|
|
|
|
restore()
|
|
|
|
Eval_sym = ->
|
|
|
|
# note that function calls are not processed here
|
|
# because, since they have an argument (at least an empty one)
|
|
# they are actually CONs, which is a branch of the
|
|
# switch before the one that calls this function
|
|
|
|
# bare keyword?
|
|
# If it's a keyword, then we don't look
|
|
# at the binding array, because keywords
|
|
# are not redefinable.
|
|
if (iskeyword(p1))
|
|
push(p1)
|
|
push(symbol(LAST))
|
|
list(2)
|
|
Eval()
|
|
return
|
|
else if (p1 == symbol(PI) and evaluatingAsFloats)
|
|
push_double(Math.PI)
|
|
return
|
|
|
|
# Evaluate symbol's binding
|
|
p2 = get_binding(p1)
|
|
if DEBUG then console.log "looked up: " + p1 + " which contains: " + p2
|
|
|
|
push(p2)
|
|
|
|
|
|
# differently from standard Lisp,
|
|
# here the evaluation is not
|
|
# one-step only, rather it keeps evaluating
|
|
# "all the way" until a symbol is
|
|
# defined as itself.
|
|
# Uncomment these two lines to get Lisp
|
|
# behaviour (and break most tests)
|
|
if (p1 != p2)
|
|
|
|
# detect recursive lookup of symbols, which would otherwise
|
|
# cause a stack overflow.
|
|
# Note that recursive functions will still work because
|
|
# as mentioned at the top, this method doesn't look
|
|
# up and evaluate function calls.
|
|
positionIfSymbolAlreadyBeingEvaluated = chainOfUserSymbolsNotFunctionsBeingEvaluated.indexOf(p1)
|
|
if positionIfSymbolAlreadyBeingEvaluated != -1
|
|
cycleString = ""
|
|
for i in [positionIfSymbolAlreadyBeingEvaluated...chainOfUserSymbolsNotFunctionsBeingEvaluated.length]
|
|
cycleString += chainOfUserSymbolsNotFunctionsBeingEvaluated[i].printname + " -> "
|
|
cycleString += p1.printname
|
|
|
|
stop("recursive evaluation of symbols: " + cycleString)
|
|
return
|
|
|
|
chainOfUserSymbolsNotFunctionsBeingEvaluated.push(p1)
|
|
|
|
|
|
Eval()
|
|
|
|
chainOfUserSymbolsNotFunctionsBeingEvaluated.pop()
|
|
|
|
Eval_cons = ->
|
|
|
|
cons_head = car(p1)
|
|
|
|
# normally the cons_head is a symbol,
|
|
# but sometimes in the case of
|
|
# functions we don't have a symbol,
|
|
# we have to evaluate something to get to the
|
|
# symbol. For example if a function is inside
|
|
# a tensor, then we need to evaluate an index
|
|
# access first to get to the function.
|
|
# In those cases, we find an EVAL here,
|
|
# so we proceed to EVAL
|
|
if car(cons_head) == symbol(EVAL)
|
|
Eval_user_function()
|
|
return
|
|
|
|
# If we didn't fall in the EVAL case above
|
|
# then at this point we must have a symbol.
|
|
if (!issymbol(cons_head))
|
|
stop("cons?")
|
|
|
|
switch (symnum(cons_head))
|
|
when ABS then Eval_abs()
|
|
when ADD then Eval_add()
|
|
when ADJ then Eval_adj()
|
|
when AND then Eval_and()
|
|
when ARCCOS then Eval_arccos()
|
|
when ARCCOSH then Eval_arccosh()
|
|
when ARCSIN then Eval_arcsin()
|
|
when ARCSINH then Eval_arcsinh()
|
|
when ARCTAN then Eval_arctan()
|
|
when ARCTANH then Eval_arctanh()
|
|
when ARG then Eval_arg()
|
|
when ATOMIZE then Eval_atomize()
|
|
when BESSELJ then Eval_besselj()
|
|
when BESSELY then Eval_bessely()
|
|
when BINDING then Eval_binding()
|
|
when BINOMIAL then Eval_binomial()
|
|
when CEILING then Eval_ceiling()
|
|
when CHECK then Eval_check()
|
|
when CHOOSE then Eval_choose()
|
|
when CIRCEXP then Eval_circexp()
|
|
when CLEAR then Eval_clear()
|
|
when CLEARALL then Eval_clearall()
|
|
when CLEARPATTERNS then Eval_clearpatterns()
|
|
when CLOCK then Eval_clock()
|
|
when COEFF then Eval_coeff()
|
|
when COFACTOR then Eval_cofactor()
|
|
when CONDENSE then Eval_condense()
|
|
when CONJ then Eval_conj()
|
|
when CONTRACT then Eval_contract()
|
|
when COS then Eval_cos()
|
|
when COSH then Eval_cosh()
|
|
when DECOMP then Eval_decomp()
|
|
when DEGREE then Eval_degree()
|
|
when DEFINT then Eval_defint()
|
|
when DENOMINATOR then Eval_denominator()
|
|
when DERIVATIVE then Eval_derivative()
|
|
when DET then Eval_det()
|
|
when DIM then Eval_dim()
|
|
when DIRAC then Eval_dirac()
|
|
when DIVISORS then Eval_divisors()
|
|
when DO then Eval_do()
|
|
when DOT then Eval_inner()
|
|
when DRAW then Eval_draw()
|
|
when DSOLVE then Eval_dsolve()
|
|
when EIGEN then Eval_eigen()
|
|
when EIGENVAL then Eval_eigenval()
|
|
when EIGENVEC then Eval_eigenvec()
|
|
when ERF then Eval_erf()
|
|
when ERFC then Eval_erfc()
|
|
when EVAL then Eval_Eval()
|
|
when EXP then Eval_exp()
|
|
when EXPAND then Eval_expand()
|
|
when EXPCOS then Eval_expcos()
|
|
when EXPSIN then Eval_expsin()
|
|
when FACTOR then Eval_factor()
|
|
when FACTORIAL then Eval_factorial()
|
|
when FACTORPOLY then Eval_factorpoly()
|
|
when FILTER then Eval_filter()
|
|
when FLOATF then Eval_float()
|
|
when APPROXRATIO then Eval_approxratio()
|
|
when FLOOR then Eval_floor()
|
|
when FOR then Eval_for()
|
|
# this is invoked only when we
|
|
# evaluate a function that is NOT being called
|
|
# e.g. when f is a function as we do
|
|
# g = f
|
|
when FUNCTION then Eval_function_reference()
|
|
when GAMMA then Eval_gamma()
|
|
when GCD then Eval_gcd()
|
|
when HERMITE then Eval_hermite()
|
|
when HILBERT then Eval_hilbert()
|
|
when IMAG then Eval_imag()
|
|
when INDEX then Eval_index()
|
|
when INNER then Eval_inner()
|
|
when INTEGRAL then Eval_integral()
|
|
when INV then Eval_inv()
|
|
when INVG then Eval_invg()
|
|
when ISINTEGER then Eval_isinteger()
|
|
when ISPRIME then Eval_isprime()
|
|
when LAGUERRE then Eval_laguerre()
|
|
# when LAPLACE then Eval_laplace()
|
|
when LCM then Eval_lcm()
|
|
when LEADING then Eval_leading()
|
|
when LEGENDRE then Eval_legendre()
|
|
when LOG then Eval_log()
|
|
when LOOKUP then Eval_lookup()
|
|
when MOD then Eval_mod()
|
|
when MULTIPLY then Eval_multiply()
|
|
when NOT then Eval_not()
|
|
when NROOTS then Eval_nroots()
|
|
when NUMBER then Eval_number()
|
|
when NUMERATOR then Eval_numerator()
|
|
when OPERATOR then Eval_operator()
|
|
when OR then Eval_or()
|
|
when OUTER then Eval_outer()
|
|
when PATTERN then Eval_pattern()
|
|
when PATTERNSINFO then Eval_patternsinfo()
|
|
when POLAR then Eval_polar()
|
|
when POWER then Eval_power()
|
|
when PRIME then Eval_prime()
|
|
when PRINT then Eval_print()
|
|
when PRINT2DASCII then Eval_print2dascii()
|
|
when PRINTFULL then Eval_printcomputer()
|
|
when PRINTLATEX then Eval_printlatex()
|
|
when PRINTLIST then Eval_printlist()
|
|
when PRINTPLAIN then Eval_printhuman()
|
|
when PRODUCT then Eval_product()
|
|
when QUOTE then Eval_quote()
|
|
when QUOTIENT then Eval_quotient()
|
|
when RANK then Eval_rank()
|
|
when RATIONALIZE then Eval_rationalize()
|
|
when REAL then Eval_real()
|
|
when ROUND then Eval_round()
|
|
when YYRECT then Eval_rect()
|
|
when ROOTS then Eval_roots()
|
|
when SETQ then Eval_setq()
|
|
when SGN then Eval_sgn()
|
|
when SILENTPATTERN then Eval_silentpattern()
|
|
when SIMPLIFY then Eval_simplify()
|
|
when SIN then Eval_sin()
|
|
when SINH then Eval_sinh()
|
|
when SHAPE then Eval_shape()
|
|
when SQRT then Eval_sqrt()
|
|
when STOP then Eval_stop()
|
|
when SUBST then Eval_subst()
|
|
when SUM then Eval_sum()
|
|
when SYMBOLSINFO then Eval_symbolsinfo()
|
|
when TAN then Eval_tan()
|
|
when TANH then Eval_tanh()
|
|
when TAYLOR then Eval_taylor()
|
|
when TEST then Eval_test()
|
|
when TESTEQ then Eval_testeq()
|
|
when TESTGE then Eval_testge()
|
|
when TESTGT then Eval_testgt()
|
|
when TESTLE then Eval_testle()
|
|
when TESTLT then Eval_testlt()
|
|
when TRANSPOSE then Eval_transpose()
|
|
when UNIT then Eval_unit()
|
|
when ZERO then Eval_zero()
|
|
else
|
|
Eval_user_function()
|
|
|
|
Eval_binding = ->
|
|
push(get_binding(cadr(p1)))
|
|
|
|
### check =====================================================================
|
|
|
|
Tags
|
|
----
|
|
scripting, JS, internal, treenode, general concept
|
|
|
|
Parameters
|
|
----------
|
|
p
|
|
|
|
General description
|
|
-------------------
|
|
Returns whether the predicate p is true/false or unknown:
|
|
0 if false, 1 if true or remains unevaluated if unknown.
|
|
Note that if "check" is passed an assignment, it turns it into a test,
|
|
i.e. check(a = b) is turned into check(a==b)
|
|
so "a" is not assigned anything.
|
|
Like in many programming languages, "check" also gives truthyness/falsyness
|
|
for numeric values. In which case, "true" is returned for non-zero values.
|
|
Potential improvements: "check" can't evaluate strings yet.
|
|
|
|
###
|
|
|
|
Eval_check = ->
|
|
# check the argument
|
|
checkResult = isZeroLikeOrNonZeroLikeOrUndetermined cadr(p1)
|
|
|
|
if !checkResult?
|
|
# returned null: unknown result
|
|
# leave the whole check unevalled
|
|
push p1
|
|
else
|
|
# returned 1 or 0
|
|
push_integer(checkResult)
|
|
|
|
|
|
Eval_det = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
det()
|
|
|
|
|
|
### dim =====================================================================
|
|
|
|
Tags
|
|
----
|
|
scripting, JS, internal, treenode, general concept
|
|
|
|
Parameters
|
|
----------
|
|
m,n
|
|
|
|
General description
|
|
-------------------
|
|
Returns the cardinality of the nth index of tensor "m".
|
|
|
|
###
|
|
|
|
Eval_dim = ->
|
|
#int n
|
|
push(cadr(p1))
|
|
Eval()
|
|
p2 = pop()
|
|
if (iscons(cddr(p1)))
|
|
push(caddr(p1))
|
|
Eval()
|
|
n = pop_integer()
|
|
else
|
|
n = 1
|
|
if (!istensor(p2))
|
|
push_integer(1) # dim of scalar is 1
|
|
else if (n < 1 || n > p2.tensor.ndim)
|
|
push(p1)
|
|
else
|
|
push_integer(p2.tensor.dim[n - 1])
|
|
|
|
Eval_divisors = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
divisors()
|
|
|
|
### do =====================================================================
|
|
|
|
Tags
|
|
----
|
|
scripting, JS, internal, treenode, general concept
|
|
|
|
Parameters
|
|
----------
|
|
a,b,...
|
|
|
|
General description
|
|
-------------------
|
|
Evaluates each argument from left to right. Returns the result of the last argument.
|
|
|
|
###
|
|
|
|
Eval_do = ->
|
|
push(car(p1))
|
|
p1 = cdr(p1)
|
|
while (iscons(p1))
|
|
pop()
|
|
push(car(p1))
|
|
Eval()
|
|
p1 = cdr(p1)
|
|
|
|
Eval_dsolve = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
push(caddr(p1))
|
|
Eval()
|
|
push(cadddr(p1))
|
|
Eval()
|
|
dsolve()
|
|
|
|
# for example, Eval(f,x,2)
|
|
|
|
Eval_Eval = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
p1 = cddr(p1)
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
Eval()
|
|
push(cadr(p1))
|
|
Eval()
|
|
subst()
|
|
p1 = cddr(p1)
|
|
Eval()
|
|
|
|
# exp evaluation: it replaces itself with
|
|
# a POWER(E,something) node and evals that one
|
|
Eval_exp = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
exponential()
|
|
|
|
Eval_factorial = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
factorial()
|
|
|
|
Eval_factorpoly = ->
|
|
p1 = cdr(p1)
|
|
push(car(p1))
|
|
Eval()
|
|
p1 = cdr(p1)
|
|
push(car(p1))
|
|
Eval()
|
|
factorpoly()
|
|
p1 = cdr(p1)
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
Eval()
|
|
factorpoly()
|
|
p1 = cdr(p1)
|
|
|
|
Eval_hermite = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
push(caddr(p1))
|
|
Eval()
|
|
hermite()
|
|
|
|
Eval_hilbert = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
hilbert()
|
|
|
|
Eval_index = ->
|
|
h = tos
|
|
orig = p1
|
|
|
|
# look into the head of the list,
|
|
# when evaluated it should be a tensor
|
|
p1 = cdr(p1)
|
|
push car(p1)
|
|
Eval()
|
|
theTensor = stack[tos-1]
|
|
|
|
if isNumericAtom(theTensor)
|
|
stop("trying to access a scalar as a tensor")
|
|
|
|
if !istensor(theTensor)
|
|
# the tensor is not allocated yet, so
|
|
# leaving the expression unevalled
|
|
moveTos h
|
|
push orig
|
|
return
|
|
|
|
# we examined the head of the list which
|
|
# was the tensor, now look into
|
|
# the indexes
|
|
p1 = cdr(p1)
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
Eval()
|
|
if !isintegerorintegerfloat(stack[tos-1])
|
|
# index with something other than
|
|
# an integer
|
|
moveTos h
|
|
push orig
|
|
return
|
|
p1 = cdr(p1)
|
|
index_function(tos - h)
|
|
|
|
Eval_inv = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
inv()
|
|
|
|
Eval_invg = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
invg()
|
|
|
|
Eval_isinteger = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
p1 = pop()
|
|
if (isrational(p1))
|
|
if (isinteger(p1))
|
|
push(one)
|
|
else
|
|
push(zero)
|
|
return
|
|
if (isdouble(p1))
|
|
n = Math.floor(p1.d)
|
|
if (n == p1.d)
|
|
push(one)
|
|
else
|
|
push(zero)
|
|
return
|
|
push_symbol(ISINTEGER)
|
|
push(p1)
|
|
list(2)
|
|
|
|
Eval_number = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
p1 = pop()
|
|
if (p1.k == NUM || p1.k == DOUBLE)
|
|
push_integer(1)
|
|
else
|
|
push_integer(0)
|
|
|
|
Eval_operator = ->
|
|
h = tos
|
|
push_symbol(OPERATOR)
|
|
p1 = cdr(p1)
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
Eval()
|
|
p1 = cdr(p1)
|
|
list(tos - h)
|
|
|
|
# quote definition
|
|
Eval_quote = ->
|
|
push(cadr(p1))
|
|
|
|
# rank definition
|
|
Eval_rank = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
p1 = pop()
|
|
if (istensor(p1))
|
|
push_integer(p1.tensor.ndim)
|
|
else
|
|
push(zero)
|
|
|
|
# Evaluates the right side and assigns the
|
|
# result of the evaluation to the left side.
|
|
# It's called setq because it stands for "set quoted" from Lisp,
|
|
# see:
|
|
# http://stackoverflow.com/questions/869529/difference-between-set-setq-and-setf-in-common-lisp
|
|
# Note that this also takes case of assigning to a tensor
|
|
# element, which is something that setq wouldn't do
|
|
# in list, see comments further down below.
|
|
|
|
# Example:
|
|
# f = x
|
|
# // f evaluates to x, so x is assigned to g really
|
|
# // rather than actually f being assigned to g
|
|
# g = f
|
|
# f = y
|
|
# g
|
|
# > x
|
|
|
|
Eval_setq = ->
|
|
# case of tensor
|
|
if (caadr(p1) == symbol(INDEX))
|
|
setq_indexed()
|
|
return
|
|
|
|
# case of function definition
|
|
if (iscons(cadr(p1)))
|
|
define_user_function()
|
|
return
|
|
|
|
if (!issymbol(cadr(p1)))
|
|
stop("symbol assignment: error in symbol")
|
|
|
|
push(caddr(p1))
|
|
Eval()
|
|
p2 = pop()
|
|
set_binding(cadr(p1), p2)
|
|
|
|
# An assignment returns nothing.
|
|
# This is unlike most programming languages
|
|
# where an assignment does return the
|
|
# assigned value.
|
|
# TODO Could be changed.
|
|
push(symbol(NIL))
|
|
|
|
# Here "setq" is a misnomer because
|
|
# setq wouldn't work in Lisp to set array elements
|
|
# since setq stands for "set quoted" and you wouldn't
|
|
# quote an array element access.
|
|
# You'd rather use setf, which is a macro that can
|
|
# assign a value to anything.
|
|
# (setf (aref YourArray 2) "blue")
|
|
# see
|
|
# http://stackoverflow.com/questions/18062016/common-lisp-how-to-set-an-element-in-a-2d-array
|
|
#-----------------------------------------------------------------------------
|
|
#
|
|
# Example: a[1] = b
|
|
#
|
|
# p1 *-------*-----------------------*
|
|
# | | |
|
|
# setq *-------*-------* b
|
|
# | | |
|
|
# index a 1
|
|
#
|
|
# cadadr(p1) -> a
|
|
#
|
|
#-----------------------------------------------------------------------------
|
|
|
|
setq_indexed = ->
|
|
p4 = cadadr(p1)
|
|
# console.log "p4: " + p4
|
|
if (!issymbol(p4))
|
|
# this is likely to happen when one tries to
|
|
# do assignments like these
|
|
# 1[2] = 3
|
|
# or
|
|
# f(x)[1] = 2
|
|
# or
|
|
# [[1,2],[3,4]][5] = 6
|
|
#
|
|
# In other words, one can only do
|
|
# a straight assignment like
|
|
# existingMatrix[index] = something
|
|
stop("indexed assignment: expected a symbol name")
|
|
h = tos
|
|
push(caddr(p1))
|
|
Eval()
|
|
p2 = cdadr(p1)
|
|
while (iscons(p2))
|
|
push(car(p2))
|
|
Eval()
|
|
p2 = cdr(p2)
|
|
set_component(tos - h)
|
|
p3 = pop()
|
|
set_binding(p4, p3)
|
|
push(symbol(NIL))
|
|
|
|
|
|
Eval_sqrt = ->
|
|
push(cadr(p1))
|
|
Eval()
|
|
push_rational(1, 2)
|
|
power()
|
|
|
|
Eval_stop = ->
|
|
stop("user stop")
|
|
|
|
Eval_subst = ->
|
|
push(cadddr(p1))
|
|
Eval()
|
|
push(caddr(p1))
|
|
Eval()
|
|
push(cadr(p1))
|
|
Eval()
|
|
subst()
|
|
Eval() # normalize
|
|
|
|
# always returns a matrix with rank 2
|
|
# i.e. two dimensions,
|
|
# the passed parameter is the size
|
|
Eval_unit = ->
|
|
i = 0
|
|
n = 0
|
|
push(cadr(p1))
|
|
Eval()
|
|
n = pop_integer()
|
|
|
|
if isNaN(n)
|
|
push(p1)
|
|
return
|
|
|
|
if (n < 1)
|
|
push(p1)
|
|
return
|
|
|
|
p1 = alloc_tensor(n * n)
|
|
p1.tensor.ndim = 2
|
|
p1.tensor.dim[0] = n
|
|
p1.tensor.dim[1] = n
|
|
for i in [0...n]
|
|
p1.tensor.elem[n * i + i] = one
|
|
check_tensor_dimensions p1
|
|
push(p1)
|
|
|
|
Eval_noexpand = ->
|
|
prev_expanding = expanding
|
|
expanding = 0
|
|
Eval()
|
|
expanding = prev_expanding
|
|
|
|
# like Eval() except "=" (assignment) is treated
|
|
# as "==" (equality test)
|
|
# This is because
|
|
# * this allows users to be lazy and just
|
|
# use "=" instead of "==" as per more common
|
|
# mathematical notation
|
|
# * in many places we don't expect an assignment
|
|
# e.g. we don't expect to test the zero-ness
|
|
# of an assignment or the truth value of
|
|
# an assignment
|
|
# Note that these are questionable assumptions
|
|
# as for example in most programming languages one
|
|
# can indeed test the value of an assignment (the
|
|
# value is just the evaluation of the right side)
|
|
|
|
Eval_predicate = ->
|
|
save()
|
|
p1 = top()
|
|
if (car(p1) == symbol(SETQ))
|
|
# replace the assignment in the
|
|
# head with an equality test
|
|
pop()
|
|
push_symbol(TESTEQ)
|
|
push cadr(p1)
|
|
push caddr(p1)
|
|
list 3
|
|
|
|
Eval()
|
|
restore()
|