moved argument checking and evaluation in lisp_* functions to a common decorator
authorJorge Gorbe <jgorbe@stcsl.es>
Mon, 13 Jan 2014 12:47:07 +0000 (13:47 +0100)
committerJorge Gorbe <jgorbe@stcsl.es>
Mon, 13 Jan 2014 12:47:07 +0000 (13:47 +0100)
lisp0.py
repl.py

index 18c6c869ddafb8f216da25aaa08d34c947f0ea0e..edce63ab3c400511a339aff67c9d342977c2bf88 100644 (file)
--- a/lisp0.py
+++ b/lisp0.py
@@ -1,5 +1,7 @@
 import types
 import sys
+import operator
+import inspect
 from functools import reduce
 
 class Pair:
@@ -26,6 +28,7 @@ def make_string(s):                   return Value("string", s)
 def make_function(params, body, env): return Value("function", [params, body, env])
 def make_macro(f):                    return Value("macro", f)
 def make_symbol(s):                   return Value("symbol", s)
+def make_boolean(b):                  return Value.TRUE if b else Value.FALSE
 def cons(x,y):                        return Value("pair", Pair(x,y))
 def flipcons(x,y):                    return cons(y,x)
 def make_list(*args):                 return reduce(flipcons, reversed(args), Value.NIL)
@@ -92,13 +95,11 @@ def foldr(func, initial, l):
     if is_nil(l): return initial
     else:         return func(car(l), foldr(func, initial, cdr(l)))
 
-def reverse_list(l):   return foldl(flipcons, Value.NIL, l)
-def map_list(func, l): return foldr(lambda x,y: cons(func(x), y), Value.NIL, l)
-def do_list(func, l):  return foldl(lambda x,y: func(y), None, l)
-
-def eval_list(args, env):
-    return map_list(lambda x: lisp_eval(x, env), args)
-
+def reverse_list(l):      return foldl(flipcons, Value.NIL, l)
+def map_list(func, l):    return foldr(lambda x,y: cons(func(x), y), Value.NIL, l)
+def do_list(func, l):     return foldl(lambda x,y: func(y), None, l)
+def eval_list(args, env): return map_list(lambda x: lisp_eval(x, env), args)
+def list_length(l):       return foldl(lambda x,y: x+1, 0, l)
 
 # Built-in functions and special forms
 # ------------------------------------
@@ -118,7 +119,6 @@ def lisp_define(args, env):
     env[name.v] = lisp_eval(value, env)
     return Value.NIL
 
-
 def lisp_list(args, env):
     return eval_list(args, env)
 
@@ -126,6 +126,20 @@ def lisp_quote(args, env):
     assert(is_nil(cdr(args)))
     return car(args)
 
