"""BLISP builtins

Architecture:
Every builtin is a class providing the instance method Call().
The two namespaces of builtin_macros and builtin_functiosn are created when the
client calls MakeBuiltins.  It's intended that it gets called when initializing an
interpreter instance.

The reason the builtins are function-objects instead of straight up Python functions
is because some functions, like defun, need external data to work.  Thus,
these special_builtins have constructors that take in arguments supplied by the client
calling MakeBuiltins.

If you add any new builtins, make sure to inherit either BuiltinMacro or BuiltinFunction!
It's important because I don't use a master list or table of all builtins; rather,
they're dynamically generated by looping through globals(), using superclases
as an informative trait.

This also means it's easy to add new builtins: you only have to write in just -- 1 --
place!  Just add another class in the middle section and it's good to go!

As a bonus, the inheritance system provides a nifty naming system to promote my
lazines; the interpreter will generate the lispnames for most functions for me!
"""

from __future__ import division
from evaller import LispFunction, EvalExpr
from copy import copy, deepcopy
import Parser



###############  Superclass infrastructure #################

##def SplitByCap_Gen(s):
##    word = s[0]
##    for c in s[1:]:
##        if c.islower
        
class BuiltinAnything(object):
    def __init__(self ):
        #If no name, make one out of the Python name
        if not hasattr(self.__class__, 'lispname'):
            self.lispname = self.make_lispname()
    def make_lispname(self):
        return self.__class__.__name__.lower().replace("_", "-")
##        words = [w.lower for w in SplitByCap(self.__class__.__name__)]

class BuiltinFunction(BuiltinAnything):
    """You have to do the Call _after_ recursively evaluating all everything in the 2nd -> last
    positions in the s-expr.  Once they're evalutaed, you've got lispargs."""
    def Call(self, lispargs):
        print "%s needs to implement Call()!" %self

class BuiltinMacro(BuiltinAnything):
    """Macros get called without their arguments being evaluated"""
    def Call(self, lispargs):
        print "%s needs to implement Call()!" %self


######### Definitions for builtin functions ##############

class Eval(BuiltinFunction):
    """\
    Lisp usage:     (eval '(+ 1 2))
    Python usage:   Call( [['+', '1', '2']] )
                    << or rather, Eval.PyEval(['+', '1', '2']) >>
        not quoted becaues it's called AFTER EvalExpr'ing away the quote
    
    """
    def Call(self, lispargs):
        assert len(lispargs) == 1
        eval_this = lispargs[0]

        eval_this_copy = deepcopy(eval_this)
        return EvalExpr(eval_this_copy, {})
    
    def PyEval(cls, lispexpr):
        cls.Call(cls(), [lispexpr])
    PyEval = classmethod(PyEval)

class Lambda(BuiltinMacro):
    def Call(self, lispargs):
        assert len(lispargs) == 2
        params = lispargs[0]; assert not isinstance(params, str)
        evals_to = lispargs[1]
        return LispFunction(params, evals_to, "Anonymous Lambda Function")
        
class Map(BuiltinFunction):
    def Call(self, lispargs):
        assert len(lispargs) == 2
        return map(lispargs[1].Call, [ [itm] for itm in lispargs[0] ])

class Defun(BuiltinMacro):
    needed_external_data = ("defined_fn_ns",)
    def __init__(self, fn_ns):
        """Pass it the namespace dictionary where newly defined
        functions will go"""
        BuiltinMacro.__init__(self)
        self.defined_functions = fn_ns
    def Call(self, lispargs):
        assert len(lispargs) == 3
        name = lispargs[0]; assert isinstance(name, str)
        params = lispargs[1]; assert not isinstance(params, str)
        evals_to = lispargs[2]
        if name in self.defined_functions:
            raise LispExeption, \
                    "defun error: Function with name '%s' already exists" % name

        self.defined_functions[name] = LispFunction(params, evals_to, name)
        return 0
        

class Quote(BuiltinMacro):
    def Call(self, lispargs):
        return lispargs[0]
    
class If(BuiltinMacro):
    """Doesnt ayet work"""
    def Call(self, lispargs):
        assert len(lispargs) in (2,3)
        test, ontrue = lispargs[:2]
        if len(lispargs) == 3: onfalse = lispargs[2]
        else:                  onfalse = Parser.ParseString("(quote NIL)")
        testresult = Eval.PyEval(lispargs[0])
        if testresult in ('t','T'):
            return Eval.PyEval(ontrue)
        elif testresult in ('nil', 'NIL', 'Nil'):
            return Eval.PyEval(onfalse)
        else:
            print "if: Oops! Instead of T or NIL, got '%s'!  Returning NIL for the hell of it" %testresult
            return 'NIL'
            
