DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / rpython / mal_types.py
index e378b53..20d626d 100644 (file)
@@ -1,60 +1,71 @@
 import sys, copy, types as pytypes
+IS_RPYTHON = sys.argv[0].endswith('rpython')
+
+if IS_RPYTHON:
+    from rpython.rlib.listsort import TimSort
+else:
+    import re
 
 # General functions
 
+class StringSort(TimSort):
+    def lt(self, a, b):
+        assert isinstance(a, unicode)
+        assert isinstance(b, unicode)
+        return a < b
+
 def _equal_Q(a, b):
     assert isinstance(a, MalType) and isinstance(b, MalType)
     ota, otb = a.__class__, b.__class__
     if not (ota is otb or (_sequential_Q(a) and _sequential_Q(b))):
         return False
-    if _symbol_Q(a):
-        assert isinstance(a, MalSym) and isinstance(b, MalSym)
+    if isinstance(a, MalSym) and isinstance(b, MalSym):
         return a.value == b.value
-    elif _string_Q(a):
-        assert isinstance(a, MalStr) and isinstance(b, MalStr)
+    elif isinstance(a, MalStr) and isinstance(b, MalStr):
         return a.value == b.value
-    elif _int_Q(a):
-        assert isinstance(a, MalInt) and isinstance(b, MalInt)
+    elif isinstance(a, MalInt) and isinstance(b, MalInt):
         return a.value == b.value
-##    elif _list_Q(a) or _vector_Q(a):
-    elif _list_Q(a):
+    elif _list_Q(a) or _vector_Q(a):
         if len(a) != len(b): return False
         for i in range(len(a)):
             if not _equal_Q(a[i], b[i]): return False
         return True
-##    elif _hash_map_Q(a):
-##        akeys = a.keys()
-##        akeys.sort()
-##        bkeys = b.keys()
-##        bkeys.sort()
-##        if len(akeys) != len(bkeys): return False
-##        for i in range(len(akeys)):
-##            if akeys[i] != bkeys[i]: return False
-##            if not equal_Q(a[akeys[i]], b[bkeys[i]]): return False
-##        return True
+    elif _hash_map_Q(a):
+        assert isinstance(a, MalHashMap)
+        assert isinstance(b, MalHashMap)
+        akeys = a.dct.keys()
+        bkeys = b.dct.keys()
+        if len(akeys) != len(bkeys): return False
+
+        StringSort(akeys).sort()
+        StringSort(bkeys).sort()
+        for i in range(len(akeys)):
+            ak, bk = akeys[i], bkeys[i]
+            assert isinstance(ak, unicode)
+            assert isinstance(bk, unicode)
+            if ak != bk: return False
+            av, bv = a.dct[ak], b.dct[bk]
+            if not _equal_Q(av, bv): return False
+        return True
     elif a is b:
-    #elif ((a is nil and a is nil) or (a is true and b is true) or (a
-    #    is false and b is false)):
         return True
     else:
         throw_str("no = op defined for %s" % a.__class__.__name__)
 
-##def _sequential_Q(seq): return _list_Q(seq) or _vector_Q(seq)
-def _sequential_Q(seq): return _list_Q(seq)
-
-##def _clone(obj):
-##    #if type(obj) == type(lambda x:x):
-##    if type(obj) == pytypes.FunctionType:
-##        if obj.__code__:
-##            return pytypes.FunctionType(
-##                    obj.__code__, obj.__globals__, name = obj.__name__,
-##                    argdefs = obj.__defaults__, closure = obj.__closure__)
-##        else:
-##            return pytypes.FunctionType(
-##                    obj.func_code, obj.func_globals, name = obj.func_name,
-##                    argdefs = obj.func_defaults, closure = obj.func_closure)
-##    else:
-##        return copy.copy(obj)
+def _sequential_Q(seq): return _list_Q(seq) or _vector_Q(seq)
+
+def _clone(obj):
+    if isinstance(obj, MalFunc):
+        return MalFunc(obj.fn, obj.ast, obj.env, obj.params,
+                 obj.EvalFunc, obj.ismacro)
+    elif isinstance(obj, MalList):
+        return obj.__class__(obj.values)
+    elif isinstance(obj, MalHashMap):
+        return MalHashMap(obj.dct)
+    elif isinstance(obj, MalAtom):
+        return MalAtom(obj.value)
+    else:
+        raise Exception("_clone on invalid type")
 
 def _replace(match, sub, old_str):
     new_str = u""
@@ -81,8 +92,9 @@ def throw_str(s):
     raise MalException(MalStr(unicode(s)))
 
 
-### Parent type
+### Parent types
 class MalType(): pass
+class MalMeta(MalType): pass
 
 ### Scalars
 class MalNil(MalType): pass
@@ -121,19 +133,7 @@ class MalStr(MalType):
         return len(self.value)
 def _string_Q(exp):
     assert isinstance(exp, MalType)
-    return exp.__class__  is MalStr
-
-# Symbols
-class MalSym(MalType):
-    def __init__(self, value):
-        assert isinstance(value, unicode)
-        self.value = value
-def _symbol(strn):
-    assert isinstance(strn, unicode)
-    return MalSym(strn)
-def _symbol_Q(exp):
-    assert isinstance(exp, MalType)
-    return exp.__class__ is MalSym
+    return exp.__class__  is MalStr and not _keyword_Q(exp)
 
 # Keywords
 # A specially prefixed string
