def quasiquote(ast): if not isinstance(ast, (list, Vector)) or len(ast) == 0: return [Symbol("quote"), ast] elif ast[0] == Symbol("unquote"): return ast[1] elif isinstance(ast[0], (list, Vector)) and ast[0][0] == Symbol("splice-unquote"): return [Symbol("concat"), ast[0][1], quasiquote(ast[1:])] else: return [Symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:])]
def main(): status = True repl_env = Env(outer=None) repl_env[Symbol("+")] = lambda a, b: Number(a.value + b.value) repl_env[Symbol('-')] = lambda a, b: Number(a.value - b.value) repl_env[Symbol('*')] = lambda a, b: Number(a.value * b.value) repl_env[Symbol('/')] = lambda a, b: Number(a.value // b.value) while status: try: rep(repl_env) except EOFError: print("Bye Gitesh :)") status = False
def eval_(ast, env): if isinstance(ast, list): if ast: if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): new_env = Env(env) # for loop b/c each def can depend on prev defs for key, value in zip(ast[1][::2], ast[1][1::2]): new_env[key] = eval_(value, new_env) return eval_(ast[2], new_env) elif ast[0] == Symbol("if"): result = eval_(ast[1], env) if result is not None and result is not False: # then return eval_(ast[2], env) elif len(ast) >= 4: # else return eval_(ast[3], env) else: # no else clause return None elif ast[0] == Symbol("fn*"): def func(*args): binds = itertools.zip_longest(ast[1], args) data = {} for bind, expr in binds: if bind == Symbol("&"): # varargs key, val = next(binds) if val: data[key] = [expr, val ] + [arg for _, arg in binds] elif expr: # 1 arg special case data[key] = [expr] else: # 0 args special case data[key] = [] break else: data[bind] = expr new_env = Env(env, data) return eval_(ast[2], new_env) return func elif ast[0] == Symbol("do"): return [eval_(el, env) for el in ast[1:]][-1] else: return eval_(ast[0], env)(*eval_ast(ast[1:], env)) else: return ast else: return eval_ast(ast, env)
def read_form(self): if not self.tokens: return None x = self.peek() if x == "(": return self.read_list(")", list) elif x == "[": return self.read_list("]", Vector) elif x == "{": return self.read_list("}", hash_map) elif x == "@": #atom deref return self.macro("deref") elif x == "'": return self.macro("quote") elif x == "`": return self.macro("quasiquote") elif x == "~": return self.macro("unquote") elif x == "~@": return self.macro("splice-unquote") elif x == "^": self.pos += 1 val = self.read_form() fn = self.read_form() return [Symbol("with-meta"), fn, val] else: return self.read_atom()
def read_atom(self): x = self.next() try: return int(x) except ValueError: pass if x[0] == '"': # strings string = "" escaped = False for c in x[1:-1]: if escaped: if c == "n": string += "\n" else: string += c escaped = False elif c == "\\": escaped = True else: string += c return string if x[0] == ":": #keywords return Keyword(x[1:]) if x == "true": return True if x == "false": return False if x == "nil": return None return Symbol(x)
def eval_(ast, env): if isinstance(ast, list): if ast: if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): env = Env(env) for key, value in zip(ast[1][::2], ast[1][1::2]): env[key] = eval_(value, env) return eval_(ast[2], env) else: return env[ast[0]](*eval_ast(ast[1:], env)) else: return ast else: return eval_ast(ast, env)
def read_atom(reader): int_re = re.compile(r"-?[0-9]+$") token = reader.next() if re.match(int_re, token): return int(token) elif token[0] == '"': return unescape(token[1:-1]) elif token == "nil": return None elif token == "true": return True elif token == "false": return False else: return Symbol(token)
def eval_list(alist, env): if isinstance(alist[0], Symbol) and alist[0].is_same_as(Symbol("def!")): result = eval_ast(alist[2], env) env[alist[1]] = result elif isinstance(alist[0], Symbol) and alist[0].is_same_as(Symbol("do")): for elem in alist[1:-1]: eval_ast(elem, env) result = eval_ast(alist[-1], env) elif isinstance(alist[0], Symbol) and alist[0].is_same_as(Symbol("if")): condition = eval_ast(alist[1], env) if isinstance(condition, NoneType) or (isinstance(condition, Boolean) and not condition.value): result = eval_ast(alist[3], env) if len(alist) == 4 else NoneType() else: result = eval_ast(alist[2], env) elif isinstance(alist[0], Symbol) and alist[0].is_same_as(Symbol("let*")): let_env = Env(outer=env) param_bindings = alist[1] if not isinstance(param_bindings, List) or len(param_bindings) % 2 != 0: raise SyntaxError("Invalid parameter bindings in the `let*` form.") i = 0 while i < len(param_bindings): let_env[param_bindings[i]] = eval_ast(param_bindings[i + 1], let_env) i += 2 result = eval_ast(alist[2], let_env) elif isinstance(alist[0], Symbol) and alist[0].is_same_as(Symbol("fn*")): result = FunctionType(params=alist[1], body=alist[2], env=env) else: func = eval_ast(alist[0], env) args = [eval_ast(elem, env) for elem in alist[1:]] if isinstance(func, FunctionType): func_env = Env(outer=func.closed_env) for k, v in zip(func.parameters, args): func_env[k] = v result = eval_ast(func.body, env=func_env) else: result = func(*args) return result
def read_atom(parser: Reader): current_token = parser.next() if is_integer(current_token): result = Number(int(current_token)) elif current_token in ["true", "false"]: result = Boolean(True if current_token == "true" else False) elif current_token == "nil": result = NoneType() else: result = Symbol(current_token) return result
def eval_(ast, env): while True: if isinstance(ast, list): if ast: if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): env = Env(env) # for loop b/c each def can depend on prev defs for key, value in zip(ast[1][::2], ast[1][1::2]): env[key] = eval_(value, env) ast = ast[2] continue elif ast[0] == Symbol("if"): result = eval_(ast[1], env) if result is not None and result is not False: # then ast = ast[2] elif len(ast) >= 4: # else ast = ast[3] continue else: # no else clause return None elif ast[0] == Symbol("fn*"): return Function(ast[2], ast[1], env, eval_) elif ast[0] == Symbol("do"): eval_ast(ast[1:-1], env) ast = ast[-1] continue else: fn = eval_(ast[0], env) if isinstance(fn, Function): env = fn.bind_args(eval_ast(ast[1:], env)) ast = fn.body continue return fn(*eval_ast(ast[1:], env)) else: return ast else: return eval_ast(ast, env)
def read_form(reader): token = reader.peek() if token[0] == ";": reader.next() return Comment(token) if token == "'": reader.next() return List((Symbol("quote"), read_form(reader))) if token == "`": reader.next() return List((Symbol("quasiquote"), read_form(reader))) if token == "~@": reader.next() return List((Symbol("splice-unquote"), read_form(reader))) if token == "~": reader.next() return List((Symbol("unquote"), read_form(reader))) if token == "@": reader.next() return List((Symbol("deref"), read_form(reader))) if token == "^": reader.next() metadata = read_form(reader) data = read_form(reader) return List((Symbol("with-meta"), data, metadata)) if token == "(": return read_list(reader) if token == "[": return read_array(reader) if token == "{": return read_hash_map(reader) else: return read_atom(reader)
def read_atom(tokens: Reader): token = tokens.peek() int_re = re.compile(r"-?[0-9]+$") float_re = re.compile(r"-?[0-9][0-9.]*$") if token[0] == "\"": if token[-1] == "\"": return String(token[1:-1]) else: raise MalException("missing closing \"") if re.match(int_re, token): return Integer(int(token)) if re.match(float_re, token): return float(token) if token == "true": return Bolean(True) if token == "false": return Bolean(False) if token == "nill": return Nill() return Symbol(token)
def func(*args): binds = itertools.zip_longest(ast[1], args) data = {} for bind, expr in binds: if bind == Symbol("&"): # varargs key, val = next(binds) if val: data[key] = [expr, val ] + [arg for _, arg in binds] elif expr: # 1 arg special case data[key] = [expr] else: # 0 args special case data[key] = [] break else: data[bind] = expr new_env = Env(env, data) return eval_(ast[2], new_env)
def read_atom(reader): token = reader.next() if re.match(INT_RE, token): return int(token) if re.match(STRING_RE, token): return String(_unescape(token[1:-1])) if token[0] == '"': raise Exception('Expected \'"\', found EOF') if token[0] == ':': return Keyword(token) if token == "true": return True if token == "false": return False if token == "nil": return None else: return Symbol(token)
def read_atom(reader: Reader) -> MalType: token = reader.next() parse_keyword_scalars = { 'nil': Nil(), 'true': BoolTrue(), 'false': BoolFalse(), } if token in parse_keyword_scalars: return parse_keyword_scalars[token] elif token[0] == '"': if token[-1] == '"': return String(token) else: raise MalParseError(f"Expected '\"', got EOF") else: try: parsed_integer = int(token) return Number(parsed_integer) except ValueError: return Symbol(token)
def core_is_list_empty(arg: List): return Boolean(len(arg.list) == 0) def core_list_count(arg: List): return Number(len(arg.list)) def core_equal(first, second): return first == second namespace = ( (Symbol("+"), lambda a, b: Number(a.value + b.value)), (Symbol("-"), lambda a, b: Number(a.value - b.value)), (Symbol("*"), lambda a, b: Number(a.value * b.value)), (Symbol("/"), lambda a, b: Number(a.value // b.value)), (Symbol("<"), lambda a, b: Boolean(a.value < b.value)), (Symbol("<="), lambda a, b: Boolean(a.value <= b.value)), (Symbol(">"), lambda a, b: Boolean(a.value > b.value)), (Symbol(">="), lambda a, b: Boolean(a.value >= b.value)), (Symbol("="), core_equal), (Symbol("prn"), core_prn), (Symbol("list"), core_list), (Symbol("list?"), core_is_list), (Symbol("empty?"), core_is_list_empty), (Symbol("count"), core_list_count), )
from mal_types import Symbol, MalType class Env: def __init__(self, outer=None): self.outer = outer self.data = dict() def set(self, key: Symbol, value): self.data[key] = value def find(self, key: Symbol): if key in self.data: return self.data elif self.outer is not None: return self.outer.find(key) return None def get(self, key: Symbol): env = self.find(key) if env is None: raise SymbolNotFound(f"'{key}' not found.") return env[key] repl_env = Env() repl_env.set(Symbol('+'), lambda a, b: a + b) repl_env.set(Symbol('-'), lambda a, b: a - b) repl_env.set(Symbol('*'), lambda a, b: a * b) repl_env.set(Symbol('/'), lambda a, b: int(a / b))
def read_metadata(reader: Reader) -> MalType: reader.next() metadata = read_from(reader) expr = read_from(reader) return Sexpr([Symbol('with-meta'), expr, metadata]) # Todo: replace Symbol by Function
def eval_(ast, env): while True: if isinstance(ast, list): if ast: ast = macro_expand(ast, env) if isinstance(ast, list): if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): env = Env(env) # for loop b/c each def can depend on prev defs for key, value in zip(ast[1][::2], ast[1][1::2]): env[key] = eval_(value, env) ast = ast[2] continue elif ast[0] == Symbol("if"): result = eval_(ast[1], env) if result is not None and result is not False: # then ast = ast[2] elif len(ast) >= 4: # else ast = ast[3] continue else: # no else clause return None elif ast[0] == Symbol("fn*"): return Function(ast[2], ast[1], env, eval_) elif ast[0] == Symbol("do"): eval_ast(ast[1:-1], env) ast = ast[-1] continue elif ast[0] == Symbol("quote"): return ast[1] elif ast[0] == Symbol("quasiquote"): ast = quasiquote(ast[1]) continue elif ast[0] == Symbol("defmacro!"): val = eval_(ast[2], env) val.is_macro = True env[ast[1]] = val return val elif ast[0] == Symbol("macroexpand"): return macro_expand(ast[1], env) elif ast[0] == Symbol("try*"): try: return eval_(ast[1], env) except Exception as e: if ast[2][0] != Symbol("catch*"): print("Warning: catch* not found", file=sys.stderr) env = Env(env) env[ast[2][1]] = str(e) return eval_(ast[2][2], env) else: fn = eval_(ast[0], env) if isinstance(fn, Function): env = fn.bind_args(eval_ast(ast[1:], env)) ast = fn.body continue return fn(*eval_ast(ast[1:], env)) else: return eval_ast(ast, env) else: return ast else: return eval_ast(ast, env)
while is_macro_call(ast, env): macro = env[ast[0]] ast = macro(*ast[1:]) return ast def print_(inp: str) -> str: return pretty_print(inp, True) def rep(inp: str): return print_(eval_(read(inp), global_env)) #setup global env global_env = Env(None, {Symbol(k): v for k, v in core.items()}) global_env[Symbol("eval")] = lambda ast: eval_(ast, global_env) global_env[Symbol("*ARGV*")] = sys.argv[2:] rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\\nnil)")))))' ) 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)))))))" ) def main(): if len(sys.argv) > 1: rep(f"(load-file \"{sys.argv[1]}\")") return while True: try:
def test_env_simple_w_symbols(): env = Env() env.set(Symbol("a"), 3) assert (3 == env.get(Symbol("a")))
def eval_(ast, env): while True: #if Symbol("el") in env and env[Symbol("el")][1] is None: # print(ast, env.data[Symbol("el")]) if isinstance(ast, list): if ast: ast = macro_expand(ast, env) if isinstance(ast, list): if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): env = Env(env) # for loop b/c each def can depend on prev defs for key, value in zip(ast[1][::2], ast[1][1::2]): env[key] = eval_(value, env) ast = ast[2] continue elif ast[0] == Symbol("if"): result = eval_(ast[1], env) if result is not None and result is not False: # then ast = ast[2] continue elif len(ast) >= 4: # else ast = ast[3] continue else: # no else clause return None elif ast[0] == Symbol("fn*"): return Function(ast[2], ast[1], env, eval_) elif ast[0] == Symbol("do"): eval_ast(ast[1:-1], env) ast = ast[-1] continue elif ast[0] == Symbol("quote"): return ast[1] elif ast[0] == Symbol("quasiquote"): ast = quasiquote(ast[1]) continue elif ast[0] == Symbol("defmacro!"): val = eval_( ast[2], env).copy() # avoid mutating existing function val.is_macro = True env[ast[1]] = val return val elif ast[0] == Symbol("macroexpand"): return macro_expand(ast[1], env) elif ast[0] == Symbol("try*"): try: return eval_(ast[1], env) except Exception as e: # pylint: disable=broad-except try: if ast[2][0] != Symbol("catch*"): raise ValueError("catch* not found") env = Env(env) if isinstance(e, MalException): env[ast[2][1]] = e.val # pylint: disable=no-member else: env[ast[2][1]] = str(e) return eval_(ast[2][2], env) except IndexError: # no catch clause raise e else: fn = eval_(ast[0], env) if isinstance(fn, Function): env = fn.bind_args(eval_ast(ast[1:], env)) ast = fn.body continue else: return fn(*eval_ast(ast[1:], env)) else: return eval_ast(ast, env) else: return ast else: return eval_ast(ast, env)
while is_macro_call(ast, env): macro = env[ast[0]] ast = macro(*ast[1:]) return ast def print_(inp: str) -> str: return pretty_print(inp, True) def rep(inp: str): return print_(eval_(read(inp), global_env)) #setup global env global_env = Env(None, {Symbol(k): v for k, v in core.items()}) global_env[Symbol("eval")] = lambda ast: eval_(ast, global_env) global_env[Symbol("*ARGV*")] = sys.argv[2:] global_env[Symbol("*host-language*")] = "python.3" rep("(def! not (fn* [a] (if a false true)))") rep('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\\nnil)")))))' ) 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)))))))" ) def main(): if len(sys.argv) > 1: rep(f"(load-file \"{sys.argv[1]}\")") return
#!/usr/bin/env python3 import readline # pylint: disable=unused-import import sys from core import core from env import Env from mal_types import Function, Symbol, Vector from pretty_print import pretty_print from reader import read_str global_env = Env(None, {Symbol(k): v for k, v in core.items()}) def read(inp: str): return read_str(inp) def eval_(ast, env): while True: if isinstance(ast, list): if ast: if ast[0] == Symbol("def!"): val = eval_(ast[2], env) env[ast[1]] = val return val elif ast[0] == Symbol("let*"): env = Env(env) # for loop b/c each def can depend on prev defs for key, value in zip(ast[1][::2], ast[1][1::2]): env[key] = eval_(value, env) ast = ast[2]
def macro(self, func: str): self.pos += 1 return [Symbol(func), self.read_form()]