def test_calling_with_wrong_number_of_arguments():
    """Functions should raise exceptions when called with wrong number of arguments."""

    env = Environment()
    evaluate(parse("(def fn (lambda (p1 p2) 'what_ever))"), env)
    with assert_raises(LispError):
        evaluate(parse("(fn 1 2 3)"), env)
def test_calling_atom_raises_exception():
    """A function call to a non-function should result in an error."""

    with assert_raises(LispError):
        evaluate(parse("(True 'foo 'bar)"), Environment())
    with assert_raises(LispError):
        evaluate(parse("(42)"), Environment())
예제 #3
0
def test_parse_exception_extra_paren():
    """Another exception is raised if the expression is too large.

    The parse function expects to recieve only one single expression. Anything
    more than this, should result in the proper exception."""

    with assert_raises(LispError):
        parse('(foo (bar x y)))')
def test_define_with_wrong_number_of_arguments():
    """Defines should have exactly two arguments, or raise an error"""

    with assert_raises(LispError):
        evaluate(parse("(def x)"), Environment())

    with assert_raises(LispError):
        evaluate(parse("(def x 1 2)"), Environment())
예제 #5
0
def test_combined_math_operators():
    """To be able to do anything useful, we need some combined math operators.

    Let us try stringing together some additions.
    """

    assert_equals(10, evaluate(parse("(+ (+ 1 2) (+ 3 4))"), Environment()))
    assert_equals(10.8123, evaluate(parse("(+ (+ 1.2 2.0123) (+ 3.5 4.1))"), Environment()))
예제 #6
0
def test_define_with_wrong_number_of_arguments():
    """Defines should have exactly two arguments, or raise an error"""

    with assert_raises(LispError):
        evaluate(parse("(def x)"), Environment())

    with assert_raises(LispError):
        evaluate(parse("(def x 1 2)"), Environment())
예제 #7
0
def test_parse_boolean():
    """Parsing single booleans.

    Booleans are the special symbols True and False. In the ASTs they are represented 
    by Pythons True and False, respectively. """

    assert_equals(True, parse('True'))
    assert_equals(False, parse('False'))
예제 #8
0
def test_checking_whether_list_is_empty():
    """The `empty` form checks whether or not a list is empty."""

    assert_equals(False, evaluate(parse("(empty '(1 2 3))"), Environment()))
    assert_equals(False, evaluate(parse("(empty '(1))"), Environment()))

    assert_equals(True, evaluate(parse("(empty '())"), Environment()))
    assert_equals(True, evaluate(parse("(empty (tail '(1)))"), Environment()))
def test_lambda_arguments_are_lists():
    """The parameters of a `lambda` should be a list."""

    closure = evaluate(parse("(lambda (x y) (+ x y))"), Environment())
    assert_true(is_list(closure.params))

    with assert_raises(LispError):
        evaluate(parse("(lambda not-a-list (body of fn))"), Environment())
예제 #10
0
def test_creating_longer_lists_with_only_cons():
    """`cons` needs to evaluate it's arguments.

    Like all the other special forms and functions in our language, `cons` is 
    call-by-value. This means that the arguments must be evaluated before we 
    create the list with their values."""

    result = evaluate(parse("(cons 3 (cons (- 4 2) (cons 1 '())))"), Environment())
    assert_equals(parse("(3 2 1)"), result)
def test_set_functionality():
    """Tests if we can correctly reset the value of a
    variable using `set`."""

    env = Environment()
    evaluate(parse("""(def xyz 123)"""), env)

    assert_equals(123, evaluate(parse("xyz"), env))
    evaluate(parse("""(set xyz 314)"""), env)
    assert_equals(314, evaluate(parse("xyz"), env))
예제 #12
0
def test_parse_integer():
    """Parsing single integer.

    Integers are represented in the ASTs as Python ints.

    Tip: String objects have a handy .isdigit() method.
    """

    assert_equals(42, parse('42'))
    assert_equals(1337, parse('1337'))