@@ -150,16 +150,30 @@ def _keywordu(strn):
     assert isinstance(strn, unicode)
     return MalStr(u"\u029e" + strn)
 def _keyword_Q(exp):
-    if isinstance(exp, MalStr):
+    if isinstance(exp, MalStr) and len(exp.value) > 0:
         return exp.value[0] == u"\u029e"
     else:
         return False
 
+# Symbols
+class MalSym(MalMeta):
+    def __init__(self, value):
+        assert isinstance(value, unicode)
+        self.value = value
+        self.meta = nil
+def _symbol(strn):
+    assert isinstance(strn, unicode)
+    return MalSym(strn)
+def _symbol_Q(exp):
+    assert isinstance(exp, MalType)
+    return exp.__class__ is MalSym
+
 # lists
-class MalList(MalType):
+class MalList(MalMeta):
     def __init__(self, vals):
         assert isinstance(vals, list)
         self.values = vals
+        self.meta = nil
     def append(self, val):
         self.values.append(val)
     def rest(self):
@@ -174,54 +188,68 @@ class MalList(MalType):
     def slice2(self, start, end):
         assert end >= 0
         return MalList(self.values[start:end])
-##    def __add__(self, rhs): return List(list.__add__(self, rhs))
-##    def __getitem__(self, i):
-##        if type(i) == slice: return List(list.__getitem__(self, i))
-##        elif i >= len(self): return None
-##        else:                return list.__getitem__(self, i)
 def _list(*vals): return MalList(list(vals))
-def _listl(l): return MalList(l.values)
-#def _list_Q(exp): return exp.__class__ == MalList
+def _listl(lst): return MalList(lst)
 def _list_Q(exp):
     assert isinstance(exp, MalType)
     return exp.__class__ is MalList
 
-
 ### vectors
-##class Vector(list):
-##    def __add__(self, rhs): return Vector(list.__add__(self, rhs))
-##    def __getitem__(self, i):
-##        if type(i) == slice: return Vector(list.__getitem__(self, i))
-##        elif i >= len(self): return None
-##        else:                return list.__getitem__(self, i)
-##    def __getslice__(self, *a): return Vector(list.__getslice__(self, *a))
-##def _vector(*vals): return Vector(vals)
-##def _vector_Q(exp): return type(exp) == Vector
-##
-### Hash maps
-##class Hash_Map(dict): pass
-##def _hash_map(*key_vals):
-##    hm = Hash_Map()
-##    for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1]
-##    return hm
-##def _hash_map_Q(exp): return type(exp) == Hash_Map
+class MalVector(MalList):
+    pass
+def _vector(*vals): return MalVector(list(vals))
+def _vectorl(lst): return MalVector(lst)
+def _vector_Q(exp):
+    assert isinstance(exp, MalType)
+    return exp.__class__ is MalVector
+
+### hash maps
+class MalHashMap(MalMeta):
+    def __init__(self, dct):
+        self.dct = dct
+        self.meta = nil
+    def append(self, val):
+        self.dct.append(val)
+    def __getitem__(self, k):
+        assert isinstance(k, unicode)
+        if not isinstance(k, unicode):
+            throw_str("hash-map lookup by non-string/non-keyword")
+        return self.dct[k]
+    def __setitem__(self, k, v):
+        if not isinstance(k, unicode):
+            throw_str("hash-map key must be string or keyword")
+        assert isinstance(v, MalType)
+        self.dct[k] = v
+        return v
+def _hash_mapl(kvs):
+    dct = {}
+    for i in range(0, len(kvs), 2):
+        k = kvs[i]
+        if not isinstance(k, MalStr):
+            throw_str("hash-map key must be string or keyword")
+        v = kvs[i+1]
+        dct[k.value] = v
+    return MalHashMap(dct)
+def _hash_map_Q(exp):
+    assert isinstance(exp, MalType)
+    return exp.__class__ is MalHashMap
 
 # Functions
 # env import must happen after MalSym and MalList definitions to allow
 # circular dependency
 from env import Env
-class MalFunc(MalType):
+class MalFunc(MalMeta):
     def __init__(self, fn, ast=None, env=None, params=None,
                  EvalFunc=None, ismacro=False):
         if fn is None and EvalFunc is None:
             throw_str("MalFunc requires either fn or EvalFunc")
         self.fn = fn
-        #assert isinstance(ast, MalType) or ast is None
         self.ast = ast
         self.env = env
         self.params = params
         self.EvalFunc = EvalFunc
         self.ismacro = ismacro
+        self.meta = nil
     def apply(self, args):
         if self.EvalFunc:
             return self.EvalFunc(self.ast, self.gen_env(args))
@@ -233,10 +261,13 @@ def _function_Q(exp):
     assert isinstance(exp, MalType)
     return exp.__class__ is MalFunc
 
-##
-### atoms
-##class Atom(object):
-##    def __init__(self, val):
-##        self.val = val
-##def _atom(val): return Atom(val)
-##def _atom_Q(exp):   return type(exp) == Atom
+
+# atoms
+class MalAtom(MalMeta):
+    def __init__(self, value):
+        self.value = value
+        self.meta = nil
+    def get_value(self):
+        return self.value
+def _atom(val): return MalAtom(val)
+def _atom_Q(exp): return exp.__class__ is MalAtom