aboutsummaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/Makefile27
-rw-r--r--python/mal_readline.py24
-rw-r--r--python/mal_types.py268
-rw-r--r--python/reader.py104
-rw-r--r--python/step0_repl.py32
-rw-r--r--python/step1_read_print.py32
-rw-r--r--python/step2_eval.py60
-rw-r--r--python/step3_env.py76
-rw-r--r--python/step4_if_fn_do.py91
-rw-r--r--python/step5_tco.py99
-rw-r--r--python/step6_file.py108
-rw-r--r--python/step7_quote.py125
-rw-r--r--python/step8_macros.py145
-rw-r--r--python/step9_interop.py154
-rw-r--r--python/stepA_more.py168
15 files changed, 1513 insertions, 0 deletions
diff --git a/python/Makefile b/python/Makefile
new file mode 100644
index 0000000..1c8e467
--- /dev/null
+++ b/python/Makefile
@@ -0,0 +1,27 @@
+
+TESTS =
+
+
+SOURCES = mal_types.py mal_readline.py reader.py stepA_more.py
+
+#all: mal.sh
+#
+#mal.sh: $(SOURCES)
+# cat $+ > $@
+# echo "#!/bin/bash" > $@
+# cat $+ | grep -v "^source " >> $@
+# chmod +x $@
+#
+#clean:
+# rm -f mal.sh
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ python $@ || exit 1; \
diff --git a/python/mal_readline.py b/python/mal_readline.py
new file mode 100644
index 0000000..e8cf957
--- /dev/null
+++ b/python/mal_readline.py
@@ -0,0 +1,24 @@
+import os, readline as pyreadline
+
+history_loaded = False
+histfile = os.path.expanduser("~/.mal-history")
+
+def readline(prompt="user> "):
+ if not history_loaded:
+ try:
+ with open(histfile, "r") as hf:
+ for line in hf.readlines():
+ pyreadline.add_history(line.rstrip("\r\n"))
+ pass
+ except IOError:
+ print("Could not open %s" % histfile)
+ pass
+
+ try:
+ line = raw_input(prompt)
+ pyreadline.add_history(line)
+ with open(histfile, "a") as hf:
+ hf.write(line + "\n")
+ return line
+ except EOFError:
+ return None
diff --git a/python/mal_types.py b/python/mal_types.py
new file mode 100644
index 0000000..fa0a11e
--- /dev/null
+++ b/python/mal_types.py
@@ -0,0 +1,268 @@
+import copy
+from itertools import chain
+
+# General functions
+
+def _pr_str(obj, print_readably=True):
+ _r = print_readably
+ if list_Q(obj):
+ return "(" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + ")"
+ elif vector_Q(obj):
+ return "[" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + "]"
+ elif hash_map_Q(obj):
+ ret = []
+ for k in obj.keys():
+ ret.extend((_pr_str(k), _pr_str(obj[k],_r)))
+ return "{" + " ".join(ret) + "}"
+ elif string_Q(obj):
+ if print_readably:
+ return '"' + obj.encode('unicode_escape').replace('"', '\\"') + '"'
+ else:
+ return obj
+ elif nil_Q(obj):
+ return "nil"
+ elif true_Q(obj):
+ return "true"
+ elif false_Q(obj):
+ return "false"
+ elif atom_Q(obj):
+ return "(atom " + _pr_str(obj.val,_r) + ")"
+ else:
+ return obj.__str__()
+
+def pr_str(*args):
+ return " ".join(map(lambda exp: _pr_str(exp, True), args))
+
+def do_str(*args):
+ return "".join(map(lambda exp: _pr_str(exp, False), args))
+
+def prn(*args):
+ print " ".join(map(lambda exp: _pr_str(exp, True), args))
+ return None
+
+def println(*args):
+ line = " ".join(map(lambda exp: _pr_str(exp, False), args))
+ print line.replace('\\n', '\n')
+ return None
+
+def with_meta(obj, meta):
+ new_obj = copy.copy(obj)
+ new_obj.__meta__ = meta
+ return new_obj
+
+def meta(obj):
+ if hasattr(obj, "__meta__"): return obj.__meta__
+ else: return None
+
+def equal_Q(a, b):
+ ota, otb = type(a), type(b)
+ if not (ota == otb or (sequential_Q(a) and sequential_Q(b))):
+ return False;
+ if symbol_Q(a):
+ return a == b
+ 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
+ else:
+ return a == b
+
+# nil, true, false
+def nil_Q(exp): return exp is None
+def true_Q(exp): return exp is True
+def false_Q(exp): return exp is False
+def string_Q(exp): return type(exp) in [str, unicode]
+
+# numbers
+int_plus = lambda a,b: a+b
+int_minus = lambda a,b: a-b
+int_multiply = lambda a,b: a*b
+int_divide = lambda a,b: a/b
+int_lt = lambda a,b: a<b
+int_lte = lambda a,b: a<=b
+int_gt = lambda a,b: a>b
+int_gte = lambda a,b: a>=b
+
+# symbols
+class Symbol(str): pass
+def new_symbol(str): return Symbol(str)
+def symbol_Q(exp): return type(exp) == Symbol
+
+
+# functions
+def new_function(func, exp, env, params):
+ def f(*args):
+ return func(exp, Env(env, params, args))
+ f.__meta__ = {"exp": exp, "env": env, "params": params}
+ return f
+def function_Q(f): return type(f) == type(function_Q)
+
+# hash maps
+class Hash_Map(dict): pass
+def new_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
+
+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: del hm[key]
+ return hm
+
+def get(hm, key):
+ if key in hm:
+ return hm[key]
+ else:
+ return None
+
+def contains_Q(hm, key): return key in hm
+
+def keys(hm): return new_list(*hm.keys())
+
+def vals(hm): return new_list(*hm.values())
+
+
+# errors/exceptions
+def throw(exc): raise Exception(exc)
+
+
+# lists
+class List(list):
+ 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 __getslice__(self, *a): return List(list.__getslice__(self, *a))
+def new_list(*vals): return List(vals)
+def list_Q(exp): return type(exp) == List
+
+
+# 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 new_vector(*vals): return Vector(vals)
+def vector_Q(exp): return type(exp) == Vector
+
+
+# atoms
+class Atom(object):
+ def __init__(self, val):
+ self.val = val
+def new_atom(val): return Atom(val)
+def atom_Q(exp): return type(exp) == Atom
+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
+
+
+
+# Sequence operations
+def sequential_Q(seq): return list_Q(seq) or vector_Q(seq)
+
+def coll_Q(coll): return sequential_Q(coll) or hash_map_Q(coll)
+
+def cons(x, seq): return List([x]) + List(seq)
+
+def nth(lst, idx): return lst[idx]
+
+def count(lst): return len(lst)
+
+def empty_Q(lst): return len(lst) == 0
+
+def concat(*lsts): return List(chain(*lsts))
+
+# retains metadata
+def conj(lst, *args):
+ new_lst = List(lst + list(args))
+ if hasattr(lst, "__meta__"):
+ new_lst.__meta__ = lst.__meta__
+ return new_lst
+
+def first(lst): return lst[0]
+
+def rest(lst): return List(lst[1:])
+
+def apply(f, *args):
+ return f(*(list(args[0:-1])+args[-1]))
+
+def mapf(f, lst):
+ return List(map(f, lst))
+
+
+# Environment
+
+class Env():
+ def __init__(self, outer=None, binds=None, exprs=None):
+ self.data = {}
+ self.outer = outer or None
+
+ if binds:
+ for i in range(len(binds)):
+ if binds[i] == "&":
+ self.data[binds[i+1]] = exprs[i:]
+ break
+ else:
+ self.data[binds[i]] = exprs[i]
+
+ def find(self, key):
+ if key in self.data: return self
+ elif self.outer: return self.outer.find(key)
+ else: return None
+
+ def set(self, key, value):
+ self.data[key] = value
+ return value
+
+ def get(self, key):
+ env = self.find(key)
+ if not env: raise Exception("'" + key + "' not found")
+ return env.data[key]
+
+types_ns = {
+ 'pr-str': pr_str, 'str': do_str, 'prn': prn, 'println': println,
+ 'with-meta': with_meta, 'meta': meta,
+ '=': equal_Q,
+ 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q,
+ 'symbol?': symbol_Q,
+ '<': int_lt, '<=': int_lte, '>': int_gt, '>=': int_gte,
+ '+': int_plus, '-': int_minus, '*': int_multiply, '/': int_divide,
+ 'hash-map': new_hash_map, 'map?': hash_map_Q,
+ 'assoc': assoc, 'dissoc': dissoc, 'get': get,
+ 'contains?': contains_Q, 'keys': keys, 'vals': vals,
+ 'throw': throw,
+ 'list': new_list, 'list?': list_Q,
+ 'vector': new_vector, 'vector?': vector_Q,
+ 'atom': new_atom, 'atom?': atom_Q, 'deref': deref,
+ 'reset!': reset_BANG, 'swap!': swap_BANG,
+ 'sequential?': sequential_Q,
+ 'cons': cons, 'nth': nth, 'count': count, 'empty?': empty_Q,
+ 'concat': concat, "conj": conj, "first": first, "rest": rest,
+ 'apply': apply, 'map': mapf}
+
diff --git a/python/reader.py b/python/reader.py
new file mode 100644
index 0000000..ddd6a32
--- /dev/null
+++ b/python/reader.py
@@ -0,0 +1,104 @@
+import re
+from mal_types import (new_symbol, Symbol, new_hash_map, List, new_list, Vector)
+
+class Blank(Exception): pass
+
+class Reader():
+ def __init__(self, tokens, position=0):
+ self.tokens = tokens
+ self.position = position
+
+ def next(self):
+ self.position += 1
+ return self.tokens[self.position-1]
+
+ def peek(self):
+ if len(self.tokens) > self.position:
+ return self.tokens[self.position]
+ else:
+ return None
+
+def tokenize(str):
+ tre = re.compile(r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:[\\].|[^\\"])*"|;.*|[^\s\[\]{}()'"`@,;]+)""");
+ return [t for t in re.findall(tre, str) if t[0] != ';']
+
+def read_atom(reader):
+ int_re = re.compile(r"-?[0-9]+$")
+ float_re = re.compile(r"-?[0-9][0-9.]*$")
+ token = reader.next()
+ if re.match(int_re, token): return int(token)
+ elif re.match(float_re, token): return int(token)
+ elif token[0] == '"': return token[1:-1].replace('\\"', '"')
+ elif token == "nil": return None
+ elif token == "true": return True
+ elif token == "false": return False
+ else: return Symbol(token)
+
+def read_sequence(reader, typ=list, start='(', end=')'):
+ ast = typ()
+ token = reader.next()
+ if token != start: raise Exception("expected '" + start + "'")
+
+ token = reader.peek()
+ while token != end:
+ if not token: raise Exception("expected '" + end + "', got EOF")
+ ast.append(read_form(reader))
+ token = reader.peek()
+ reader.next()
+ return ast
+
+def read_hash_map(reader):
+ lst = read_sequence(reader, list, '{', '}')
+ return new_hash_map(*lst)
+
+def read_list(reader):
+ return read_sequence(reader, List, '(', ')')
+
+def read_vector(reader):
+ return read_sequence(reader, Vector, '[', ']')
+
+def read_form(reader):
+ token = reader.peek()
+ # reader macros/transforms
+ if token[0] == ';':
+ reader.next()
+ return None
+ elif token == '\'':
+ reader.next()
+ return new_list(Symbol('quote'), read_form(reader))
+ elif token == '`':
+ reader.next()
+ return new_list(Symbol('quasiquote'), read_form(reader))
+ elif token == '~':
+ reader.next()
+ return new_list(Symbol('unquote'), read_form(reader))
+ elif token == '~@':
+ reader.next()
+ return new_list(Symbol('splice-unquote'), read_form(reader))
+ elif token == '^':
+ reader.next()
+ meta = read_form(reader)
+ return new_list(Symbol('with-meta'), read_form(reader), meta)
+ elif token == '@':
+ reader.next()
+ return new_list(Symbol('deref'), read_form(reader))
+
+ # list
+ elif token == ')': raise Exception("unexpected ')'")
+ elif token == '(': return read_list(reader)
+
+ # vector
+ elif token == ']': raise Exception("unexpected ']'");
+ elif token == '[': return read_vector(reader);
+
+ # hash-map
+ elif token == '}': raise Exception("unexpected '}'");
+ elif token == '{': return read_hash_map(reader);
+
+ # atom
+ else: return read_atom(reader);
+
+def read_str(str):
+ tokens = tokenize(str)
+ if len(tokens) == 0: raise Blank
+ return read_form(Reader(tokens))
diff --git a/python/step0_repl.py b/python/step0_repl.py
new file mode 100644
index 0000000..8d42c33
--- /dev/null
+++ b/python/step0_repl.py
@@ -0,0 +1,32 @@
+import sys, traceback
+import mal_readline
+
+# read
+def READ(str):
+ return str
+
+# eval
+def EVAL(ast, env):
+ # try it as an expression then a statement
+ try:
+ return eval(ast)
+ except SyntaxError:
+ exec compile(ast, '', 'single') in globals()
+ return None
+
+# print
+def PRINT(exp):
+ return exp
+
+# repl
+def REP(str):
+ return PRINT(EVAL(READ(str), {}))
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Exception as e:
+ print "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
diff --git a/python/step1_read_print.py b/python/step1_read_print.py
new file mode 100644
index 0000000..165dfa3
--- /dev/null
+++ b/python/step1_read_print.py
@@ -0,0 +1,32 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ return ast
+
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+def REP(str):
+ return PRINT(EVAL(READ(str), {}))
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step2_eval.py b/python/step2_eval.py
new file mode 100644
index 0000000..bb5d6f8
--- /dev/null
+++ b/python/step2_eval.py
@@ -0,0 +1,60 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env[ast]
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = {}
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+
+repl_env['+'] = lambda a,b: a+b
+repl_env['-'] = lambda a,b: a-b
+repl_env['*'] = lambda a,b: a*b
+repl_env['/'] = lambda a,b: a/b
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step3_env.py b/python/step3_env.py
new file mode 100644
index 0000000..f95a978
--- /dev/null
+++ b/python/step3_env.py
@@ -0,0 +1,76 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+_ref('+', lambda a,b: a+b)
+_ref('-', lambda a,b: a-b)
+_ref('*', lambda a,b: a*b)
+_ref('/', lambda a,b: a/b)
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step4_if_fn_do.py b/python/step4_if_fn_do.py
new file mode 100644
index 0000000..4b54d8f
--- /dev/null
+++ b/python/step4_if_fn_do.py
@@ -0,0 +1,91 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ el = eval_ast(ast[1:], env)
+ return el[-1]
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: return EVAL(ast[3], env)
+ else: return None
+ else:
+ return EVAL(a2, env)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step5_tco.py b/python/step5_tco.py
new file mode 100644
index 0000000..ffde863
--- /dev/null
+++ b/python/step5_tco.py
@@ -0,0 +1,99 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step6_file.py b/python/step6_file.py
new file mode 100644
index 0000000..b53863a
--- /dev/null
+++ b/python/step6_file.py
@@ -0,0 +1,108 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))")
+
+if len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step7_quote.py b/python/step7_quote.py
new file mode 100644
index 0000000..3054bb0
--- /dev/null
+++ b/python/step7_quote.py
@@ -0,0 +1,125 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))")
+
+if len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step8_macros.py b/python/step8_macros.py
new file mode 100644
index 0000000..616e7d3
--- /dev/null
+++ b/python/step8_macros.py
@@ -0,0 +1,145 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))")
+
+if len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step9_interop.py b/python/step9_interop.py
new file mode 100644
index 0000000..3a20960
--- /dev/null
+++ b/python/step9_interop.py
@@ -0,0 +1,154 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "py!*" == a0:
+ exec compile(ast[1], '', 'single') in globals()
+ return None
+ elif "py*" == a0:
+ return eval(ast[1])
+ elif "." == a0:
+ el = eval_ast(ast[2:], env)
+ f = eval(ast[1])
+ return f(*el)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))")
+
+if len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/stepA_more.py b/python/stepA_more.py
new file mode 100644
index 0000000..c0c5004
--- /dev/null
+++ b/python/stepA_more.py
@@ -0,0 +1,168 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "py!*" == a0:
+ exec compile(ast[1], '', 'single') in globals()
+ return None
+ elif "py*" == a0:
+ return eval(ast[1])
+ elif "." == a0:
+ el = eval_ast(ast[2:], env)
+ f = eval(ast[1])
+ return f(*el)
+ elif "try*" == a0:
+ a1, a2 = ast[1], ast[2]
+ if a2[0] == "catch*":
+ try:
+ return EVAL(a1, env);
+ except Exception as exc:
+ exc = exc.message
+ catch_env = Env(env, [a2[1]], [exc])
+ return EVAL(a2[2], catch_env)
+ else:
+ return EVAL(a1, env);
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('readline', lambda prompt: mal_readline.readline(prompt))
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
+REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
+REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))")
+
+if len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))