+def lisp_cond(args, env):
+    while not is_nil(args):
+        c = car(args)
+        assert(is_pair(c))
+        if is_true(lisp_eval(car(c), env)):
+            actions = cdr(c)
+            while not is_nil(actions):
+                result = lisp_eval(car(actions), env)
+                actions = cdr(actions)
+            return result
+        args = cdr(args)
+    return Value.NIL
+
+
 def quasiquote_expression(expr, env):
     """returns a tuple with:
     - the expression with unquotes recursively expanded as needed
@@ -152,98 +166,66 @@ def quasiquote_expression(expr, env):
         return expr, "normal"
 
 def lisp_quasiquote(args, env):
-    assert(is_nil(cdr(args)))
+    assert(list_length(args) == 1)
     e = car(args)
     return quasiquote_expression(e, env)[0]
 
+##############################
+# functions exported to lisp #
+##############################
+
+# a decorator which checks the number of arguments, evals them,
+# and forwards them to a python function as positional arguments
+def lisp_function(f):
+    def result(args, env):
+        argspec = inspect.getargspec(f)
+        if (argspec.varargs is None):
+            expected_args = len(argspec.args)
+            given_args = list_length(args)
+            if (given_args != expected_args):
+                raise Exception("function %s takes %d arguments (%d given)"%(f.func_name, expected_args, given_args))
+        def eval_and_append(l, x):
+            l.append(lisp_eval(x, env))
+            return l
+        python_args = foldl(eval_and_append, [], args)
+        return f(*python_args)
+    return result
 
-def lisp_car(args, env):
-    args = eval_list(args, env)
-    assert(is_nil(cdr(args)))
-    return car(car(args))
+@lisp_function
+def lisp_car(l): return car(l)
 
-def lisp_cdr(args, env):
-    args = eval_list(args, env)
-    assert(is_nil(cdr(args)))
-    return cdr(car(args))
+@lisp_function
+def lisp_cdr(l): return cdr(l)
 
-def lisp_cons(args, env):
-    args = eval_list(args, env)
-    assert(is_nil(cddr(args)))
-    return cons(car(args), cadr(args))
+@lisp_function
+def lisp_cons(a, b): return cons(a, b)
     
-
-def lisp_cond(args, env):
-    while not is_nil(args):
-        c = car(args)
-        assert(is_pair(c))
-        if is_true(lisp_eval(car(c), env)):
-            actions = cdr(c)
-            while not is_nil(actions):
-                result = lisp_eval(car(actions), env)
-                actions = cdr(actions)
-            return result
-        args = cdr(args)
-    return Value.NIL
-
 # defined only for atoms
-def lisp_equal(args, env):
-    args = eval_list(args, env)
-    e1 = car(args)
-    e2 = cadr(args)
-    result = (not is_pair(e1)) and e1.t == e2.t and e1.v == e2.v
-    if result:
-        return Value.TRUE
-    else:
-        return Value.FALSE
-
-
-def plus_rec(args, env, result):
-    if is_nil(args): return result
-    else:
-        result.v += lisp_eval(car(args), env).v
-        return plus_rec(cdr(args), env, result)
-
-def lisp_plus(args, env):
-    return plus_rec(args, env, make_int(0))
+@lisp_function
+def lisp_equal(e1, e2): return make_boolean((not is_pair(e1)) and e1.t == e2.t and e1.v == e2.v)
 
-def lisp_minus(args, env):
-    args = eval_list(args, env)
-    e1 = car(args)
-    e2 = cadr(args)
-    assert(e1.t == "int" and e2.t == "int")
-    return make_int(e1.v - e2.v)
+@lisp_function
+def lisp_plus(*args): return make_int(sum((i.v for i in args)))
 
-def times_rec(args, env, result):
-    if is_nil(args): return result
-    else:
-        result.v *= lisp_eval(car(args), env).v
-        return times_rec(cdr(args), env, result)
+@lisp_function
+def lisp_minus(e1, e2): return make_int(e1.v - e2.v)
 
-def lisp_times(args, env):
-    return times_rec(args, env, make_int(1))
+@lisp_function
+def lisp_times(*args): return make_int(reduce(operator.mul, (i.v for i in args), 1))
 
-def lisp_div(args, env):
-    args = eval_list(args, env)
-    e1 = car(args)
-    e2 = cadr(args)
-    assert(e1.t == "int" and e2.t == "int")
-    return make_int(e1.v / e2.v)
+@lisp_function
+def lisp_div(e1, e2): return make_int(e1.v / e2.v)
 
-def lisp_mod(args, env):
-    args = eval_list(args, env)
-    e1 = car(args)
-    e2 = cadr(args)
-    assert(e1.t == "int" and e2.t == "int")
-    return make_int(e1.v % e2.v)
+@lisp_function
+def lisp_mod(e1, e2): return make_int(e1.v % e2.v) 
 
-def lisp_display(args, env):
-    args = eval_list(args, env)
-    do_list(lambda x: sys.stdout.write(str(x)), args)
+@lisp_function
+def lisp_display(*args):
+    for i in args:
+        sys.stdout.write(str(i))
     sys.stdout.write("\n")
     return Value.NIL
     
-
 # Helper functions for the evaluator
 # ----------------------------------
 
@@ -268,9 +250,10 @@ global_env = Environment()
 # in order to optimize tail calls
 def lisp_eval(e, env):
     """evals an expression"""
+    result = None
     ended = False
     while not ended:
-        #print "eval: %s"%str(e)
+        # print "eval: %s"%str(e)
         if is_symbol(e):
             result = env[e.v]
             ended = True
@@ -323,7 +306,7 @@ def lisp_eval(e, env):
         else:
             result = e
             ended = True
-            
+        # print "result: %s"%result    
     return result
 
 def lisp_apply(f, args, env):
diff --git a/repl.py b/repl.py
index eb06d6c85fcb1caf0d0460df4700336b08c19524..96122d701b95b4d1b18556b683bd00986bf85c4f 100644 (file)
--- a/repl.py
+++ b/repl.py
@@ -10,7 +10,10 @@ else:
     while True:
         in_str = raw_input()
         e,s = parse(in_str)
-        result = lisp_eval(e, global_env)
-        print(result)
+        try:
+            result = lisp_eval(e, global_env)
+            print(result)
+        except Exception as e:
+            print '%s: %s' % (type(e).__name__, e)