simplify quasiquote function
authorJorge Gorbe <jgorbe@stcsl.es>
Fri, 10 Jan 2014 13:53:13 +0000 (14:53 +0100)
committerJorge Gorbe <jgorbe@stcsl.es>
Fri, 10 Jan 2014 13:53:13 +0000 (14:53 +0100)
lisp0.py

index abb63b7f11b9b833499b6c5f751f901832c08235..18c6c869ddafb8f216da25aaa08d34c947f0ea0e 100644 (file)
--- a/lisp0.py
+++ b/lisp0.py
@@ -1,5 +1,6 @@
 import types
 import sys
+from functools import reduce
 
 class Pair:
     def __init__(self, car, cdr):
@@ -7,41 +8,18 @@ class Pair:
         self.cdr = cdr
 
 class Value:
+    """A class encapsulating a tagged lisp value. self.t is a string with the type name, self.v is the value"""
     def __init__(self, t, v=None):
         self.t = t
         self.v = v
     
     def __str__(self):
-        result = "" # self.t+"["
-        if is_pair(self):
-            result += "("
-            l = self
-            while is_pair(cdr(l)):
-                result += "%s " % car(l)
-                l = cdr(l)
-            result += "%s" % car(l)
-            if not is_nil(cdr(l)):
-                result += " . %s" % cdr(l)
-            result += ")"
-        elif is_function(self):
-            result += "(lambda %s %s), environment: %s"%(self.v[0], self.v[1], self.v[2])
-        elif is_macro(self):
-            result += "macro: %s"%str(self.v)
-        elif is_nil(self):
-            result += "nil"
-        elif is_string(self):
-            result += '"' + self.v + '"'
-        else:
-            result += str(self.v)
-
-        #result += "]"
-        return result
+        return Value2String(self)
 
 Value.TRUE = Value("symbol", "#t")
 Value.FALSE = Value("symbol", "#f")
 Value.NIL = Value("nil", None)
 
-
 # value constructors
 def make_int(i):                      return Value("int", i)
 def make_string(s):                   return Value("string", s)
