def _setup_repl_env(): repl_env = Env() repl_env.set(make_symbol('+'), lambda a, b: a + b) repl_env.set(make_symbol('-'), lambda a, b: a - b) repl_env.set(make_symbol('*'), lambda a, b: a * b) repl_env.set(make_symbol('/'), lambda a, b: a / b) return repl_env
def read_form(reader): curr_token = reader.peek() if curr_token in tuple('([{'): token_to_type = { '(': make_list, '[': make_vector, '{': make_hashmap, } sequential = token_to_type[curr_token] return read_list(reader, sequential) elif curr_token == '@': reader.next() return make_list([make_symbol('deref'), read_form(reader)]) elif curr_token == '\'': reader.next() return make_list([make_symbol('quote'), read_form(reader)]) elif curr_token == '`': reader.next() return make_list([make_symbol('quasiquote'), read_form(reader)]) elif curr_token == '~': reader.next() return make_list([make_symbol('unquote'), read_form(reader)]) elif curr_token == '~@': reader.next() return make_list([make_symbol('splice-unquote'), read_form(reader)]) elif curr_token == '^': reader.next() term2 = read_form(reader) term1 = read_form(reader) return make_list([make_symbol('with-meta'), term1, term2]) return read_atom(reader)
def EVAL(ast, env): """ Evaluate set of mal instructions. """ if not is_list(ast): return eval_ast(ast, env) elif is_empty(ast): return ast elif first(ast) == make_symbol('def!'): try: operands = rest(ast) symbol = first(operands) value = EVAL(first(rest(operands)), env) except ValueError: raise RuntimeError('def! syntax is (def! /symbol/ /value/)') env.set(symbol, value) return value elif first(ast) == make_symbol('let*'): let_error = RuntimeError( 'let* syntax is (let* /list_of definitions/ /list_of_instructions/)' ) # noqa new_env = Env(env) try: operands = rest(ast) definitions, instructions = operands except Exception: raise let_error if len(definitions) % 2 != 0: raise let_error symbol_value_pairs = list(zip( definitions[0::2], definitions[1::2], )) for symb, value in symbol_value_pairs: new_env.set(symb, EVAL(value, new_env)) return EVAL(instructions, new_env) func, *args = eval_ast(ast, env) return func(*args)
def read_atom(reader): token = reader.next() if token == 'nil': return NIL elif token == 'true': return TRUE elif token == 'false': return FALSE elif re.match(r'-?\d+\.*\d*', token): return make_number(token) elif token.startswith('"'): if not valid_string(token): raise MalException('EOF') return make_string(token) elif token.startswith(':'): return make_keyword(token) elif re.match(r'.*', token): return make_symbol(token) raise MalException('Input/output error')
def quasiquote(ast): if is_list(ast): if is_empty(ast): return ast if ast[0] == make_symbol('unquote'): return ast[1] else: processed = [] for elt in ast[::-1]: if is_list(elt) and not is_empty(elt) and elt[0] == make_symbol('splice-unquote'): processed = make_list([make_symbol('concat'), elt[1], processed]) else: processed = make_list([make_symbol('cons'), quasiquote(elt), processed]) return make_list(processed) elif is_vector(ast): return make_list([make_symbol('vec'), *ast]) elif is_symbol(ast) or is_hashmap(ast): return make_list([make_symbol('quote'), ast]) return ast
def EVAL(ast, env): """ Evaluate set of mal instructions. """ while True: if not is_list(ast): return eval_ast(ast, env) elif is_empty(ast): return ast else: ast = macroexpand(ast, env) if not is_list(ast): return eval_ast(ast, env) elif not ast: return ast if first(ast) == make_symbol('def!'): try: operands = rest(ast) symbol = first(operands) value = EVAL(first(rest(operands)), env) except ValueError: raise RuntimeError('def! syntax is (def! /symbol/ /value/)') if is_nil(value): return env.get(symbol) env.set(symbol, value) return value elif first(ast) == make_symbol('let*'): let_error = RuntimeError('let* syntax is (let* /list_of definitions/ /list_of_instructions/)') # noqa new_env = Env(env) try: operands = rest(ast) definitions, instructions = operands except Exception: raise let_error if len(definitions) % 2 != 0: raise let_error symbol_value_pairs = list(zip( definitions[0::2], definitions[1::2], )) for symb, value in symbol_value_pairs: new_env.set(symb, EVAL(value, new_env)) ast = instructions env = new_env continue elif first(ast) == make_symbol('if'): elements = rest(ast) condition = first(elements) true_branch = first(rest(elements)) false_branch = first(rest(rest(elements))) condition = EVAL(condition, env) # empty lists, strings and 0 are 'truthy', only false and nil are 'falsy' if is_nil(condition) or is_bool(condition) and condition == FALSE: ast = false_branch else: ast = true_branch continue elif first(ast) == make_symbol('fn*'): try: op, binds, body = ast except ValueError: raise RuntimeError('fn* syntax us (fn* /arguments/ /function_body/)') # noqa def closure(*arguments): try: new_env = Env(outer=env, binds=binds, exprs=arguments) except ValueError: raise RuntimeError( 'Error: function is called with wrong number of parameters' f'expected: {len(binds)}, actual: {len(arguments)}' ) return EVAL(body, new_env) return make_function(body, binds, env, closure) elif first(ast) == make_symbol('do'): op, *exprs = ast for expr in exprs[:-1]: EVAL(expr, env) ast = exprs[-1] continue # quoting element elif ast[0] == make_symbol('quote'): return ast[1] elif ast[0] == make_symbol('quasiquote'): ast = quasiquote(ast[1]) continue elif ast[0] == make_symbol('defmacro!'): try: op, symbol, operation_ast = ast fn_sym, binds, body = operation_ast if fn_sym != make_symbol('fn*'): raise ValueError except ValueError: raise RuntimeError('defmacro! syntax is (def! /symbol/ /function_body/)') fn = make_function(body, binds, env, None, True) # fn.fn is set to None. Check in step 9 is it ok env.set(symbol, fn) return NIL elif ast[0] == make_symbol('macroexpand'): return macroexpand(ast[1], env) elif ast[0] == make_symbol('try*'): try: op, try_branch, catch = ast except ValueError: op, try_branch = ast return EVAL(try_branch, env) try: return EVAL(try_branch, env) except Exception as exc: catch_symbol, exception_symbol, catch_branch = catch return EVAL(catch_branch, Env(env, [exception_symbol], [exc])) func, *args = eval_ast(ast, env) if not is_mal_function(func): # core function return func(*args) ast = func.ast env = Env(func.env, func.params, args)
return make_list(concatenated) def apply_(fn, *args): if is_mal_function(fn): fn = fn.fn args = _flatten(args) return fn(*args) def rep(arg): return PRINT(EVAL(READ(arg), repl_env)) # setup env step 2 repl_env.set(make_symbol('eval'), eval_) 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)))))))""") # noqa repl_env.set(make_symbol('apply'), apply_) repl_env.set(make_symbol('map'), mal_map) repl_env.set(make_symbol('throw'), throw) parser = argparse.ArgumentParser() parser.add_argument('-i', '--interactive', action='store_true') parser.add_argument('filename', nargs='?', help='Filename to be executed') parser.add_argument('prog_args', nargs='*', help='Arguments passed to program') args = parser.parse_args() if __name__ == '__main__':
def EVAL(ast, env): """ Evaluate set of mal instructions. """ while True: if not is_list(ast): return eval_ast(ast, env) elif is_empty(ast): return ast elif first(ast) == make_symbol('def!'): try: operands = rest(ast) symbol = first(operands) value = EVAL(first(rest(operands)), env) except ValueError: raise RuntimeError('def! syntax is (def! /symbol/ /value/)') env.set(symbol, value) return value elif first(ast) == make_symbol('let*'): let_error = RuntimeError( 'let* syntax is (let* /list_of definitions/ /list_of_instructions/)' ) # noqa new_env = Env(env) try: operands = rest(ast) definitions, instructions = operands except Exception: raise let_error if len(definitions) % 2 != 0: raise let_error symbol_value_pairs = list( zip( definitions[0::2], definitions[1::2], )) for symb, value in symbol_value_pairs: new_env.set(symb, EVAL(value, new_env)) ast = instructions env = new_env continue elif first(ast) == make_symbol('if'): elements = rest(ast) condition = first(elements) true_branch = first(rest(elements)) false_branch = first(rest(rest(elements))) condition = EVAL(condition, env) # empty lists, strings and 0 are 'truthy', only false and nil are 'falsy' if is_nil(condition) or is_bool(condition) and condition == FALSE: ast = false_branch else: ast = true_branch continue elif first(ast) == make_symbol('fn*'): try: op, binds, body = ast except ValueError: raise RuntimeError( 'fn* syntax us (fn* /arguments/ /function_body/)') # noqa def closure(*arguments): try: new_env = Env(outer=env, binds=binds, exprs=arguments) except ValueError: raise RuntimeError( 'Error: function is called with wrong number of parameters' f'expected: {len(binds.value)}, actual: {len(arguments)}' ) return EVAL(body, new_env) return make_function(body, binds, env, closure) elif first(ast) == make_symbol('do'): op, *exprs = ast for expr in exprs[:-1]: EVAL(expr, env) ast = exprs[-1] continue func, *args = eval_ast(ast, env) if not is_mal_function(func): # core function return func(*args) ast = func.ast env = Env(func.env, func.params, args)
""" Convert result of mal instructions into string representation. """ return pr_str(mal_type) def eval_(ast): return EVAL(ast, repl_env) def rep(arg): return PRINT(EVAL(READ(arg), repl_env)) # setup env step 2 repl_env.set(make_symbol('eval'), eval_) rep("(def! not (fn* (a) (if a false true)))") rep("""(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) "\nnil)")))))""" ) parser = argparse.ArgumentParser() parser.add_argument('-i', '--interactive', action='store_true') parser.add_argument('filename', nargs='?', help='Filename to be executed') parser.add_argument('prog_args', nargs='*', help='Arguments passed to program') args = parser.parse_args() if __name__ == '__main__': arg_to_str = lambda arg: f'"{arg}"' rep(f'(def! *ARGV* {"(list " + " ".join(arg_to_str(arg) for arg in args.prog_args) + ")" })' ) if args.filename is not None:
from mal_types import ( make_symbol, make_list, is_symbol, MalException ) VARIADIC_ASSIGNMENT_SYMBOL = make_symbol('&') class Env: def __init__(self, outer=None, binds=[], exprs=[]): self._scope = {} self._outer = outer if ( len(binds) != len(exprs) and VARIADIC_ASSIGNMENT_SYMBOL not in binds ): raise ValueError for idx, elem in enumerate(binds): if elem == VARIADIC_ASSIGNMENT_SYMBOL: self.set(binds[idx + 1], make_list(exprs[idx:])) return self.set(elem, exprs[idx]) def set(self, name, value): self._scope[name] = value def find(self, name): if name in self._scope: return self elif self._outer is not None: return self._outer.find(name)
dissoc, 'readline': mal_readline, '*host-language*': make_string("\"python-by-davemus\""), 'time-ms': lambda: time() / 1000, # metadata is not supported in my mal implementation 'meta': meta, 'with-meta': with_meta, 'fn?': lambda entity: is_function(entity) or (is_mal_function(entity) and not entity.is_macro), 'macro?': lambda entity: is_mal_function(entity) and entity.is_macro, 'string?': is_string, 'number?': is_number, 'seq': seq, 'conj': conj, 'py-eval': py_eval, } namespace = {make_symbol(k): v for k, v in namespace_.items()}
from mal_types import ( is_vector, make_vector, is_hashmap, make_hashmap_from_pydict, items, is_list, make_list, is_empty, iterate, is_symbol, make_symbol, ) repl_env = { make_symbol('+'): lambda a, b: a + b, make_symbol('-'): lambda a, b: a - b, make_symbol('*'): lambda a, b: a * b, make_symbol('/'): lambda a, b: a / b } def eval_ast(ast, env): if is_vector(ast): return make_vector(EVAL(elem, env) for elem in iterate(ast)) if is_hashmap(ast): return make_hashmap_from_pydict( {key: EVAL(value, env) for key, value in items(ast)}) if is_symbol(ast): return env.get(ast)