diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/Makefile | 27 | ||||
| -rw-r--r-- | python/mal_readline.py | 24 | ||||
| -rw-r--r-- | python/mal_types.py | 268 | ||||
| -rw-r--r-- | python/reader.py | 104 | ||||
| -rw-r--r-- | python/step0_repl.py | 32 | ||||
| -rw-r--r-- | python/step1_read_print.py | 32 | ||||
| -rw-r--r-- | python/step2_eval.py | 60 | ||||
| -rw-r--r-- | python/step3_env.py | 76 | ||||
| -rw-r--r-- | python/step4_if_fn_do.py | 91 | ||||
| -rw-r--r-- | python/step5_tco.py | 99 | ||||
| -rw-r--r-- | python/step6_file.py | 108 | ||||
| -rw-r--r-- | python/step7_quote.py | 125 | ||||
| -rw-r--r-- | python/step8_macros.py | 145 | ||||
| -rw-r--r-- | python/step9_interop.py | 154 | ||||
| -rw-r--r-- | python/stepA_more.py | 168 |
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())) |
