def test_lambda_with_argument_and_env(self): """Test that arguments overshadow variables defined in the environment when the function body is evaluated""" env = Environment({"x": integer(1)}) ast = [["lambda", ["x"], "x"], integer(2)] assert_equals(integer(2), evaluate(ast, env))
def test_defining_then_looking_up_function(self): """Test calling named function that's been previously defined from the environment""" env = Environment() evaluate(["define", "my-fn", ["lambda", ["x"], "x"]], env) assert_equals(integer(42), evaluate(["my-fn", integer(42)], env))
def test_lambda_with_free_var(self): """Tests that the lambda have access to variables from the environment in which it was defined""" env = Environment({"free-variable": integer(100)}) ast = [["lambda", [], "free-variable"]] assert_equals(integer(100), evaluate(ast, env))
def test_nested_quasiquotes(self): assert_equals(["quasiquote", ["quasiquote", ["quasiquote", "foo"]]], parse("```foo")) assert_equals(["quasiquote", ["quasiquote", ["quasiquote", ["+", integer(1), integer(2)]]]], parse("```(+ 1 2)"))
def test_lambda_closure_keeps_defining_env(self): """The closure should keep a copy of the environment where it was defined""" env = Environment({"foo": integer(1), "bar": integer(2)}) ast = ["lambda", [], integer(42)] lm = evaluate(ast, env) assert_equals(lm.env, env)
def test_set_bang(self): """The set! special form updates an already defined variable.""" env = Environment({"x": integer(1)}) ast = ["set!", "x", integer(2)] evaluate(ast, env) assert_equals(integer(2), env["x"])
def test_calling_with_wrong_number_of_arguments(self): """Lambda should raise exception when called with wrong number of arguments""" env = Environment() evaluate(["define", "fn", ["lambda", ["x", "y"], integer(42)]], env) with assert_raises_regexp(LispTypeError, "expected 2"): evaluate(["fn", integer(1)], env)
def test_quote(self): """Quoting returns the expression being quoted without evaluating it.""" ast = ["quote", ["foo", ["+", integer(1), integer(2)], ["*", integer(4), integer(10)]]] assert_equals(ast[1], evaluate(ast, Environment()))
def test_define_with_wrong_number_of_arguments(self): """Defines should have exactly two arguments, or raise an error""" with assert_raises_regexp(LispSyntaxError, "Wrong number of arguments"): evaluate(["define", "x"], Environment()) with assert_raises_regexp(LispSyntaxError, "Wrong number of arguments"): evaluate(["define", "x", integer(1), integer(2)], Environment())
def test_simple_let_expression(self): """Let expressions should create a new environment with the new definitions, then evaluate the body with this environment, and not make any changes to the outer environment""" env = Environment({"foo": integer(1)}) ast = parse("(let ((foo 2)) foo)") assert_equals(integer(2), evaluate(ast, env)) assert_equals(integer(1), env["foo"])
def test_set_bang_only_updates_visible_variable(self): """Only the innermost (visible) variable binding should be updated""" outer = Environment({"x": integer(1)}) middle = Environment({"x": integer(2)}, outer) inner = Environment({}, middle) evaluate(["set!", "x", integer(3)], inner) assert_false("x" in inner) assert_equals(integer(3), middle["x"]) assert_equals(integer(1), outer["x"])
def test_calling_builtin_forces_argument_evaluation(self): """Bultins (like the regular lambdas) are call-by-value and the arguments thould therefore be evaluated first""" env = Environment({ 'x': integer(2), '+': Builtin(lambda a, b: integer(value_of(a) + value_of(b))) }) ast = ['+', ['cond', [boolean(True), integer(2)], [boolean(True), 'whatever']], 'x'] assert_equals(integer(4), evaluate(ast, env))
def test_begin_form(self): """Testing evaluating expressions in sequence with the begin special form""" env = Environment() result = evaluate(parse(""" (begin (define foo 1) (define bar 2) foo) """), env) assert_equals(integer(1), result) assert_equals(Environment({"foo": integer(1), "bar": integer(2)}), env)
def test_lambda_evaluates_to_lambda_which_is_a_closure(self): """The lambda form should evaluate to a lambda object extending closure""" ast = ["lambda", [], integer(42)] lm = evaluate(ast, Environment()) assert_is_instance(lm, Lambda) assert_is_instance(lm, Closure)
def test_cond(self): program = """ (cond (#f 1) ((atom '(1 2 3)) 2) ((atom 'foo) 3) (#t 4)) """ assert_equals(integer(3), evaluate(parse(program), Environment()))
def test_parse_comments(self): program = """ ;; this first line is a comment (define variable ; here is another comment (if #t 42 ; inline comment! (something else))) """ expected_ast = ['define', 'variable', ['if', boolean(True), integer(42), ['something', 'else']]] assert_equals(expected_ast, parse(program))
def test_calling_function_recursively(self): """Tests that a named function is included in the environment where it is evaluated""" oposite = """ (define oposite (lambda (p) (cond (p #f) (#t #t)))) """ fn = """ (define fn ;; Meaningless (albeit recursive) function (lambda (x) (cond (x (fn (oposite x))) (#t 1000)))) """ env = Environment() evaluate(parse(oposite), env) evaluate(parse(fn), env) assert_equals(integer(1000), evaluate(["fn", boolean(True)], env)) assert_equals(integer(1000), evaluate(["fn", boolean(False)], env))
def test_atom(self): env = Environment() assert_equals(boolean(True), evaluate(["atom", boolean(True)], env)) assert_equals(boolean(True), evaluate(["atom", boolean(False)], env)) assert_equals(boolean(True), evaluate(["atom", integer(42)], env)) assert_equals(boolean(True), evaluate(["atom", "foo"], Environment({"foo": "bar"}))) assert_equals(boolean(False), evaluate(["atom", "foo"], Environment({"foo": ["bar"]}))) assert_equals(boolean(False), evaluate(["atom", ["quote", ["foo", "bar"]]], env))
def test_simple_quasiquote_with_unquotes(self): env = Environment({"bar": 'inc', "foo": integer(42)}) ast = ['quasiquote', [['unquote', 'bar'], ['unquote', 'foo']]] assert_equals(parse("`(,bar ,foo)"), ast) assert_equals(['inc', integer(42)], evaluate(ast, env))
def test_expand_quasiquoted_list(self): assert_equals(["quasiquote", ["+", integer(1), integer(2)]], parse("`(+ 1 2)"))
def test_expand_single_quoted_list(self): assert_equals(["foo", ["quote", ["+", integer(1), integer(2)]]], parse("(foo '(+ 1 2))")) assert_equals(["foo", ["quote", [boolean(True), boolean(False)]]], parse("(foo '(#t #f))"))
def test_parse_quote_tick_on_atom(self): assert_equals(["quote", integer(1)], parse("'1")) assert_equals(["quote", boolean(True)], parse("'#t"))
def test_quasiqute_with_unquote(self): assert_equals(["quasiquote", ["+", ["unquote", "foo"], ["unquote", "bar"], integer(42)]], parse("`(+ ,foo ,bar 42)"))
def test_eval_integer(self): assert_equals(integer(42), evaluate(integer(42), Environment()))
def test_parse_with_types(self): program = '(if #f (* 42 x) 100)' ast = ['if', boolean(False), ['*', integer(42), 'x'], integer(100)] assert_equals(ast, parse(program))
def test_calling_a_builtin_function(self): """Tests that calling a builtin function gives the expected result""" env = Environment({'+': Builtin(lambda a, b: integer(value_of(a) + value_of(b)))}) assert_equals(integer(4), evaluate(["+", integer(2), integer(2)], env))
def test_define_with_nonsymbol_as_variable(self): """Malformed defines should throw an error""" with assert_raises_regexp(LispSyntaxError, "non-symbol"): evaluate(["define", boolean(True), integer(42)], Environment())
def test_define(self): """Test simplest possible define""" env = Environment() evaluate(["define", "x", integer(1000)], env) assert_equals(integer(1000), env["x"])
def test_eval_quasiquote_without_unquote(self): assert_equals("foo", evaluate(["quasiquote", "foo"], Environment())) assert_equals(["+", integer(1), integer(2)], evaluate(["quasiquote", ["+", integer(1), integer(2)]], Environment()))
def test_eval_quasiquote_with_deeper_unquotes(self): env = Environment({"foo": integer(42), "bar": integer(100)}) ast = ["quasiquote", ["+", ["unquote", "foo"], ["+", integer(1), ["unquote", "bar"]]]] assert_equals(["+", integer(42), ["+", integer(1), integer(100)]], evaluate(ast, env))