Fix second-order self-hosting.
[jackhill/mal.git] / rpython / core.py
dissimilarity index 68%
index ed7a72a..5e7a12f 100644 (file)
-import copy, time
-from itertools import chain
-
-import mal_types as types
-from mal_types import (MalType, nil, true, false,
-                       MalInt, MalStr, MalList)
-import mal_readline
-import reader
-import printer
-
-# General functions
-def do_equal(args):
-    if types._equal_Q(args[0], args[1]): return true
-    else:                                return false
-
-## Errors/Exceptions
-#def throw(exc): raise Exception(exc)
-#
-
-# String functions
-def pr_str(args):
-    parts = []
-    for exp in args.values: parts.append(printer._pr_str(exp, True))
-    return MalStr(u" ".join(parts))
-
-def do_str(args):
-    parts = []
-    for exp in args.values: parts.append(printer._pr_str(exp, False))
-    return MalStr(u"".join(parts))
-
-def prn(args):
-    parts = []
-    for exp in args.values: parts.append(printer._pr_str(exp, True))
-    print(u" ".join(parts))
-    return nil
-
-def println(args):
-    parts = []
-    for exp in args.values: parts.append(printer._pr_str(exp, False))
-    print(u" ".join(parts))
-    return nil
-
-def read_str(args):
-    a0 = args[0]
-    assert isinstance(a0, MalStr)
-    return reader.read_str(str(a0.value))
-
-def slurp(args):
-    a0 = args[0]
-    assert isinstance(a0, MalStr)
-    return MalStr(unicode(open(str(a0.value)).read()))
-
-# Number functions
-def lt(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    if a.value < b.value: return true
-    else:                 return false
-def lte(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    if a.value <= b.value: return true
-    else:                  return false
-def gt(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    if a.value > b.value: return true
-    else:                 return false
-def gte(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    if a.value >= b.value: return true
-    else:                  return false
-
-def plus(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    return MalInt(a.value+b.value)
-def minus(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    return MalInt(a.value-b.value)
-def multiply(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    return MalInt(a.value*b.value)
-def divide(args):
-    a, b = args[0], args[1]
-    assert isinstance(a, MalInt)
-    assert isinstance(b, MalInt)
-    return MalInt(int(a.value/b.value))
-
-
-## Hash map functions
-#def assoc(src_hm, *key_vals):
-#    hm = copy.copy(src_hm)
-#    for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1]
-#    return hm
-#
-#def dissoc(src_hm, *keys):
-#    hm = copy.copy(src_hm)
-#    for key in keys:
-#        if key in hm: del hm[key]
-#    return hm
-#
-#def get(hm, key):
-#    if hm and key in hm:
-#        return hm[key]
-#    else:
-#        return None
-#
-#def contains_Q(hm, key): return key in hm
-#
-#def keys(hm): return types._list(*hm.keys())
-#
-#def vals(hm): return types._list(*hm.values())
-#
-
-# Sequence functions
-def do_list(ml):
-    assert isinstance(ml, MalList)
-    return ml
-
-def list_Q(args):
-    if isinstance(args[0], MalList): return true
-    else:                            return false
-
-def empty_Q(args):
-    assert isinstance(args, MalType)
-    seq = args[0]
-    if isinstance(seq, MalList):
-        if len(seq) == 0: return true
-        else:             return false
-    elif seq is nil:
-        return true
-    else:
-        raise Exception("empty? called on non-sequence")
-
-def count(args):
-    assert isinstance(args, MalType)
-    seq = args[0]
-    if isinstance(seq, MalList):
-        return MalInt(len(seq))
-    elif seq is nil:
-        return MalInt(0)
-    else:
-        raise Exception("count called on non-sequence")
-
-#def coll_Q(coll): return sequential_Q(coll) or hash_map_Q(coll)
-#
-#def cons(x, seq): return List([x]) + List(seq)
-#
-#def concat(*lsts): return List(chain(*lsts))
-#
-#def nth(lst, idx):
-#    if idx < len(lst): return lst[idx]
-#    else: throw("nth: index out of range")
-#
-#def first(lst): return lst[0]
-#
-#def rest(lst): return List(lst[1:])
-#
-#def empty_Q(lst): return len(lst) == 0
-#
-#def count(lst):
-#    if types._nil_Q(lst): return 0
-#    else: return len(lst)
-#
-## retains metadata
-#def conj(lst, *args):
-#    if types._list_Q(lst): 
-#        new_lst = List(list(reversed(list(args))) + lst)
-#    else:
-#        new_lst = Vector(lst + list(args))
-#    if hasattr(lst, "__meta__"):
-#        new_lst.__meta__ = lst.__meta__
-#    return new_lst
-#
-#def apply(f, *args): return f(*(list(args[0:-1])+args[-1]))
-#
-#def mapf(f, lst): return List(map(f, lst))
-#
-#
-## Metadata functions
-#def with_meta(obj, meta):
-#    new_obj = types._clone(obj)
-#    new_obj.__meta__ = meta
-#    return new_obj
-#
-#def meta(obj):
-#    if hasattr(obj, "__meta__"): return obj.__meta__
-#    else:                        return None
-#
-#
-## Atoms functions
-#def deref(atm):    return atm.val
-#def reset_BANG(atm,val):
-#    atm.val = val
-#    return atm.val
-#def swap_BANG(atm,f,*args):
-#    atm.val = f(atm.val,*args)
-#    return atm.val
-
-
-ns = { 
-        '=': do_equal,
-#        'throw': throw,
-#        'nil?': types._nil_Q,
-#        'true?': types._true_Q,
-#        'false?': types._false_Q,
-#        'symbol': types._symbol,
-#        'symbol?': types._symbol_Q,
-#        'keyword': types._keyword,
-#        'keyword?': types._keyword_Q,
-#
-        'pr-str': pr_str,
-        'str': do_str,
-        'prn': prn,
-        'println': println,
-#        'readline': lambda prompt: mal_readline.readline(prompt),
-        'read-string': read_str,
-        'slurp': slurp,
-        '<':  lt,
-        '<=': lte,
-        '>':  gt,
-        '>=': gte,
-        '+':  plus,
-        '-':  minus,
-        '*':  multiply,
-        '/':  divide,
-#        'time-ms': lambda : int(time.time() * 1000),
-#
-        'list': do_list,
-        'list?': list_Q,
-#        'vector': types._vector,
-#        'vector?': types._vector_Q,
-#        'hash-map': types._hash_map,
-#        'map?': types._hash_map_Q,
-#        'assoc': assoc,
-#        'dissoc': dissoc,
-#        'get': get,
-#        'contains?': contains_Q,
-#        'keys': keys,
-#        'vals': vals,
-#
-#        'sequential?': types._sequential_Q,
-#        'cons': cons,
-#        'concat': concat,
-#        'nth': nth,
-#        'first': first,
-#        'rest': rest,
-        'empty?': empty_Q,
-        'count': count,
-#        'conj': conj,
-#        'apply': apply,
-#        'map': mapf,
-#
-#        'with-meta': with_meta,
-#        'meta': meta,
-#        'atom': types._atom,
-#        'atom?': types._atom_Q,
-#        'deref': deref,
-#        'reset!': reset_BANG,
-#        'swap!': swap_BANG
-    }
-
+#import copy, time
+import time
+
+import mal_types as types
+from mal_types import (throw_str,
+                       MalType, MalMeta, nil, true, false,
+                       MalInt, MalSym, MalStr,
+                       MalList, MalVector, MalHashMap,
+                       MalAtom, MalFunc)
+import mal_readline
+import reader
+import printer
+
+# General functions
+def wrap_tf(tf):
+    if tf: return true
+    else:  return false
+
+def do_equal(args): return wrap_tf(types._equal_Q(args[0], args[1]))
+
+# Errors/Exceptions
+def throw(args):
+    raise types.MalException(args[0])
+
+# Scalar functions
+def nil_Q(args): return wrap_tf(types._nil_Q(args[0]))
+def true_Q(args): return wrap_tf(types._true_Q(args[0]))
+def false_Q(args): return wrap_tf(types._false_Q(args[0]))
+def string_Q(args): return wrap_tf(types._string_Q(args[0]))
+def symbol(args):
+    a0 = args[0]
+    if isinstance(a0, MalStr):
+        return types._symbol(a0.value)
+    elif isinstance(a0, MalSym):
+        return a0
+    else:
+        throw_str("symbol called on non-string/non-symbol")
+def symbol_Q(args): return wrap_tf(types._symbol_Q(args[0]))
+def keyword(args): return types._keyword(args[0])
+def keyword_Q(args): return wrap_tf(types._keyword_Q(args[0]))
+def number_Q(args): return wrap_tf(types._int_Q(args[0]))
+def function_Q(args): return wrap_tf(types._function_Q(args[0]) and not args[0].ismacro)
+def macro_Q(args): return wrap_tf(types._function_Q(args[0]) and args[0].ismacro)
+
+
+# String functions
+def pr_str(args):
+    parts = []
+    for exp in args.values: parts.append(printer._pr_str(exp, True))
+    return MalStr(u" ".join(parts))
+
+def do_str(args):
+    parts = []
+    for exp in args.values: parts.append(printer._pr_str(exp, False))
+    return MalStr(u"".join(parts))
+
+def prn(args):
+    parts = []
+    for exp in args.values: parts.append(printer._pr_str(exp, True))
+    print(u" ".join(parts))
+    return nil
+
+def println(args):
+    parts = []
+    for exp in args.values: parts.append(printer._pr_str(exp, False))
+    print(u" ".join(parts))
+    return nil
+
+def do_readline(args):
+    prompt = args[0]
+    if not isinstance(prompt, MalStr):
+        throw_str("readline prompt is not a string")
+    try:
+        return MalStr(unicode(mal_readline.readline(str(prompt.value))))
+    except EOFError:
+        return nil
+
+def read_str(args):
+    a0 = args[0]
+    if not isinstance(a0, MalStr):
+        throw_str("read-string of non-string")
+    return reader.read_str(str(a0.value))
+
+def slurp(args):
+    a0 = args[0]
+    if not isinstance(a0, MalStr):
+        throw_str("slurp with non-string filename")
+    return MalStr(unicode(open(str(a0.value)).read()))
+
+# Number functions
+def lt(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("< called on non-integer")
+    return wrap_tf(a.value < b.value)
+def lte(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("<= called on non-integer")
+    return wrap_tf(a.value <= b.value)
+def gt(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("> called on non-integer")
+    return wrap_tf(a.value > b.value)
+def gte(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str(">= called on non-integer")
+    return wrap_tf(a.value >= b.value)
+
+def plus(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("+ called on non-integer")
+    return MalInt(a.value+b.value)
+def minus(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("- called on non-integer")
+    return MalInt(a.value-b.value)
+def multiply(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("* called on non-integer")
+    return MalInt(a.value*b.value)
+def divide(args):
+    a, b = args[0], args[1]
+    if not isinstance(a, MalInt) or not isinstance(b, MalInt):
+        throw_str("/ called on non-integer")
+    if b.value == 0:
+        throw_str("divide by zero")
+    return MalInt(int(a.value/b.value))
+
+def time_ms(args):
+    return MalInt(int(time.time() * 1000))
+
+
+# Hash map functions
+def do_hash_map(ml):
+    return types._hash_mapl(ml.values)
+
+def hash_map_Q(args):
+    return wrap_tf(types._hash_map_Q(args[0]))
+
+def assoc(args):
+    src_hm, key_vals = args[0], args.rest()
+    new_dct = src_hm.dct.copy()
+    for i in range(0,len(key_vals),2):
+        k = key_vals[i]
+        if not isinstance(k, MalStr):
+            throw_str("assoc called with non-string/non-keyword key")
+        new_dct[k.value] = key_vals[i+1]
+    return MalHashMap(new_dct)
+
+def dissoc(args):
+    src_hm, keys = args[0], args.rest()
+    new_dct = src_hm.dct.copy()
+    for k in keys.values:
+        if not isinstance(k, MalStr):
+            throw_str("dissoc called with non-string/non-keyword key")
+        if k.value in new_dct:
+            del new_dct[k.value]
+    return MalHashMap(new_dct)
+
+def get(args):
+    obj, key = args[0], args[1]
+    if obj is nil:
+        return nil
+    elif isinstance(obj, MalHashMap):
+        if not isinstance(key, MalStr):
+            throw_str("get called on hash-map with non-string/non-keyword key")
+        if obj and key.value in obj.dct:
+            return obj.dct[key.value]
+        else:
+            return nil
+    elif isinstance(obj, MalList):
+        if not isinstance(key, MalInt):
+            throw_str("get called on list/vector with non-string/non-keyword key")
+        return obj.values[key.value]
+    else:
+        throw_str("get called on invalid type")
+
+def contains_Q(args):
+    hm, key = args[0], args[1]
+    if not isinstance(key, MalStr):
+        throw_str("contains? called on hash-map with non-string/non-keyword key")
+    return wrap_tf(key.value in hm.dct)
+
+def keys(args):
+    hm = args[0]
+    keys = []
+    for k in hm.dct.keys(): keys.append(MalStr(k))
+    return MalList(keys)
+
+def vals(args):
+    hm = args[0]
+    return MalList(hm.dct.values())
+
+
+# Sequence functions
+def do_list(ml):
+    return ml
+
+def list_Q(args):
+    return wrap_tf(types._list_Q(args[0]))
+
+def do_vector(ml):
+    return MalVector(ml.values)
+
+def vector_Q(args):
+    return wrap_tf(types._vector_Q(args[0]))
+
+def empty_Q(args):
+    seq = args[0]
+    if isinstance(seq, MalList):
+        return wrap_tf(len(seq) == 0)
+    elif seq is nil:
+        return true
+    else:
+        throw_str("empty? called on non-sequence")
+
+def count(args):
+    seq = args[0]
+    if isinstance(seq, MalList):
+        return MalInt(len(seq))
+    elif seq is nil:
+        return MalInt(0)
+    else:
+        throw_str("count called on non-sequence")
+
+def sequential_Q(args):
+    return wrap_tf(types._sequential_Q(args[0]))
+
+def cons(args):
+    x, seq = args[0], args[1]
+    if not isinstance(seq, MalList):
+        throw_str("cons called with non-list/non-vector")
+    return MalList([x] + seq.values)
+
+def concat(args):
+    new_lst = []
+    for l in args.values:
+        if not isinstance(l, MalList):
+            throw_str("concat called with non-list/non-vector")
+        new_lst = new_lst + l.values
+    return MalList(new_lst)
+
+def nth(args):
+    lst, idx = args[0], args[1]
+    if not isinstance(lst, MalList):
+        throw_str("nth called with non-list/non-vector")
+    if not isinstance(idx, MalInt):
+        throw_str("nth called with non-int index")
+    if idx.value < len(lst): return lst[idx.value]
+    else: throw_str("nth: index out of range")
+
+def first(args):
+    a0 = args[0]
+    if a0 is nil:
+        return nil
+    elif not isinstance(a0, MalList):
+        throw_str("first called with non-list/non-vector")
+    if len(a0) == 0: return nil
+    else:            return a0[0]
+
+def rest(args):
+    a0 = args[0]
+    if a0 is nil:
+        return MalList([])
+    elif not isinstance(a0, MalList):
+        throw_str("rest called with non-list/non-vector")
+    if len(a0) == 0: return MalList([])
+    else:            return a0.rest()
+
+def apply(args):
+    f, fargs = args[0], args.rest()
+    last_arg = fargs.values[-1]
+    if not isinstance(last_arg, MalList):
+        throw_str("map called with non-list")
+    all_args = fargs.values[0:-1] + last_arg.values
+    return f.apply(MalList(all_args))
+
+def mapf(args):
+    f, lst = args[0], args[1]
+    if not isinstance(lst, MalList):
+        throw_str("map called with non-list")
+    res = []
+    for a in lst.values:
+        res.append(f.apply(MalList([a])))
+    return MalList(res)
+
+# retains metadata
+def conj(args):
+    lst, args = args[0], args.rest()
+    new_lst = None
+    if types._list_Q(lst):
+        vals = args.values[:]
+        vals.reverse()
+        new_lst = MalList(vals + lst.values)
+    elif types._vector_Q(lst):
+        new_lst = MalVector(lst.values + list(args.values))
+    else:
+        throw_str("conj on non-list/non-vector")
+    new_lst.meta = lst.meta
+    return new_lst
+
+def seq(args):
+    a0 = args[0]
+    if isinstance(a0, MalVector):
+        if len(a0) == 0: return nil
+        return MalList(a0.values)
+    elif isinstance(a0, MalList):
+        if len(a0) == 0: return nil
+        return a0
+    elif types._string_Q(a0):
+        assert isinstance(a0, MalStr)
+        if len(a0) == 0: return nil
+        return MalList([MalStr(unicode(c)) for c in a0.value])
+    elif a0 is nil:
+        return nil
+    else:
+        throw_str("seq: called on non-sequence")
+
+# Metadata functions
+def with_meta(args):
+    obj, meta = args[0], args[1]
+    if isinstance(obj, MalMeta):
+        new_obj = types._clone(obj)
+        new_obj.meta = meta
+        return new_obj
+    else:
+        throw_str("with-meta not supported on type")
+
+def meta(args):
+    obj = args[0]
+    if isinstance(obj, MalMeta):
+        return obj.meta
+    else:
+        throw_str("meta not supported on type")
+
+
+# Atoms functions
+def do_atom(args):
+    return MalAtom(args[0])
+def atom_Q(args):
+    return wrap_tf(types._atom_Q(args[0]))
+def deref(args):
+    atm = args[0]
+    if not isinstance(atm, MalAtom):
+        throw_str("deref called on non-atom")
+    return atm.value
+def reset_BANG(args):
+    atm, val = args[0], args[1]
+    if not isinstance(atm, MalAtom):
+        throw_str("reset! called on non-atom")
+    atm.value = val
+    return atm.value
+def swap_BANG(args):
+    atm, f, fargs = args[0], args[1], args.slice(2)
+    if not isinstance(atm, MalAtom):
+        throw_str("swap! called on non-atom")
+    if not isinstance(f, MalFunc):
+        throw_str("swap! called with non-function")
+    all_args = [atm.value] + fargs.values
+    atm.value = f.apply(MalList(all_args))
+    return atm.value
+
+
+ns = {
+        '=': do_equal,
+        'throw': throw,
+        'nil?': nil_Q,
+        'true?': true_Q,
+        'false?': false_Q,
+        'string?': string_Q,
+        'symbol': symbol,
+        'symbol?': symbol_Q,
+        'keyword': keyword,
+        'keyword?': keyword_Q,
+        'number?': number_Q,
+        'fn?': function_Q,
+        'macro?': macro_Q,
+
+        'pr-str': pr_str,
+        'str': do_str,
+        'prn': prn,
+        'println': println,
+        'readline': do_readline,
+        'read-string': read_str,
+        'slurp': slurp,
+        '<':  lt,
+        '<=': lte,
+        '>':  gt,
+        '>=': gte,
+        '+':  plus,
+        '-':  minus,
+        '*':  multiply,
+        '/':  divide,
+        'time-ms': time_ms,
+
+        'list': do_list,
+        'list?': list_Q,
+        'vector': do_vector,
+        'vector?': vector_Q,
+        'hash-map': do_hash_map,
+        'map?': hash_map_Q,
+        'assoc': assoc,
+        'dissoc': dissoc,
+        'get': get,
+        'contains?': contains_Q,
+        'keys': keys,
+        'vals': vals,
+
+        'sequential?': sequential_Q,
+        'cons': cons,
+        'concat': concat,
+        'nth': nth,
+        'first': first,
+        'rest': rest,
+        'empty?': empty_Q,
+        'count': count,
+        'apply': apply,
+        'map': mapf,
+
+        'conj': conj,
+        'seq': seq,
+
+        'with-meta': with_meta,
+        'meta': meta,
+        'atom': do_atom,
+        'atom?': atom_Q,
+        'deref': deref,
+        'reset!': reset_BANG,
+        'swap!': swap_BANG
+    }
+