def test_function_parameter_parsing(): class Test(NamedTuple): input: str expected_params: List[str] tests = [ Test('fn() {};', []), Test('fn(x) {};', ['x']), Test('fn(x, y, z) {};', ['x', 'y', 'z']), ] for tt in tests: l = lexer.Lexer(tt.input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] function = stmt.expression assert len(function.parameters) == len(tt.expected_params), \ 'length parameters wrong. want {}, got={}'.format(len(tt.expected_params), len(function.parameters)) for i, ident in enumerate(tt.expected_params): _test_literal_expression(function.parameters[i], ident)
def test_function_literal_parsing(): input = 'fn(x, y) { x + y; }' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain {} statements. got={}'.format(1, len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program.statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) function = stmt.expression assert issubclass(function.__class__, ast.FunctionLiteral), \ 'stmt.expression is not ast.FunctionLiteral. got={}'.format(function.__class__.__name__) assert len(function.parameters) == 2, \ 'function literal parameters wrong. want 2, got={}'.format(len(function.parameters)) _test_literal_expression(function.parameters[0], 'x') _test_literal_expression(function.parameters[1], 'y') assert len(function.body.statements) == 1, \ 'function.body.statements has not 1 statements. got={}'.format(len(function.body.statements)) body_stmt = function.body.statements[0] assert issubclass(body_stmt.__class__, ast.ExpressionStatement), \ 'function body stmt is not ast.ExpressionStatement. got={}'.format(body_stmt.__class__.__name__) _test_infix_expression(body_stmt.expression, 'x', '+', 'y')
def test_return_statements(): class Test(NamedTuple): input: str expected_value: Any tests = [ Test('return 5;', 5), Test('return true;', True), Test('return foobar;', 'foobar'), ] for tt in tests: l = lexer.Lexer(tt.input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain 1 statements. got={}'.format(len(program.statements)) return_stmt = program.statements[0] assert issubclass(return_stmt.__class__, ast.ReturnStatement), \ 'stmt not ast.ReturnStatement. got={}'.format(return_stmt.__class__.__name__) assert return_stmt.token_literal() == 'return', \ "return_stmt.token_literal not 'return', got {}".format(return_stmt.token_literal()) if _test_literal_expression(return_stmt.return_value, tt.expected_value): return
def test_if_expression(): input = 'if (x < y) { x }' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain {} statements. got={}'.format(1, len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program.statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) exp = stmt.expression assert issubclass(exp.__class__, ast.IfExpression), \ 'stmt.expression is not ast.IfExpression. got={}'.format(exp.__class__.__name__) if not _test_infix_expression(exp.condition, 'x', '<', 'y'): return assert len(exp.consequence.statements) == 1, \ 'consequence is not 1 statements. got={}'.format(len(exp.consequence.statements)) consequnce = exp.consequence.statements[0] assert issubclass(consequnce.__class__, ast.ExpressionStatement), \ 'statements[0] is not ast.ExpressionStatement. got={}'.format(consequnce.__class__.__name__) if not _test_identifier(consequnce.expression, 'x'): return assert exp.alternative is None, 'exp.alternative was not None. got={}'.format( exp.alternative)
def test_call_expression_parsing(): input = 'add(1, 2 * 3, 4 + 5);' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain {} statements. got={}'.format(1, len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program.statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) exp = stmt.expression assert issubclass(exp.__class__, ast.CallExpression), \ 'stmt.expression is not ast.CallExpression. got={}'.format(exp.__class__.__name__) if not _test_identifier(exp.function, 'add'): return assert len(exp.arguments) == 3, 'wrong length of arguments. got={}'.format( len(exp.arguments)) _test_literal_expression(exp.arguments[0], 1) _test_infix_expression(exp.arguments[1], 2, '*', 3) _test_infix_expression(exp.arguments[2], 4, '+', 5)
def test_let_statements(): class Test(NamedTuple): input: str expected_identifier: str expected_value: Any tests = [ Test('let x = 5;', 'x', 5), Test('let y = true;', 'y', True), Test('let foobar = y;', 'foobar', 'y') ] for tt in tests: l = lexer.Lexer(tt.input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain 1 statements. got={}'.format(len(program.statements)) stmt = program.statements[0] if not _test_let_statement(stmt, tt.expected_identifier): return val = stmt.value if not _test_literal_expression(val, tt.expected_value): return
def _test_eval(input: str) -> object.Object: l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() env = object.Environment() return evaluator.eval(program, env)
def test_parsing_hash_literals_integer_keys(): input = '{1: 1, 2: 2, 3: 3}' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] hash = stmt.expression assert issubclass(hash.__class__, ast.HashLiteral), \ 'exp not ast.HashLiteral. got={}'.format(hash.__class__.__name__) expected = { '1': 1, '2': 2, '3': 3, } assert len(hash.pairs) == len(expected), \ 'hash.Pairs has wrong length. got={}'.format(len(hash.pairs)) for key, value in hash.pairs.items(): assert issubclass(key.__class__, ast.IntegerLiteral), \ 'key is not ast.IntegerLiteral. got={}'.format(key.__class__.__name__) assert key.string() in expected expected_value = expected[key.string()] _test_integer_literal(value, expected_value)
def test_boolean_expression(): class Test(NamedTuple): input: str expected_boolean: bool tests = [ Test('true;', True), Test('false;', False), ] for tt in tests: l = lexer.Lexer(tt.input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program has not enough statements. got={}'.format(len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program.statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) boolean = stmt.expression assert issubclass(boolean.__class__, ast.Boolean), \ 'exp not ast.Boolean. got={}'.format(boolean.__class__.__name__) assert boolean.value == tt.expected_boolean, \ 'boolean.value not {}. got={}'.format(tt.expected_boolean, boolean.value)
def test_operator_precedence_parsing(self): tests = [ ("-a * b", "((-a) * b)"), ("!-a", "(!(-a))"), ("a + b + c", "((a + b) + c)"), ("a + b - c", "((a + b) - c)"), ("a * b * c", "((a * b) * c)"), ("a * b / c", "((a * b) / c)"), ("a + b / c", "(a + (b / c))"), ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), ("3 + 4; -5 * 5", "(3 + 4)((-5) * 5)"), ("5 > 4 == 3 < 4", "((5 > 4) == (3 < 4))"), ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), ("3 + 4 * 5 == 3 * 1 + 4 * 5", "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))"), ("true", "true"), ("false", "false"), ("3 > 5 == false", "((3 > 5) == false)"), ("3 < 5 == true", "((3 < 5) == true)"), ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), ("(5 + 5) * 2", "((5 + 5) * 2)"), ("2 / (5 + 5)", "(2 / (5 + 5))"), ("-(5 + 5)", "(-(5 + 5))"), ("!(true == true)", "(!(true == true))"), ("a + add(b * c) + d", "((a + add((b * c))) + d)"), ("add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))"), ("add(a + b + c * d / f + g)", "add((((a + b) + ((c * d) / f)) + g))") ] for input, expected in tests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(str(program), expected)
def test_if_expression(self): tests = [("if (x < y) { x }", ("x", "<", "y"), "x", None), ("if (x < y) { x } else { y }", ("x", "<", "y"), "x", "y")] for input, cond, consequence, alternative in tests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.IfExpression) ifexp = cast(ast.IfExpression, stmt.expression) self.assert_infix_expression(ifexp.condition, *cond) self.assertEqual(len(ifexp.consequence.statements), 1) self.assertIsInstance(ifexp.consequence.statements[0], ast.ExpressionStatement) cnsq = cast(ast.ExpressionStatement, ifexp.consequence.statements[0]) self.assert_identifier(cnsq.expression, consequence) if alternative: self.assertEqual(len(ifexp.alternative.statements), 1) self.assertIsInstance(ifexp.alternative.statements[0], ast.ExpressionStatement) alt = cast(ast.ExpressionStatement, ifexp.alternative.statements[0]) self.assert_identifier(alt.expression, alternative)
def test_parsing_hash_literals_integer_keys(): input = '{"one": 0 + 1, "two": 10 - 8, "three": 15 / 5}' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] hash = stmt.expression assert issubclass(hash.__class__, ast.HashLiteral), \ 'exp not ast.HashLiteral. got={}'.format(hash.__class__.__name__) assert len(hash.pairs) == 3, \ 'hash.Pairs has wrong length. got={}'.format(len(hash.pairs)) tests = { 'one': lambda e: _test_infix_expression(e, 0, '+', 1), 'two': lambda e: _test_infix_expression(e, 10, '-', 8), 'three': lambda e: _test_infix_expression(e, 15, '/', 5), } for key, value in hash.pairs.items(): assert issubclass(key.__class__, ast.StringLiteral), \ 'key is not ast.StringLiteral. got={}'.format(key.__class__.__name__) assert key.string() in tests, \ "No test function for key '{}' found".format(key.string()) test_func = tests[key.string()] test_func(value)
def test_parsing_prefix_expression(): class PrefixTest(NamedTuple): input: str operator: str value: Any prefix_tests = [ PrefixTest('!5;', '!', 5), PrefixTest('-15;', '-', 15), PrefixTest('!true', '!', True), PrefixTest('!false', '!', False), ] for tt in prefix_tests: l = lexer.Lexer(tt.input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program.statements does not contain {} statements. got={}'.format(1, len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) exp = stmt.expression assert issubclass(exp.__class__, ast.PrefixExpression), \ 'stmt is not ast.PrefixExpression. got={}'.format(stmt.expression.__class__.__name__) assert exp.operator == tt.operator, "exp.operator is not '{}'. got={}".format( tt.operator, exp.operator) assert _test_literal_expression(exp.right, tt.value)
def test_identifier_expression(self): input = "foobar;" lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assert_identifier(stmt.expression, "foobar")
def test_string_literal_expression(): input = '"hello world";' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] literal = stmt.expression assert issubclass(literal.__class__, ast.StringLiteral), \ 'exp not ast.StringLiteral. got={}'.format(stmt.expression.__class__.__name__) assert literal.value == 'hello world', \ "literal.value not '{}'. got='{}'".format('hello world', literal.value)
def test_parsing_empty_hash_literal(): input = '{}' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] hash = stmt.expression assert issubclass(hash.__class__, ast.HashLiteral), \ 'exp not ast.HashLiteral. got={}'.format(hash.__class__.__name__) assert len(hash.pairs) == 0, \ 'hash.Pairs has wrong length. got={}'.format(len(hash.pairs))
def test_integer_literal(self): input = "5;" lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.IntegerLiteral) ident = cast(ast.IntegerLiteral, stmt.expression) self.assertEqual(ident.value, 5) self.assertEqual(ident.token_literal(), "5")
def test_parsing_empty_array_literals(): input = '[]' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] array = stmt.expression assert issubclass(array.__class__, ast.ArrayLiteral), \ 'exp not ast.ArrayLiteral. got={}'.format(array.__class__.__name__) assert len(array.elements) == 0, \ 'len(array.Elements) not 0. got={}'.format(len(array.elements))
def test_parsing_index_expressions(): input = 'myArray[1 + 1]' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] index_exp = stmt.expression assert issubclass(index_exp.__class__, ast.IndexExpression), \ 'exp not ast.IndexExpression. got={}'.format(index_exp.__class__.__name__) assert _test_identifier(index_exp.left, 'myArray') assert _test_infix_expression(index_exp.index, 1, '+', 1)
def test_return_statements(self): tests = [("return 5;", 5), ("return true;", True), ("return foobar;", "foobar")] for input, expected_value in tests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) stmt = program.statements[0] self.assertIsInstance(stmt, ast.ReturnStatement) self.assertEqual(stmt.token_literal(), "return") ret_stmt = cast(ast.ReturnStatement, stmt) self.assert_literal_expression(ret_stmt.return_value, expected_value)
def test_boolean_expression(self): tests = [("true;", "true", True), ("false;", "false", False)] for input, literal, value in tests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.Boolean) boolean = cast(ast.Boolean, stmt.expression) self.assertEqual(boolean.value, value) self.assertEqual(boolean.token_literal(), literal)
def test_parsing_array_literals(): input = '[1, 2 * 2, 3 + 3]' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) stmt = program.statements[0] array = stmt.expression assert issubclass(array.__class__, ast.ArrayLiteral), \ 'exp not ast.ArrayLiteral. got={}'.format(array.__class__.__name__) assert len(array.elements) == 3, \ 'len(array.Elements) not 3. got={}'.format(len(array.elements)) _test_integer_literal(array.elements[0], 1) _test_infix_expression(array.elements[1], 2, '*', 2) _test_infix_expression(array.elements[2], 3, '+', 3)
def start(): env = object.Environment() while True: line = input(PROMPT) if not line: return l = lexer.Lexer(line) p = parser.Parser(l) program = p.parse_program() if len(p.errors) != 0: print_parse_errors(p.errors) continue evaluated = evaluator.eval(program, env) if evaluated is not None: print(evaluated.inspect())
def test_let_statements(self): tests = [ ("let x = 5;", "x", 5), ("let y = true;", "y", True), ("let foobar = y;", "foobar", "y"), ] for input, expected_ident, expected_value in tests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) stmt = program.statements[0] self.assertEqual(stmt.token_literal(), "let") self.assertIsInstance(stmt, ast.LetStatement) let_stmt = cast(ast.LetStatement, stmt) self.assert_identifier(let_stmt.name, expected_ident) self.assert_literal_expression(let_stmt.value, expected_value)
def test_identifier_expression(): input = 'foobar;' l = lexer.Lexer(input) p = parser.Parser(l) program = p.parse_program() check_parser_errors(p) assert len(program.statements) == 1, \ 'program has not enough statements. got={}'.format(len(program.statements)) stmt = program.statements[0] assert issubclass(stmt.__class__, ast.ExpressionStatement), \ 'program statements[0] is not ast.ExpressionStatement. got={}'.format(stmt.__class__.__name__) ident = stmt.expression assert ident.value == 'foobar', 'ident.value not {}. got={}'.format( 'foobar', ident.value) assert ident.token_literal() == 'foobar', \ 'ident.token_literal not {}. got={}'.format('foobar', ident.token_literal())
def test_call_expression_parsing(self): input = "add(1, 2 * 3, 4 + 5)" lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.CallExpression) call = cast(ast.CallExpression, stmt.expression) self.assert_identifier(call.function, "add") self.assertEqual(len(call.arguments), 3) self.assert_literal_expression(call.arguments[0], 1) self.assert_infix_expression(call.arguments[1], 2, "*", 3) self.assert_infix_expression(call.arguments[2], 4, "+", 5)
def test_function_parameter_parsing(self): tests = [("fn() {}", []), ("fn(x) {}", ["x"]), ("fn(x, y, z) {}", ["x", "y", "z"])] for input, expectedparmas in tests: self.subTest(input) lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.FunctionLiteral) func = cast(ast.FunctionLiteral, stmt.expression) self.assertEqual(len(func.parameters), len(expectedparmas)) for param, expectedparam in zip(func.parameters, expectedparmas): self.assert_literal_expression(param, expectedparam)
def test_parse_infix_expression(self): infixTests = [("5 + 5;", 5, "+", 5), ("5 - 5;", 5, "-", 5), ("5 * 5;", 5, "*", 5), ("5 / 5;", 5, "/", 5), ("5 > 5;", 5, ">", 5), ("5 < 5;", 5, "<", 5), ("5 == 5;", 5, "==", 5), ("5 != 5;", 5, "!=", 5), ("true == true;", True, "==", True), ("true != false;", True, "!=", False), ("false == false;", False, "==", False)] for input, left, operator, right in infixTests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assert_infix_expression(stmt.expression, left, operator, right)
def test_parse_prefix_expression(self): prefixTests = [ ("!5;", "!", 5), ("-15;", "-", 15), ("!true;", "!", True), ("!false;", "!", False), ] for input, operator, value in prefixTests: with self.subTest(input): lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.PrefixExpression) exp = cast(ast.PrefixExpression, stmt.expression) self.assertEqual(exp.operator, operator) self.assert_literal_expression(exp.right, value)
def test_function_literal_parsing(self): input = "fn(x, y) { x + y; }" lex = lexer.Lexer(input) psr = parser.Parser(lex) program = psr.parse() self.check_parser_errors(psr) self.assertEqual(len(program.statements), 1) self.assertIsInstance(program.statements[0], ast.ExpressionStatement) stmt = cast(ast.ExpressionStatement, program.statements[0]) self.assertIsInstance(stmt.expression, ast.FunctionLiteral) func = cast(ast.FunctionLiteral, stmt.expression) self.assertEqual(len(func.parameters), 2) self.assert_literal_expression(func.parameters[0], "x") self.assert_literal_expression(func.parameters[1], "y") self.assertEqual(len(func.body.statements), 1) self.assertIsInstance(func.body.statements[0], ast.ExpressionStatement) body = cast(ast.ExpressionStatement, func.body.statements[0]) self.assert_infix_expression(body.expression, "x", "+", "y")