"""Contains most of the real work of evaluating expressions & calling functions"""

import interpreter
from lisp_excs import *
from debug import dbgs
from copy import *

def EvalExpr(expr, ns, interper=None):
    """That's expression, namespace, where ns is a table of string-to-atom
        mappings.
    expr can be a string, a LispList
    It gets recursively evaluated, the first elt getting
    called.  And namespace substitution happens, too.

    THIS FUNCTION IS NOT COPY SAFE needs a writable copy of expr!!!
        But ns is read-only.
    """
    
    if interper==None:
        interper=interpreter.the_interper

    print>>dbgs, "EvalExpr begin: %s, %s and namespace=%s" %(expr, type(expr), ns)
    if isinstance(expr, str):
        if expr in ns:  return ns[expr]
        else:           return expr

    if expr[0] in interper.builtin_macros: #We don't want to do any recursive evaluation!
        print>>dbgs, "EvalExpr: '%s' is a macro, not recursively evaluating elements."\
                %expr[0]
        return interper.builtin_macros[expr[0]].Call(expr[1:])

    startfrom = 1
    Substitute(expr, ns, startfrom)
    print>>dbgs, "EvalExpr: After substitution: %s" %expr

    for i in range(startfrom, len(expr)):
        expr[i] = EvalExpr(expr[i], ns)

    expr = CallFunction(expr[0], expr[1:])
    return expr


def Substitute(lispList, ns, startfrom=1):
    "Does namespace substitution on lispList, in-place."
    for i in range(startfrom, len(lispList)):  #We iterate over indexes because iterating over elements won't do in-place substitution on the list.
        if isinstance(lispList[i], str):
            print>>dbgs, 'thinking about substitutions for %s...' %lispList[i]
            if lispList[i] in ns:
                print>>dbgs, '\tsubstituting %s for %s!' %(ns[lispList[i]], lispList[i])
                lispList[i] = ['quote', ns[lispList[i]]]
            else:
                print>>dbgs, '\tnot subbing.'
        else:
            assert isinstance(lispList[i], list)

def CallFunction(fnName, args, interper=None):
    """Does function-namespaces lookup and then calls the correct function.
    It's expected that the function's arguments have already been resolved,
    that is, you only call this from EvalExpr.
    """

    if interper==None:
        interper = interpreter.the_interper
    if   fnName in interper.builtin_functions:
        fn = interper.builtin_functions[fnName]
    elif fnName in interper.defined_functions:
        fn = interper.defined_functions[fnName]
    else:
        raise LispException, "Unknown function name '%s'!" %fnName
    return fn.Call(args)


class LispFunction(object):
    """This is a function defined in Lisp, as opposed to a BuiltinFunction
    that's implemented in Python.

    However, they both have the same interface."""

    def __init__(self, params, evals_to, lispname="< no name ever given :( >"):
        self.params   = params
        self.evals_to = evals_to
        self.lispname = lispname
        print>>dbgs, "New LispFunction created."
        print>>dbgs, "  %(lispname)s, params=%(params)s, evals_to=%(evals_to)s"  %vars()

    def checkargs(self, args):
        "Helper for Call()"
        if len(args) != len(self.params):
            error_msg = "Call died!, '%s' got %d args but wants %d!" \
                        % (self.lispname, len(args), len(self.params))
            raise LispException( error_msg )
    def make_namespace(self, args):
        """Helper for Call()
        Example:  If params=['a','b'] and args=[1, 2],
            then we return { 'a':1, 'b':2 }
        """
        items = zip(self.params, args)
        return dict(items)

    def Call(self, args):
        "Precondition: args have already been recursively evaluated!"
        self.checkargs(args)
        print>>dbgs, "%s's Call() begin: args=%s  %s" %(self.lispname, args, type(args))

        thisCallsNamespace = self.make_namespace(args)
        print>>dbgs, "%s's Call(): created namespace=%s" %(self.lispname, thisCallsNamespace)
        
        eval_result = EvalExpr(deepcopy(self.evals_to), thisCallsNamespace)
        print>>dbgs, "%s's Call(): returning %s %s" %(self.lispname, eval_result, type(eval_result))

        return eval_result


