def pr_str(entity, print_readably=True): if isinstance(entity, MalException): return f'{pr_str(entity.value)}' if isinstance(entity, Exception): return entity.args[0] elif is_function(entity): return '#function' elif is_nil(entity): return 'nil' elif is_bool(entity): return { TRUE: 'true', FALSE: 'false', }[entity] elif is_number(entity): return str(entity) elif is_symbol(entity): return str(entity, 'utf-8') elif is_keyword(entity): return ':' + entity[1:] elif is_string(entity): if print_readably: return '"' + entity.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') + '"' return entity elif is_atom(entity): return f'(atom {deref(entity)})' elif is_list(entity): return '(' + ' '.join(pr_str(inner, print_readably) for inner in entity) + ')' elif is_vector(entity): return '[' + ' '.join(pr_str(inner, print_readably) for inner in entity) + ']' elif is_hashmap(entity): return ( '{' + ' '.join(f'{pr_str(k, print_readably)} {pr_str(v, print_readably)}' for k, v in entity.items()) + '}' ) raise RuntimeError(f'pr_str: unknown type {type(entity)}')
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)
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)