def test_length(): """Count the number of element in the list. Tip: How many elements are there in the empty list?""" assert_equals("5", interpret("(length '(1 2 3 4 5))", env)) assert_equals("3", interpret("(length '(#t '(1 2 3) 'foo-bar))", env)) assert_equals("0", interpret("(length '())", env))
def test_map(): """TEST 7.14: Map applies a given function to all elements of a list.""" interpret(""" (define inc (lambda (x) (+ 1 x))) """, env) assert_equals("(2 3 4)", interpret("(map inc '(1 2 3))", env))
def test_append(): """Append should merge two lists together.""" assert_equals("()", interpret("(append '() '())", env)) assert_equals("(1)", interpret("(append '() '(1))", env)) assert_equals("(2)", interpret("(append '(2) '())", env)) assert_equals("(1 2 3 4 5)", interpret("(append '(1 2) '(3 4 5))", env)) assert_equals("(#t #f 'maybe)", interpret("(append '(#t) '(#f 'maybe))", env))
def test_reverse(): """Reverse simply outputs the same list with elements in reverse order. Tip: See if you might be able to utilize the function you just made.""" assert_equals("()", interpret("(reverse '())", env)) assert_equals("(1)", interpret("(reverse '(1))", env)) assert_equals("(4 3 2 1)", interpret("(reverse '(1 2 3 4))", env))
def test_strings_have_heads_and_tails(): """ Next, `head` and `tail` needs to extract the first character and the rest of the characters, respectively, from the string. """ assert_equals('"f"', interpret('(head "foobar")')) assert_equals('"oobar"', interpret('(tail "foobar")'))
def test_range(): """Output a list with a range of numbers. The two arguments define the bounds of the (inclusive) bounds of the range.""" assert_equals("(1 2 3 4 5)", interpret("(range 1 5)", env)) assert_equals("(1)", interpret("(range 1 1)", env)) assert_equals("()", interpret("(range 2 1)", env))
def test_filter(): """Filter removes any element not satisfying a predicate from a list.""" interpret(""" (define even (lambda (x) (eq (mod x 2) 0))) """, env) assert_equals("(2 4 6)", interpret("(filter even '(1 2 3 4 5 6))", env))
def test_let_bindings_do_not_affect_outer_environment(): """ After the let is evaluated, all of it's bindings are forgotten """ interpret("(define foo 1)", env) assert_equals("2", interpret("(let ((foo 2)) foo)", env)) assert_equals("1", interpret("foo", env))
def test_defn_binds_the_variable_just_like_define(): """ Like `define`, the `defn` form should bind a variable to the environment. This variable should be a closure, just like if we had defined a new variable using the old `define` + `lambda` syntax. """ interpret("(defn foo (x) (> x 10))", env) assert_is_instance(env.lookup("foo"), Closure)
def test_let_bindings_overshadow_outer_environment(): """ Let bindings should shadow definitions in from outer environments """ interpret("(define foo 1)", env) program = """ (let ((foo 2)) foo) """ assert_equals("2", interpret(program, env))
def test_cond_doesnt_evaluate_all_branches(): """ Of all the second tuple elements, only the one we return is ever evaluated. """ interpret("(define foo 42)", env) program = """ (cond ((#f fire-the-missiles) (#t foo) (#f something-else-we-wont-do))) """ assert_equals("42", interpret(program, env))
def test_empty_strings_behave_as_empty_lists(): """ It is common in many languages for strings to behave as lists. This can be rather convenient, so let's make it that way here as well. We have four basic list functions: `cons`, `head`, `tail` and `empty`. To take the easy one first: `empty` should only return `#t` for the empty string (and empty lists, as before). """ assert_equals("#t", interpret('(empty "")')) assert_equals("#f", interpret('(empty "not empty")'))
def test_defn_result_in_the_correct_closure(): """ The closure created should be no different than from the old syntax. """ interpret("(defn foo-1 (x) (> x 10))", env) interpret("(define foo-2 (lambda (x) (> x 10)))", env) foo1 = env.lookup("foo-1") foo2 = env.lookup("foo-2") assert_equals(foo1.body, foo2.body) assert_equals(foo1.params, foo2.params) assert_equals(foo1.env, foo2.env)
def test_evaluating_strings(): """ Strings are one of the basic data types, and thus an atom. Strings should therefore evaluate to themselves. """ random_quote = '"The limits of my language means the limits of my world."' assert_equals(random_quote, interpret(random_quote, env))
def test_parsing_of_strings(): """ A final sanity check, to make sure parsing strings works. This test should already pass if you've done the above correctly. """ program = "(head '(#t \"()((()()) wtf \\\" ())) is )))) ()() going on \"))" assert_equals("#t", interpret(program))
def test_let_returns_result_of_the_given_expression(): """ The result when evaluating a `let` binding is the evaluation of the expression given as argument. Let's first try one without any bindings. """ program = "(let () (if #t 'yep 'nope))" assert_equals("yep", interpret(program, env))
def test_cond_evaluates_predicates(): """ Remember to evaluate the predicates before checking whether they are true. """ program = """ (cond (((not #t) 'totally-not-true) ((> 4 3) 'tru-dat))) """ assert_equals("tru-dat", interpret(program, env))
def test_gcd(): """Tests Greatest Common Divisor (GCD). This test is intended to run after you have completed the core of the language, just to make sure that everything is holding together. """ program = """ (define gcd (lambda (a b) (if (eq b 0) a (gcd b (mod a b))))) """ env = Environment() interpret(program, env) assert_equals("6", interpret("(gcd 108 30)", env)) assert_equals("1", interpret("(gcd 17 5)", env))
def test_let_bindings_have_access_to_previous_bindings(): """ Each new binding should have access to the previous bindings in the list """ program = """ (let ((foo 10) (bar (+ foo 5))) bar) """ assert_equals("15", interpret(program, env))
def test_let_extends_environment(): """ The evaluation of the inner expression should have available the bindings provided within the first argument. """ program = """ (let ((foo (+ 1000 42))) foo) """ assert_equals("1042", interpret(program, env))
def test_cond_not_evaluating_more_predicates_than_necessary(): """ Once we find a predicate that evaluates to `#t`, no more predicates should be evaluated. """ program = """ (cond ((#f 1) (#t 2) (dont-evaluate-me! 3))) """ assert_equals("2", interpret(program, env))
def test_reduce(): """TEST 7.15: Reduce, also known as fold, reduce a list into a single value. It does this by combining elements two by two, until there is only one left. If this is unfamiliar to you, have a look at: http://en.wikipedia.org/wiki/Fold_(higher-order_function) """ interpret( """ (define max (lambda (a b) (if (> a b) a b))) """, env) # Evaluates as (max 1 (max 6 (max 3 (max 2 0)))) -> 6 assert_equals("6", interpret("(reduce max 0 '(1 6 3 2))", env)) interpret( """ (define add (lambda (a b) (+ a b))) """, env) # Lets see if we can improve a bit on `sum` while we're at it assert_equals("10", interpret("(reduce add 0 (range 1 4))", env))
def test_reduce(): """TEST 7.15: Reduce, also known as fold, reduce a list into a single value. It does this by combining elements two by two, until there is only one left. If this is unfamiliar to you, have a look at: http://en.wikipedia.org/wiki/Fold_(higher-order_function) """ interpret(""" (define max (lambda (a b) (if (> a b) a b))) """, env) # Evaluates as (max 1 (max 6 (max 3 (max 2 0)))) -> 6 assert_equals("6", interpret("(reduce max 0 '(1 6 3 2))", env)) interpret(""" (define add (lambda (a b) (+ a b))) """, env) # Lets see if we can improve a bit on `sum` while we're at it assert_equals("10", interpret("(reduce add 0 (range 1 4))", env))
def test_cond_returns_false_as_default(): """ If we evaluate all the predicates, only to find that none of them turned out to be true, then `cond` should return `#f`. """ program = """ (cond ((#f 'no) (#f 'nope) (#f 'i-dont-even))) """ assert_equals("#f", interpret(program, env))
def test_cond_returns_right_branch(): """ `cond` takes as arguments a list of tuples (two-element lists, or "conses"). The first element of each tuple is evaluated in order, until one evaluates to `#t`. The second element of that tuple is returned. """ program = """ (cond ((#f 'foo) (#t 'bar) (#f 'baz))) """ assert_equals("bar", interpret(program, env))
def test_sort(): """TEST 7.16: Implementing (sort ...)""" assert_equals("()", interpret("(sort '())", env)) assert_equals("(1)", interpret("(sort '(1))", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(6 3 7 2 4 1 5))", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(1 2 3 4 5 6 7))", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(7 6 5 4 3 2 1))", env)) assert_equals("(1 1 1)", interpret("(sort '(1 1 1))", env))
def test_sort(): assert_equals("()", interpret("'()", env)) assert_equals("(1)", interpret("'(1)", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(6 3 7 2 4 1 5))", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(1 2 3 4 5 6 7))", env)) assert_equals("(1 2 3 4 5 6 7)", interpret("(sort '(7 6 5 4 3 2 1))", env)) assert_equals("(1 1 1)", interpret("(sort '(1 1 1))", env))
def test_xor(): """TEST 7.4: Implementing (xor ...)""" assert_equals("#f", interpret('(xor #f #f)', env)) assert_equals("#t", interpret('(xor #t #f)', env)) assert_equals("#t", interpret('(xor #f #t)', env)) assert_equals("#f", interpret('(xor #t #t)', env))
def test_sum(): """TEST 7.9: Calculate the sum of all elements in the list.""" assert_equals("5", interpret("(sum '(1 1 1 1 1))", env)) assert_equals("10", interpret("(sum '(1 2 3 4))", env)) assert_equals("0", interpret("(sum '())", env))
def test_greater_or_equal(): """TEST 7.5: Implementing (>= ...)""" assert_equals("#f", interpret('(>= 1 2)', env)) assert_equals("#t", interpret('(>= 2 2)', env)) assert_equals("#t", interpret('(>= 2 1)', env))
def test_consing_strings_back_together(): """ Finally, we need to be able to reconstruct a string from its head and tail """ assert_equals('"foobar"', interpret('(cons "f" "oobar")'))
def test_less_or_equal(): """TEST 7.6: Implementing (<= ...)""" assert_equals("#t", interpret('(<= 1 2)', env)) assert_equals("#t", interpret('(<= 2 2)', env)) assert_equals("#f", interpret('(<= 2 1)', env))
def test_less_than(): """TEST 7.7: Implementing (< ...)""" assert_equals("#t", interpret('(< 1 2)', env)) assert_equals("#f", interpret('(< 2 2)', env)) assert_equals("#f", interpret('(< 2 1)', env))
def test_not(): """TEST 7.1: Implementing (not ...)""" assert_equals("#t", interpret('(not #f)', env)) assert_equals("#f", interpret('(not #t)', env))
def test_and(): """TEST 7.3: Implementing (and ...)""" assert_equals("#f", interpret('(and #f #f)', env)) assert_equals("#f", interpret('(and #t #f)', env)) assert_equals("#f", interpret('(and #f #t)', env)) assert_equals("#t", interpret('(and #t #t)', env))