def __call__(self, *args: Any): """ Evaluates lambdas body with passed arguments. Args/Kwarg: `*args`: Arguments to evaluate the body with. Evaluation is done by firstly pushing captured definition environment, then pushing environment created by associating values passed to the call with names provided during lambda definition; onto the environment stack and then evaluating the lambdas body. Environments are always popped from the stack even if exception happens during evaluation. If lambda was used to define a top-level function then it captures a global environment. In this case, on call, only arguments environment is pushed onto the stack as pushing a global one would create a reference cycle for the environments. """ func_args = [x for x in self._func_args] if len(func_args) != len(args): raise EvaluationError( f"number of call arguments doesn't match" f" expected {len(func_args)} got {len(args)}") env_init = dict(zip(func_args, args)) call_env = Env(env_init) self._push_envs(call_env) try: return self._evaluator.eval(self._body) finally: self._pop_envs()
def eval(source, init_env=None): if init_env is None: init_env = Env(STD_ENV) evaluator = Evaluator(init_env) comp = ObjectCompiler() ast = parser.parse(lexer.lex(source)) code = ast.accept(comp) return evaluator.eval(code)
def test_set_form_setting_value_in_outer_scope(sym, val): env = Env() eval(f"(define {sym} (quote ()))", env) eval( f""" (define a (lambda () (set! {sym} {val}))) """, env, ) eval("(a)", env) assert env[obj.Symbol(sym)] == val
def push_env(self, env: Env): """ Pushes new environment onto the stack. Assertion is made to make sure that the same environment is not pushed twice as that would create a reference cycle. And probably that is not what you want anyway. """ assert env is not self._current_env env.parent = self._current_env self._current_env = env
def test_lambda_env_capture(val): m = mock.Mock() env = Env({obj.Symbol("func"): m}) eval( """ (define a (lambda (x) (lambda () (func x)))) """, init_env=env, ) eval(f"(define b (a {val}))", init_env=env) eval("(b)", init_env=env) m.assert_called_once_with(val)
def __init__(self, env: Env = None): """ Creates new `PylisperConsole`. Args/Kwargs: `env`: Optional environment to run code with. If `None` then `STD_ENV` is used. """ super().__init__() if env is None: env = Env(STD_ENV) self.env = env self.eval = Evaluator(env) self.comp = ObjectCompiler()
def test_recursive_function_call(val): m = mock.Mock() env = Env({obj.Symbol("func"): m, **STD_ENV}) calls = [mock.call(x) for x in range(val, 0, -1)] eval( """ (define a (lambda (acc _) (cond ((= acc 0) #t) (#t (a (- acc 1) (func acc)))))) """, init_env=env, ) assert eval(f"(a {val} (func {val}))", init_env=env) m.assert_has_calls(calls)
def test_set_form_setting_value_in_inner_scope(sym, outer, inner): m = mock.Mock() env = Env({obj.Symbol("func"): m}) eval(f"(define {sym} {outer})", env) eval( f""" (define a (lambda ({sym}) (begin (set! {sym} {inner}) (func {sym})))) """, env, ) eval("(a (quote ()))", env) m.assert_called_once_with(inner) assert env[obj.Symbol(sym)] == outer
def test_against_env_reference_cycle(): env = Env(STD_ENV) eval( """ (define a (lambda (acc) (cond ((= acc 0) #t) (#t (a (- acc 1)))))) """, init_env=env, ) assert eval("(a 10)", env) eval( """ (define a (lambda (acc) (cond ((= acc 0) #t) (#t (a (- acc 1)))))) """, init_env=env, ) assert eval("(a 10)", env)
def test_lambda_evaluation(val): m = mock.Mock() env = Env({obj.Symbol("func"): m}) eval(f"((lambda (x) (func x)) {val})", init_env=env) m.assert_called_once_with(val)
def test_symbol_evaluation(sym, val): env = Env() eval(f"(define {sym} {val})", init_env=env) assert eval(sym, init_env=env) == val
def test_function_call(fst, snd): m = mock.Mock() env = Env({obj.Symbol("func"): m}) eval(f"(func {fst} {snd})", init_env=env) m.assert_called_once_with(fst, snd)
def test_symbol_definition(sym, val): env = Env() eval(f"(define {sym} {val})", init_env=env) assert obj.Symbol(sym) in env assert env[obj.Symbol(sym)] == val
def test_set_form_on_some_cell_in_list(init, val): env = Env(STD_ENV) list_vals = " ".join(map(str, init)) eval(f"(define loc (quote ({list_vals})))", env) eval(f"(set! (car (cdr (cdr loc))) {val})", env) assert env[obj.Symbol("loc")].cdr.cdr.value == val
def test_set_form_evaluation(sym, val): env = Env() eval(f"(define {sym} (quote ()))", env) eval(f"(set! {sym} {val})", env) assert env[obj.Symbol(sym)] == val