class Not(BuiltinFunction):
    def Call(self, lispargs):
        if lispargs[0] in ('t', 'T'):
            return 'NIL'
        if lispargs[0] in ('nil', 'NIL', 'Nil'):
            return 'T'
        raise LispException, "%s neither true nor false value" %lispargs[0]
        
## Math functions ##

class Add(BuiltinFunction):
    lispname = '+'
    def __init__(self):
        BuiltinFunction.__init__(self)
    def Call(self, lispargs):
        assert len(lispargs) > 1
        for e in lispargs: assert isinstance(e, str), e
        numeric_sum = 0
        for str_num in lispargs:
            numeric_sum += int(str_num)
        return str(numeric_sum)

class Mult(BuiltinFunction):
    lispname = '*'
    def __init__(self):
        BuiltinFunction.__init__(self)
    def Call(self, lispargs):
        assert len(lispargs) > 1
        for e in lispargs: assert isinstance(e, str), e
        numeric_prod = 1
        for str_num in lispargs:
            numeric_prod *= int(str_num)
        return str(numeric_prod)

class Divide(BuiltinFunction):
    lispname = '/'
    def __init__(self):
        BuiltinFunction.__init__(self)
    def Call(self, lispargs):
        assert len(lispargs) == 2
        for e in lispargs: assert isinstance(e, str), e
        return str(int(lispargs[0]) / int(lispargs[1]))

class Subtract(BuiltinFunction):
    lispname = '-'
    def __init__(self):
        BuiltinFunction.__init__(self)
    def Call(self, lispargs):
        assert len(lispargs) > 1
        for e in lispargs: assert isinstance(e, str), e
        numeric_sum = int(lispargs[0])
        for str_num in lispargs[1:]:
            numeric_sum -= int(str_num)
        return str(numeric_sum)

class Modulo(BuiltinFunction):
    lispname = "%"
    def Call(self, lispargs):
        assert len(lispargs) == 2
        return str(int(lispargs[0]) % int(lispargs[1]))
        
## Interpreter control ##
class Setopt(BuiltinMacro):
    needed_external_data = ("interper",)
    def __init__(self, interper):
        BuiltinMacro.__init__(self)
        self.interper = interper
    def Call(self, lispargs):
        option, newval = lispargs[:2]
        if option == 'debug':
            if   newval == 'on':  self.interper.DEBUG = 1
            elif newval == 'off': self.interper.DEBUG = 0
            
## NOTE: Since there are no cons cells in blisp, we only support lisp-lists.
##       This means there are no dotted pairs and related things I don't know about
##       that lisp is supposed to have.
class Cons(BuiltinFunction):
    def Call(self,lispargs):
        assert len(lispargs) == 2 
        return [lispargs[0]] + lispargs[1] 

class Cdr(BuiltinFunction):
    def Call(self, lispargs):
        assert len(lispargs) == 1
        return lispargs[0][1:]
    
class Car(BuiltinFunction):
    def Call(self, lispargs):
        assert len(lispargs) == 1
        return lispargs[0][0]


############### Dynamic (namespace generation system) ##################

#These need arguments passed to their constructors; that is,
#external references to important parts of the interpreter 
#beyond builtins.py

all_builtins = [B for B in globals().values() 
                    if B.__class__ == type                #Gotta be a class 
                    if issubclass(B, BuiltinAnything)     #and a builtin
                    if B.__class__ not in \
                        (BuiltinMacro, BuiltinFunction)   #but not a parent class
                ]

special_builtins = [B for B in all_builtins \
                    if hasattr(B, 'needed_external_data') ]

def MakeBuiltins(**external_data):
    global special_builtins, all_builtins
    builtin_fns, builtin_macros = {}, {}

    for BuiltinClass in all_builtins:
        if BuiltinClass in special_builtins:
            args = ArgsForSpecialCase(BuiltinClass, external_data)
        else: args = []

        new_builtin = BuiltinClass(*args)
        if issubclass(BuiltinClass, BuiltinFunction):
            builtin_fns[new_builtin.lispname] = new_builtin
        elif issubclass(BuiltinClass, BuiltinMacro):
            builtin_macros[new_builtin.lispname] = new_builtin

    return builtin_fns, builtin_macros

def ArgsForSpecialCase(Builtin, external_data):
    return [external_data[data_name] for data_name in \
            Builtin.needed_external_data]


