223 lines
5.3 KiB
CoffeeScript
223 lines
5.3 KiB
CoffeeScript
# Evaluate a user defined function
|
|
|
|
|
|
#define F p3 # F is the function body
|
|
#define A p4 # A is the formal argument list
|
|
#define B p5 # B is the calling argument list
|
|
#define S p6 # S is the argument substitution list
|
|
|
|
# we got here because there was a function invocation and
|
|
# it's not been parsed (and consequently tagged) as any
|
|
# system function.
|
|
# So we are dealing with another function.
|
|
# The function could be actually defined, or not yet,
|
|
# so we'll deal with both cases.
|
|
|
|
### d =====================================================================
|
|
|
|
Tags
|
|
----
|
|
scripting, JS, internal, treenode, general concept
|
|
|
|
Parameters
|
|
----------
|
|
f,x
|
|
|
|
General description
|
|
-------------------
|
|
Returns the partial derivative of f with respect to x. x can be a vector e.g. [x,y].
|
|
|
|
###
|
|
|
|
Eval_user_function = ->
|
|
|
|
# Use "derivative" instead of "d" if there is no user function "d"
|
|
|
|
if DEBUG then console.log "Eval_user_function evaluating: " + car(p1)
|
|
if (car(p1) == symbol(SYMBOL_D) && get_binding(symbol(SYMBOL_D)) == symbol(SYMBOL_D))
|
|
Eval_derivative()
|
|
return
|
|
|
|
# normally car(p1) is a symbol with the function name
|
|
# but it could be something that has to be
|
|
# evaluated to get to the function definition instead
|
|
# (e.g. the function is an element of an array)
|
|
# so we do an eval to sort it all out.
|
|
push(car(p1))
|
|
Eval()
|
|
|
|
# we expect to find either the body and
|
|
# formula arguments, OR, if the function
|
|
# has not been defined yet, then the
|
|
# function will just contain its own name, as
|
|
# all undefined variables do.
|
|
bodyAndFormalArguments = pop()
|
|
|
|
if isNumericAtom(bodyAndFormalArguments)
|
|
stop("expected function invocation, found multiplication instead. Use '*' symbol explicitly for multiplication.")
|
|
else if istensor(bodyAndFormalArguments)
|
|
stop("expected function invocation, found tensor product instead. Use 'dot/inner' explicitly.")
|
|
else if isstr(bodyAndFormalArguments)
|
|
stop("expected function, found string instead.")
|
|
|
|
p3 = car(cdr(bodyAndFormalArguments)) # p3 is function body F
|
|
# p4 is the formal argument list
|
|
# that is also contained here in the FUNCTION node
|
|
p4 = car(cdr(cdr(bodyAndFormalArguments)))
|
|
|
|
p5 = cdr(p1); # p5 is the calling argument list
|
|
|
|
# example:
|
|
# f(x) = x+2
|
|
# then:
|
|
# p3.toString() = "x + 2"
|
|
# p4 = x
|
|
# p5 = 2
|
|
|
|
# first check is whether we don't obtain a function
|
|
if (car(bodyAndFormalArguments) != symbol(FUNCTION)) or
|
|
# next check is whether evaluation did nothing, so the function is undefined
|
|
(bodyAndFormalArguments == car(p1)) # p3 is F
|
|
# leave everything as it was and return
|
|
h = tos
|
|
push(bodyAndFormalArguments); # p3 is F
|
|
p1 = p5; # p5 is B
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
Eval()
|
|
p1 = cdr(p1)
|
|
list(tos - h)
|
|
return
|
|
|
|
# Create the argument substitution list p6(S)
|
|
|
|
p1 = p4; # p4 is A
|
|
p2 = p5; # p5 is B
|
|
h = tos
|
|
while (iscons(p1) && iscons(p2))
|
|
push(car(p1))
|
|
push(car(p2))
|
|
# why explicitly Eval the parameters when
|
|
# the body of the function is
|
|
# evalled anyways? Commenting it out. All tests pass...
|
|
#Eval()
|
|
p1 = cdr(p1)
|
|
p2 = cdr(p2)
|
|
|
|
list(tos - h)
|
|
p6 = pop(); # p6 is S
|
|
|
|
# Evaluate the function body
|
|
|
|
push(p3); # p3 is F
|
|
if (iscons(p6)) # p6 is S
|
|
push(p6); # p6 is S
|
|
rewrite_args()
|
|
#console.log "rewritten body: " + stack[tos-1]
|
|
Eval()
|
|
|
|
# Rewrite by expanding symbols that contain args
|
|
|
|
rewrite_args = ->
|
|
n = 0
|
|
save()
|
|
|
|
# subst. list which is a list
|
|
# where each consecutive pair
|
|
# is what needs to be substituted and with what
|
|
p2 = pop();
|
|
#console.log "subst. list " + p2
|
|
|
|
# expr to substitute in i.e. the
|
|
# function body
|
|
p1 = pop();
|
|
#console.log "expr: " + p1
|
|
|
|
if (istensor(p1))
|
|
n = rewrite_args_tensor()
|
|
restore()
|
|
return n
|
|
|
|
if (iscons(p1))
|
|
h = tos
|
|
if (car(p1) == car(p2))
|
|
# rewrite a function in
|
|
# the body with the one
|
|
# passed from the paramaters
|
|
push_symbol(EVAL)
|
|
push(car(cdr(p2)));
|
|
list(2)
|
|
else
|
|
# if there is no match
|
|
# then no substitution necessary
|
|
push(car(p1));
|
|
|
|
# continue recursively to
|
|
# rewrite the rest of the body
|
|
p1 = cdr(p1)
|
|
while (iscons(p1))
|
|
push(car(p1))
|
|
push(p2)
|
|
n += rewrite_args()
|
|
p1 = cdr(p1)
|
|
list(tos - h)
|
|
restore()
|
|
return n
|
|
|
|
# ground cases here
|
|
# (apart from function name which has
|
|
# already been substituted as it's in the head
|
|
# of the cons)
|
|
# -----------------
|
|
|
|
# If not a symbol then no
|
|
# substitution to be done
|
|
if (!issymbol(p1))
|
|
push(p1)
|
|
restore()
|
|
return 0
|
|
|
|
# Here we are in a symbol case
|
|
# so we need to substitute
|
|
|
|
# Check if there is a direct match
|
|
# of symbols right away
|
|
p3 = p2
|
|
while (iscons(p3))
|
|
if (p1 == car(p3))
|
|
push(cadr(p3))
|
|
restore()
|
|
return 1
|
|
p3 = cddr(p3)
|
|
|
|
# Get the symbol's content, if _that_
|
|
# matches then do the substitution
|
|
p3 = get_binding(p1)
|
|
push(p3)
|
|
if (p1 != p3)
|
|
push(p2); # subst. list
|
|
n = rewrite_args()
|
|
if (n == 0)
|
|
pop()
|
|
push(p1); # restore if not rewritten with arg
|
|
|
|
restore()
|
|
return n
|
|
|
|
rewrite_args_tensor = ->
|
|
n = 0
|
|
i = 0
|
|
push(p1)
|
|
copy_tensor()
|
|
p1 = pop()
|
|
for i in [0...p1.tensor.nelem]
|
|
push(p1.tensor.elem[i])
|
|
push(p2)
|
|
n += rewrite_args()
|
|
p1.tensor.elem[i] = pop()
|
|
|
|
check_tensor_dimensions p1
|
|
|
|
push(p1)
|
|
return n
|