예제 #13
0
def test_evaluating_eq_function():
    """The `eq` form is used to check whether two expressions are the same atom."""

    assert_equals(True, evaluate(["eq", 1, 1], Environment()))
    assert_equals(False, evaluate(["eq", 1, 2], Environment()))
    assert_equals(True, evaluate(parse("(eq 'foo 'foo)"), Environment()))
    assert_equals(False, evaluate(parse("(eq 'foo 'bar)"), Environment()))

    # Lists are never equal, because lists are not atoms
    assert_equals(False, evaluate(parse("(eq '(1 2 3) '(1 2 3))"), Environment()))
def test_call_to_function_should_evaluate_arguments():
    """Call to function should evaluate all arguments.

    When a function is applied, the arguments should be evaluated before being bound
    to the parameter names."""

    env = Environment()
    closure = evaluate(parse("(lambda (a) (+ a 5))"), env)
    ast = [closure, parse("(if False 0 (+ 10 10))")]

    assert_equals(25, evaluate(ast, env))
예제 #15
0
def test_parse_list_of_symbols():
    """Parsing list of only symbols.

    A list is represented by a number of elements surrounded by parens. Python lists 
    are used to represent lists as ASTs.

    Tip: The useful helper function `find_matching_paren` is already provided in
    `parse.py`.
    """

    assert_equals(['foo', 'bar', 'baz'], parse('(foo bar baz)'))
    assert_equals([], parse('()'))
def test_calling_very_simple_function_in_environment():
    """A call to a symbol corresponds to a call to its value in the environment.

    When a symbol is the first element of the AST list, it is resolved to its value in
    the environment (which should be a function closure). An AST with the variables
    replaced with its value should then be evaluated instead."""

    env = Environment()
    evaluate(parse("(def add (lambda (x y) (+ x y)))"), env)
    assert_is_instance(env.lookup("add"), Closure)

    result = evaluate(parse("(add 1 2)"), env)
    assert_equals(3, result)
예제 #17
0
def test_evaluating_atom_function():
    """The `atom` form is used to determine whether an expression is an atom.

    Atoms are expressions that are not list, i.e. integers, booleans or symbols.
    Remember that the argument to `atom` must be evaluated before the check is done.
    """

    assert_equals(True, evaluate(["atom", True], Environment()))
    assert_equals(True, evaluate(["atom", False], Environment()))
    assert_equals(True, evaluate(["atom", 42], Environment()))
    assert_equals(True, evaluate(["atom", ["quote", "foo"]], Environment()))
    assert_equals(False, evaluate(["atom", ["quote", [1, 2]]], Environment()))
    assert_equals(False, evaluate(["atom", parse("'(1 2 3)")], Environment()))
    assert_equals(False, evaluate(parse("(atom '(1 2 3))"), Environment()))
예제 #18
0
def test_undefined_decorator():

    program2 = """(@inc
    (def power_2
    (lambda (n)
        (* n n)))
        )
    """

    env = Environment()
    second_ast = parse(program2)
    evaluate(second_ast, env)
    third_ast = parse("(power_2 3)")
    with assert_raises(LispError):
        assert_equals(10, evaluate(third_ast, env))
예제 #19
0
def test_parse_list_of_mixed_types():
    """Parsing a list containing different types.

    When parsing lists, make sure each of the sub-expressions are also parsed 
    properly."""

    assert_equals(['foo', True, 123], parse('(foo True 123)'))
예제 #20
0
def test_parse_single_symbol():
    """Parsing a single symbol.

    Symbols are represented by text strings. Parsing a single atom should result
    in an AST consisting of only that symbol."""

    assert_equals('foo', parse('foo'))
