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_eq_on_non_atoms(self): """Only atoms can equal with eq Two lists are not equal (as measured by `eq`) even if they contains the same elements.""" env = Environment() ast = ["eq", ["quote", ["foo", "bar"]], ["quote", ["foo", "bar"]]] assert_equals(boolean(False), evaluate(ast, env))
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_expand_single_quoted_symbol(self): assert_equals(["foo", ["quote", "bar"]], parse("(foo 'bar)")) assert_equals(["foo", ["quote", boolean(True)]], parse("(foo '#t)")) assert_equals(["foo", ["quote", '+']], parse("(foo '+)"))
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_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_unparse_list(self): assert_equals("(1 2 3)", unparse([integer(1), integer(2), integer(3)])) assert_equals("(if #t 42 #f)", unparse(["if", boolean(True), integer(42), boolean(False)]))
def test_expand_quasiquoted_symbol(self): assert_equals(["quasiquote", "foo"], parse("`foo")) assert_equals(["quasiquote", "+"], parse("`+")) assert_equals(["quasiquote", boolean(False)], parse("`#f"))
def test_set_bang_on_undefined_variable(self): """Only defined variables can be updated.""" with assert_raises_regexp(LispNamingError, "undefined"): evaluate(["set!", "wtf", boolean(True)], Environment())
def test_eval_boolean(self): assert_equals(boolean(True), evaluate(boolean(True), Environment())) assert_equals(boolean(False), evaluate(boolean(False), Environment()))
def test_simple_lookup_from_env(self): env = Environment({"foo": integer(42), "bar": boolean(True)}) assert_equals(integer(42), evaluate("foo", env))
def test_call_to_non_function(self): "Should raise a TypeError when a non-closure is called as a function" with assert_raises(LispTypeError): evaluate([boolean(True), integer(1), integer(2)], Environment()) with assert_raises(LispTypeError): evaluate(["foo", integer(1), integer(2)], Environment({"foo": integer(42)}))
def test_parse_quote_tick_on_atom(self): assert_equals(["quote", integer(1)], parse("'1")) assert_equals(["quote", boolean(True)], parse("'#t"))
def test_eq_on_two_equal_atoms(self): """Two of the same atom are equal""" ast = ["eq", ["quote", "foo"], ["quote", "foo"]] assert_equals(boolean(True), evaluate(ast, Environment()))
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_eq_on_two_different_atoms(self): """Two different atoms are not equal""" ast = ["eq", ["quote", "foo"], ["quote", "bar"]] assert_equals(boolean(False), evaluate(ast, Environment()))
def test_expand_unquoted_symbol(self): assert_equals(["unquote", "foo"], parse(",foo")) assert_equals(["unquote", "+"], parse(",+")) assert_equals(["unquote", boolean(False)], parse(",#f"))
def test_unparse_bool(self): assert_equals("#t", unparse(boolean(True))) assert_equals("#f", unparse(boolean(False)))