From 5e51c90aca817cc76fa2378ecbe34d3fc0d5bf12 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Fri, 2 Oct 2026 00:09:26 +0200 Subject: [PATCH] feat: parse grouped expressions --- README.md | 2 + source/parser/{parser.hon => parse.hon} | 17 ++++- .../{parser.test.hon => parse.test.hon} | 70 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) rename source/parser/{parser.hon => parse.hon} (94%) rename source/parser/{parser.test.hon => parse.test.hon} (53%) diff --git a/README.md b/README.md index 639c244..16d02da 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,5 @@ Build Honey from this directory: ```sh bro build ``` + +The parser supports arithmetic expressions with nested parenthesized groups, such as `a + (b - c) * 2 / 3`. Groups control precedence without adding AST nodes; inner expression spans exclude the grouping parentheses. diff --git a/source/parser/parser.hon b/source/parser/parse.hon similarity index 94% rename from source/parser/parser.hon rename to source/parser/parse.hon index ede8126..c8f8fe8 100644 --- a/source/parser/parser.hon +++ b/source/parser/parse.hon @@ -249,6 +249,19 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! (me parse_primary proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) { token :: current(state) match token.kind { + .open_paren: { + state.next_token += 1 + expression :: try parse_expr(state) + if next_is(state, .close_paren) { + state.next_token += 1 + return expression + } + diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span) + return try ast.add_invalid_expr(&state.builder, ast.Span{ + start = token.span.start, + end = ast.builder_span(&state.builder, expression).end, + }, diagnostic) + } .int: { state.next_token += 1 return try ast.add_literal(&state.builder, .int, token.span) @@ -270,9 +283,11 @@ parse_primary proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics. token.data.diagnostic else try diagnostics.add(state.diagnostics, .unexpected_token, token.span) - if (token.kind != .newline and token.kind != .close_curly and token.kind != .eof) { + + if (token.kind != .newline and token.kind != .close_paren and token.kind != .close_curly and token.kind != .eof) { state.next_token += 1 } + return try ast.add_invalid_expr(&state.builder, token.span, diagnostic) } } diff --git a/source/parser/parser.test.hon b/source/parser/parse.test.hon similarity index 53% rename from source/parser/parser.test.hon rename to source/parser/parse.test.hon index ab18291..79c644d 100644 --- a/source/parser/parser.test.hon +++ b/source/parser/parse.test.hon @@ -33,6 +33,37 @@ parses_expression_precedence_through_tree_views test { try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } +parses_grouped_expression_precedence test { + strpool.STRINGS = strpool.init(mem.c_allocator) + defer strpool.deinit(&strpool.STRINGS) + diagnostic_store := diagnostics.init(mem.c_allocator) + defer diagnostics.deinit(&diagnostic_store) + source :: "value :: a + ((b - c)) * 2 / 3\n" + scan_state := lexer.init(mem.c_allocator) + defer lexer.deinit(&scan_state) + try lexer.scan(&scan_state, source, &diagnostic_store) + + tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator) + defer ast.deinit(&tree) + + try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) + try testing.expect_equal(1, ast.package_decl_count(&tree)) + statement :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) + add :: ast.binary_expr(&tree, statement.initializer) + try testing.expect_equal(ast.BinaryOperator.add, add.operator) + try testing.expect_equal("a", ast.source_text(&tree, ast.node_span(&tree, add.left))) + divide :: ast.binary_expr(&tree, add.right) + try testing.expect_equal(ast.BinaryOperator.divide, divide.operator) + try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, divide.right))) + multiply :: ast.binary_expr(&tree, divide.left) + try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator) + try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.right))) + subtract :: ast.binary_expr(&tree, multiply.left) + try testing.expect_equal(ast.BinaryOperator.subtract, subtract.operator) + try testing.expect_equal("b", ast.source_text(&tree, ast.node_span(&tree, subtract.left))) + try testing.expect_equal("c", ast.source_text(&tree, ast.node_span(&tree, subtract.right))) +} + parses_function_structure_through_tree_views test { strpool.STRINGS = strpool.init(mem.c_allocator) defer strpool.deinit(&strpool.STRINGS) @@ -89,3 +120,42 @@ recovers_invalid_declaration_and_preserves_sibling test { valid_statement :: ast.statement_decl(&tree, valid_decl) try testing.expect_equal("valid", ast.source_text(&tree, valid_statement.name)) } + +recovers_malformed_groups_and_preserves_following_statements test { + strpool.STRINGS = strpool.init(mem.c_allocator) + defer strpool.deinit(&strpool.STRINGS) + diagnostic_store := diagnostics.init(mem.c_allocator) + defer diagnostics.deinit(&diagnostic_store) + source :: + `main proc() void { + ` empty :: () + ` operand :: (1 + ) + ` missing :: (2 + ` valid :: (3) + `} + `after :: 4 + scan_state := lexer.init(mem.c_allocator) + defer lexer.deinit(&scan_state) + try lexer.scan(&scan_state, source, &diagnostic_store) + + tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator) + defer ast.deinit(&tree) + + try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) + try testing.expect_equal(2, ast.package_decl_count(&tree)) + function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) + try testing.expect_equal(4, ast.block_statement_count(&tree, function.body)) + empty :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)) + try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, empty.initializer)) + operand :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)) + add :: ast.binary_expr(&tree, operand.initializer) + try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, add.right)) + missing :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2)) + try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.initializer)) + valid :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3)) + try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, valid.initializer)) + try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, valid.initializer))) + after :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) + try testing.expect_equal("after", ast.source_text(&tree, after.name)) + try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, after.initializer))) +}