def test_error_handling(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("5 + true;", "type mismatch: INTEGER + BOOLEAN"), TestData("5 + true; 5;", "type mismatch: INTEGER + BOOLEAN"), TestData("-true", "unknown operator: -BOOLEAN"), TestData("true + false", "unknown operator: BOOLEAN + BOOLEAN"), TestData("5; true + false; 5;", "unknown operator: BOOLEAN + BOOLEAN"), TestData( "if (10 > 1) { true + false }", "unknown operator: BOOLEAN + BOOLEAN", ), TestData( "if (true) { if (true) { true + false } }", "unknown operator: BOOLEAN + BOOLEAN", ), TestData('"foo" - "bar"', "unknown operator: STRING - STRING"), TestData("foobar", "identifier not found: foobar"), TestData( '{"name": "Monkey"}[fn(x){x}]', "unusable as hash key: FUNCTION", ), ] for test in tests: evaluated = monkey.eval_source(test.source) self.assertIsInstance(evaluated, monkey.ObjectError) self.assertEqual(evaluated.what, test.expected)
def test_eval_hash(self): source = "\n".join([ 'let two = "two";', "{" ' "one": 10 - 9,' " two: 1 + 1," ' "thr" + "ee": 6 / 2,' " 4: 4," " true: 5," " false: 6," "}", ]) evaluated = monkey.eval_source(source) self.assertIsInstance(evaluated, monkey.ObjectHash) self.assertEqual(len(evaluated.pairs), 6) keys = list(evaluated.pairs.keys()) vals = list(evaluated.pairs.values()) self.assertEqual(keys[0], monkey.ObjectString("one")) self.assertEqual(vals[0], monkey.ObjectInteger(1)) self.assertEqual(keys[1], monkey.ObjectString("two")) self.assertEqual(vals[1], monkey.ObjectInteger(2)) self.assertEqual(keys[2], monkey.ObjectString("three")) self.assertEqual(vals[2], monkey.ObjectInteger(3)) self.assertEqual(keys[3], monkey.ObjectInteger(4)) self.assertEqual(vals[3], monkey.ObjectInteger(4)) self.assertEqual(keys[4], monkey.ObjectBoolean(True)) self.assertEqual(vals[4], monkey.ObjectInteger(5)) self.assertEqual(keys[5], monkey.ObjectBoolean(False)) self.assertEqual(vals[5], monkey.ObjectInteger(6))
def test_eval_boolean(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("true", True), TestData("false", False), TestData("1 < 2", True), TestData("1 > 2", False), TestData("1 < 1", False), TestData("1 > 1", False), TestData("1 == 1", True), TestData("1 != 1", False), TestData("1 == 2", False), TestData("1 != 2", True), TestData("true == true", True), TestData("false == false", True), TestData("true == false", False), TestData("false == true", False), TestData("true != true", False), TestData("false != false", False), TestData("true != false", True), TestData("false != true", True), TestData("(1 < 2) == true", True), TestData("(1 < 2) == false", False), TestData("(1 > 2) == true", False), TestData("(1 > 2) == false", True), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_boolean(evaluated, test.expected)
def test_eval_function(self): source = "fn(x, y) { x + y + 2; };" evaluated = monkey.eval_source(source) self.assertIsInstance(evaluated, monkey.ObjectFunction) self.assertEqual(len(evaluated.parameters), 2) self.assertEqual(str(evaluated.parameters[0]), "x") self.assertEqual(str(evaluated.parameters[1]), "y") self.assertEqual(str(evaluated.body), "{ ((x + y) + 2) }")
def test_eval_array(self): source = "[1, 2 * 2, 3 + 3]" evaluated = monkey.eval_source(source) self.assertIsInstance(evaluated, monkey.ObjectArray) self.assertEqual(len(evaluated.elements), 3) self.check_integer(evaluated.elements[0], 1) self.check_integer(evaluated.elements[1], 4) self.check_integer(evaluated.elements[2], 6)
def test_closures(self): source = "\n".join([ "let adder = fn(x) {", " fn(y) { x + y }", "}", "let addtwo = adder(2)", "addtwo(3)", ]) evaluated = monkey.eval_source(source) self.check_integer(evaluated, 5)
def test_let_statement(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("let a = 5; a;", 5), TestData("let a = 5 * 5; a;", 25), TestData("let a = 5; let b = a; b;", 5), TestData("let a = 5; let b = a; let c = a + b + 5; c;", 15), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_integer(evaluated, test.expected)
def test_return_statement(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("return 10;", 10), TestData("return 10; 9", 10), TestData("return 2 * 5; 9", 10), TestData("9; return 2 * 5; 9", 10), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_integer(evaluated, test.expected)
def test_prefix_bang(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("!true", False), TestData("!false", True), TestData("!5", False), TestData("!!true", True), TestData("!!false", False), TestData("!!5", True), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_boolean(evaluated, test.expected)
def test_function_application(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("let identity = fn(x) { x; }; identity(5);", 5), TestData("let identity = fn(x) { return x; }; identity(5);", 5), TestData("let double = fn(x) { x * 2; }; double(5);", 10), TestData("let add = fn(x, y) { x + y; }; add(3, 5);", 8), TestData("let add = fn(x, y) { x + y; }; add(1 + 3, add(5, 7));", 16), TestData("fn(x) { x; }(5);", 5), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_integer(evaluated, test.expected)
def test_hash_index_expressions(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData('{"foo": 5}["foo"]', 5), TestData('{"foo": 5}["bar"]', None), TestData('let key = "foo"; {"foo": 5}[key]', 5), TestData('{}["foo"]', None), TestData("{5: 5}[5]", 5), TestData("{true: 5}[true]", 5), TestData("{false: 5}[false]", 5), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_result(evaluated, test.expected)
def test_if_expression(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("if (true) { 10 }", 10), TestData("if (false) { 10 }", None), TestData("if (1) { 10 }", 10), TestData("if (1 < 2) { 10 }", 10), TestData("if (1 > 2) { 10 }", None), TestData("if (1 < 2) { 10 } else { 20 }", 10), TestData("if (1 > 2) { 10 } else { 20 }", 20), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_result(evaluated, test.expected)
def test_builtin_functions(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData('len("")', 0), TestData('len("four")', 4), TestData('len("hello world")', 11), TestData("len(1)", "argument to `len` not supported, got INTEGER"), TestData('len("one", "two")', "wrong number of arguments. got=2, want=1"), TestData("len([1, 2, 3])", 3), TestData("len([])", 0), TestData("first([1, 2, 3])", 1), TestData("first([])", None), TestData("first(1)", "argument to `first` must be ARRAY, got INTEGER"), TestData("first(1, 2)", "wrong number of arguments. got=2, want=1"), TestData("last([1, 2, 3])", 3), TestData("last([])", None), TestData("last(1)", "argument to `last` must be ARRAY, got INTEGER"), TestData("last(1, 2)", "wrong number of arguments. got=2, want=1"), TestData("rest([1, 2, 3])", [2, 3]), TestData("rest([])", None), TestData("rest(1)", "argument to `rest` must be ARRAY, got INTEGER"), TestData("rest(1, 2)", "wrong number of arguments. got=2, want=1"), TestData("push([], 1)", [1]), TestData("push([1], 2)", [1, 2]), TestData("push(1, 2)", "argument to `push` must be ARRAY, got INTEGER"), TestData("push(1)", "wrong number of arguments. got=1, want=2"), ] for test in tests: # TODO: These branches are a disorganized mess and could use some # cleanup. evaluated = monkey.eval_source(test.source) if isinstance(evaluated, monkey.ObjectInteger): self.check_integer(evaluated, test.expected) elif isinstance(evaluated, monkey.ObjectError): self.assertEqual(str(evaluated), test.expected) elif isinstance(evaluated, monkey.ObjectNull): self.assertEqual(test.expected, None) elif isinstance(evaluated, monkey.ObjectArray): for i in range(len(evaluated.elements)): self.check_integer(evaluated.elements[i], test.expected[i]) else: self.fail(f"Invalid type {type(evaluated)}")
def test_array_index_expressions(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("[1, 2, 3][0]", 1), TestData("[1, 2, 3][1]", 2), TestData("[1, 2, 3][2]", 3), TestData("let i = 0; [1][i];", 1), TestData("[1, 2, 3][1 + 1];", 3), TestData("let myarray = [1, 2, 3]; myarray[2];", 3), TestData( ("let myarray = [1, 2, 3];\n" "myarray[0] + myarray[1] + myarray[2];"), 6, ), TestData( "let myarray = [1, 2, 3]; let i = myarray[0]; myarray[i];", 2), TestData("[1, 2, 3][3]", None), TestData("[1, 2, 3][-1]", None), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_result(evaluated, test.expected)
def test_eval_integer(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [ TestData("5", 5), TestData("10", 10), TestData("-5", -5), TestData("-10", -10), TestData("5 + 5 + 5 + 5 - 10", 10), TestData("2 * 2 * 2 * 2 * 2", 32), TestData("-50 + 100 - 50", 0), TestData("5 * 2 + 10", 20), TestData("5 + 2 * 10", 25), TestData("20 + 2 * -10", 0), TestData("50 / 2 * 2 + 10", 60), TestData("2 * (5 + 10)", 30), TestData("3 * 3 * 3 + 10", 37), TestData("3 * (3 * 3) + 10", 37), TestData("(5 + 10 * 2 + 15 / 3) * 2 + -10", 50), ] for test in tests: evaluated = monkey.eval_source(test.source) self.check_integer(evaluated, test.expected)
def test_eval_string(self): TestData = collections.namedtuple("TestData", ["source", "expected"]) tests = [TestData('"foo"', "foo"), TestData('"foo bar"', "foo bar")] for test in tests: evaluated = monkey.eval_source(test.source) self.check_string(evaluated, test.expected)