@@ -49,40 +27,19 @@ def make_function(params, body, env): return Value("function", [params, body, en
 def make_macro(f):                    return Value("macro", f)
 def make_symbol(s):                   return Value("symbol", s)
 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)
 
-def make_list(*args):
-    if len(args) == 0: return Value.NIL
-    result = cons(args[0], Value.NIL)
-    last = result
-    for i in args[1:]:
-        new_last = cons(i, Value.NIL)
-        set_cdr(last, new_last)
-        last = new_last
-    return result
-
-
-# value_accessors
-def car(p):
-    assert(p.t == "pair")
-    return p.v.car
-    
-def cdr(p):
-    assert(p.t == "pair")
-    return p.v.cdr
+# pair value accessors
+def car(p):        return p.v.car
+def cdr(p):        return p.v.cdr 
+def set_car(p, v): p.v.car = v 
+def set_cdr(p, v): p.v.cdr = v
 
+# list helper accessors
 def cadr(p): return car(cdr(p))
 def cddr(p): return cdr(cdr(p))
 
-def set_car(p, v):
-    assert(p.t == "pair")
-    p.v.car = v
-
-def set_cdr(p, v):
-    assert(p.t == "pair")
-    p.v.cdr = v
-
-
-
 # type predicates for values
 def is_pair(v):     return v.t == "pair"
 def is_function(v): return v.t == "function"
@@ -91,12 +48,9 @@ def is_symbol(v):   return v.t == "symbol"
 def is_string(v):   return v.t == "string"
 def is_nil(v):      return v.t == "nil"
 def is_int(v):      return v.t == "int"
-
-
 def is_false(v):    return is_symbol(v) and v.v == "#f"
 def is_true(v):     return not is_false(v)
 
-
 # Environment is a dictionary wrapper with "inheritance": if a key is not found
 # the parent environment is searched, and so on.
 class Environment:
@@ -126,27 +80,21 @@ class Environment:
 
 
 # helper lisp-list functions
+def foldl(func, initial, l):
+    result = initial
+    cur = l
+    while not is_nil(cur):
+        result = func(result, car(cur))
+        cur = cdr(cur)
+    return result
 
-def reverse_list_rec(l, result):
-    if l.t == "nil": return result
-    else: return reverse_list_rec(l.v.cdr, cons(l.v.car, result))
-
-def reverse_list(l):
-    return reverse_list_rec(l, Value.NIL)
-
-def map_list_rec(func, l, result):
-    if l.t == "nil": return result
-    else: return map_list_rec(func, l.v.cdr, cons(func(l.v.car), result))
-
-def map_list(func, l):
-    return reverse_list(map_list_rec(func, l, Value.NIL))
-
+def foldr(func, initial, l):
+    if is_nil(l): return initial
+    else:         return func(car(l), foldr(func, initial, cdr(l)))
 
-def do_list(func, l):
-    if is_nil(l): return
-    else:
-        func(car(l))
-        do_list(func, 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)
@@ -166,10 +114,9 @@ def lisp_macro(args, env):
 
 def lisp_define(args, env):
     name = car(args)
-    assert(is_symbol(name))
     value = cadr(args)
     env[name.v] = lisp_eval(value, env)
-    return None
+    return Value.NIL
 
 
 def lisp_list(args, env):
@@ -179,46 +126,35 @@ def lisp_quote(args, env):
     assert(is_nil(cdr(args)))
     return car(args)
 
-def quasiquote_expression(e, env):
-    """returns an expression with unquotes recursively expanded as needed. If
+def quasiquote_expression(expr, env):
+    """returns a tuple with:
+    - the expression with unquotes recursively expanded as needed
+    - a string telling how the value should be inserted into the parent sequence: "normal" or "splicing"
     called with an unquote-splicing expression, it will return a python list of
     expressions, which will be inserted into the containing list"""
-    # if e is a list, look for unquotes
-    if is_pair(e):
-        first = car(e)
-        if is_symbol(first) and first.v == "unquote":
-            assert(is_nil(cddr(e)))
-            result = lisp_eval(cadr(e), env)
-        elif is_symbol(first) and first.v == "unquote-splicing":
-            second = lisp_eval(cadr(e), env)
-            assert(is_nil(cddr(e)))
-            assert(is_pair(second))
-            result = []
-            while not is_nil(second):
-                result.append(car(second))
-                second = cdr(second)
-
+    if is_pair(expr):
+        # if first in list is an unquote, eval the second and set mode accordingly
+        first = car(expr)
+        if is_symbol(first) and first.v in ["unquote", "unquote-splicing"]:
+            assert(is_nil(cddr(expr)))
+            mode = "splicing" if first.v == "unquote-splicing" else "normal"
+            return lisp_eval(cadr(expr), env), mode
         else:
-            result = make_list()
-            cur = e
-            while not is_nil(cur):
-                r = quasiquote_expression(car(cur), env)
-                if type(r) != list:
-                    r = [r]
-                for i in r:
-                    result = cons(i, result)
-                
-                cur = cdr(cur)
-
-            result = reverse_list(result)
+            def quasiquote_and_push_front(result, element):
+                r, mode = quasiquote_expression(element, env)
+                if mode == "splicing":
+                    return foldl(flipcons, result, r) # push elements of r, in reverse order, at the head of result
+                else:
+                    return cons(r, result)
+            result = foldl(quasiquote_and_push_front, make_list(), expr)
+            return reverse_list(result), "normal"
     else:
-        result = e
-    return result
+        return expr, "normal"
 
 def lisp_quasiquote(args, env):
     assert(is_nil(cdr(args)))
     e = car(args)
-    return quasiquote_expression(e, env)
+    return quasiquote_expression(e, env)[0]
 
 
 def lisp_car(args, env):
@@ -540,4 +476,29 @@ def eval_file(name):
         if e is not None:
             lisp_eval(e, global_env)
 
+# value pretty-printing
+def Value2String(x):
+    result = ""
+    if is_pair(x):
+        result += "("
+        l = x
+        while is_pair(cdr(l)):
+            result += "%s " % car(l)
+            l = cdr(l)
+        result += "%s" % car(l)
+        if not is_nil(cdr(l)):
+            result += " . %s" % cdr(l)
+        result += ")"
+    elif is_function(x):
+        result += "(lambda %s %s), environment: %s"%(x.v[0], x.v[1], x.v[2])
+    elif is_macro(x):
+        result += "macro: %s"%str(x.v)
+    elif is_nil(x):
+        result += "nil"
+    elif is_string(x):
+        result += '"' + x.v + '"'
+    else:
+        result += str(x.v)
+    return result
+