def test_calling_function_recursively():
    """Tests that a named function is included in the environment
    where it is evaluated."""

    env = Environment()
    evaluate(parse("""
        (def my-fn
            # A meaningless, but recursive, function
            (lambda (x)
                (if (eq x 0)
                    42
                    (my-fn (- x 1)))))
    """), env)

    assert_equals(42, evaluate(parse("(my-fn 0)"), env))
    assert_equals(42, evaluate(parse("(my-fn 10)"), env))
def test_lambda_closure_holds_function():
    """The closure contains the parameter list and function body too."""

    closure = evaluate(parse("(lambda (x y) (+ x y))"), Environment())

    assert_equals(["x", "y"], closure.params)
    assert_equals(["+", "x", "y"], closure.body)
예제 #23
0
def test_variable_lookup_after_define():
    """Test define and lookup variable in same environment.

    This test should already be working when the above ones are passing."""

    env = Environment()
    evaluate(parse("(def foo (+ 2.1 2.1))"), env)
    assert_equals(4.2, evaluate("foo", env))
def test_let_functionality():
    """Tests if functions can have sub-functions that are
    namespace specific using the classic 'let' functionality."""

    env = Environment()
    evaluate(parse("""
        (def summer 
            (lambda (a b) 
                (let plusr 
                    (lambda (i j) 
                        (+ i j)) 
                (plusr a b))))
        """), env)

    assert_equals(42, evaluate(parse("(summer 40 2)"), env))
    with assert_raises(LispError):
        evaluate(parse("(plusr 40 2)"), env)
def test_variable_lookup_after_define():
    """Test define and lookup variable in same environment.

    This test should already be working when the above ones are passing."""

    env = Environment()
    evaluate(parse("(def foo (+ 2.1 2.1))"), env)
    assert_equals(4.2, evaluate("foo", env))
예제 #26
0
def test_getting_tail_of_list():
    """`tail` returns the tail of the list.

    The tail is the list retained after removing the first element."""

    assert_equals("(2 3)", interpret("(tail '(1 2 3))", Environment()))
    assert_equals([2, 3], evaluate(parse("(tail '(1 2 3))"), Environment()))
    assert_equals([2, 3], evaluate(['tail', ['quote', [1, 2, 3]]], Environment()))
예제 #27
0
def test_getting_tail_of_rlist():
    """`tail` returns the reverse of the tail of the list.

    That is all the elements except the last."""

    assert_equals("(1 2)", interpret("(rtail '(1 2 3))", Environment()))
    assert_equals([1, 2], evaluate(parse("(rtail '(1 2 3))"), Environment()))
    assert_equals([1, 2], evaluate(['rtail', ['quote', [1, 2, 3]]], Environment()))
예제 #28
0
def test_parse_on_nested_list():
    """Parsing should also handle nested lists properly."""

    program = '(foo (bar ((True)) x) (baz y))'
    ast = ['foo', 
            ['bar', [[True]], 'x'], 
            ['baz', 'y']]
    assert_equals(ast, parse(program))
def test_calling_lambda_directly():
    """It should be possible to define and call functions directly.

    A lambda definition in the call position of an AST should be evaluated, and then
    evaluated as before."""

    ast = parse("((lambda (x) x) 42)")
    result = evaluate(ast, Environment())
    assert_equals(42, result)
def test_basic_if_statement():
    """If statements are the basic control structures.

    The `if` should first evaluate it's first argument. If this evaluates to true, then
    the second argument is evaluated and returned. Otherwise the third and last argument
    is evaluated and returned instead."""

    if_expression = parse("(if True 42 1000)")
    assert_equals(42, evaluate(if_expression, Environment()))
def test_if_with_sub_expressions():
    """A final test with a more complex if expression.
    This test should already be passing if the above ones are."""

    if_expression = parse("""
        (if (> 1 2)
            (- 1000 1)
            (+ 40 (- 3 1)))
    """)
    assert_equals(42, evaluate(if_expression, Environment()))
