# 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