예제 #32
0
def test_parse_with_extra_whitespace():
    """Excess whitespace should be removed."""

    program = """

       (program    with   much        whitespace)
    """

    expected_ast = ['program', 'with', 'much', 'whitespace']
    assert_equals(expected_ast, parse(program))
예제 #33
0
def test_evaluating_quote():
    """When a call is done to the `quote` form, the argument should be returned without 
    being evaluated.

    (quote foo) -> foo
    """

    assert_equals("foo", evaluate(["quote", "foo"], Environment()))
    assert_equals([1, 2, False], evaluate(["quote", [1, 2, False]], Environment()))
    assert_equals([1, 2, 3], evaluate(parse("'(1 2 3)"), Environment()))
예제 #34
0
def test_expand_single_quoted_symbol():
    """Quoting is a shorthand syntax for calling the `quote` form.

    Examples:

        'foo -> (quote foo)
        '(foo bar) -> (quote (foo bar))

    """
    assert_equals(["foo", ["quote", "nil"]], parse("(foo 'nil)"))
예제 #35
0
def test_define():
    """Test of simple define statement.

    The `define` form is used to define new bindings in the environment.
    A `define` call should result in a change in the environment. What you
    return from evaluating the definition is not important (although it 
    affects what is printed in the REPL)."""

    env = Environment()
    evaluate(parse("(def x 100.25)"), env)
    assert_equals(100.25, env.lookup("x"))
def test_defining_lambda_with_error_in_body():
    """The function body should not be evaluated when the lambda is defined.

    The call to `lambda` should return a function closure holding, among other things
    the function body. The body should not be evaluated before the function is called."""

    ast = parse("""
            (lambda (x y)
                (function body ((that) would never) work))
    """)
    assert_is_instance(evaluate(ast, Environment()), Closure)
def test_evaluating_call_to_closure():
    """The first case we'll handle is when the AST is a list with an actual closure
    as the first element.

    In this first test, we'll start with a closure with no arguments and no free
    variables. All we need to do is to evaluate and return the function body."""

    closure = evaluate(parse("(lambda () (+ 1 2))"), Environment())
    ast = [closure]
    result = evaluate(ast, Environment())
    assert_equals(3, result)
예제 #38
0
def test_creating_lists_by_quoting():
    """One way to create lists is by quoting.

    We have already implemented `quote` so this test should already be
    passing.

    The reason we need to use `quote` here is that otherwise the expression would
    be seen as a call to the first element -- `1` in this case, which obviously isn't
    even a function."""

    assert_equals([1, 2, 3, True], evaluate(parse("'(1 2 3 True)"), Environment()))
def test_print():
    """It is hard to capture standard out in a test, so this test will
    just make sure that print statements don't kill the build."""

    if_expression = parse("""
        (print "Hey there, fly droog 2"
            (if (> 1 2)
                (- 1000 1)
                (+ 40 (- 3 1))))
    """)
    assert_equals(42, evaluate(if_expression, Environment()))
def test_nested_expression():
    """Remember, functions should evaluate their arguments. 

    (Except `quote` and `if`, that is, which aren't really functions...) Thus, 
    nested expressions should work just fine without any further work at this 
    point.

    If this test is failing, make sure that `+`, `>` and so on is evaluating 
    their arguments before operating on them."""

    nested_expression = parse("(eq False (> (- (+ 1 3) (* 2 (mod 7 4))) 4))")
    assert_equals(True, evaluate(nested_expression, Environment()))
def test_evaluating_call_to_closure_with_arguments():
    """The function body must be evaluated in an environment where the parameters are bound.

    Create an environment where the function parameters (which are stored in the closure)
    are bound to the actual argument values in the function call. Use this environment
    when evaluating the function body."""

    env = Environment()
    closure = evaluate(parse("(lambda (a b) (+ a b))"), env)
    ast = [closure, 4, 5]

    assert_equals(9, evaluate(ast, env))