diff --git a/include/tree_sitter/parser.h b/include/tree_sitter/parser.h index 6e68a9f4..295b3c96 100644 --- a/include/tree_sitter/parser.h +++ b/include/tree_sitter/parser.h @@ -42,6 +42,7 @@ typedef struct { union { TSStateId to_state; struct { + short dynamic_precedence; TSSymbol symbol; unsigned short child_count; }; @@ -145,21 +146,30 @@ typedef struct TSLanguage { { .type = TSParseActionTypeShift, .extra = true } \ } -#define REDUCE(symbol_val, child_count_val) \ +#define REDUCE(symbol_val, child_count_val, dynamic_precedence_val) \ { \ { \ .type = TSParseActionTypeReduce, \ - .params = {.symbol = symbol_val, .child_count = child_count_val } \ + .params = { \ + .symbol = symbol_val, \ + .child_count = child_count_val, \ + .dynamic_precedence = dynamic_precedence_val, \ + } \ } \ } -#define REDUCE_FRAGILE(symbol_val, child_count_val) \ - { \ - { \ - .type = TSParseActionTypeReduce, .fragile = true, \ - .params = {.symbol = symbol_val, .child_count = child_count_val } \ - } \ - } +#define REDUCE_FRAGILE(symbol_val, child_count_val, dynamic_precedence_val) \ +{ \ + { \ + .type = TSParseActionTypeReduce, \ + .fragile = true, \ + .params = { \ + .symbol = symbol_val, \ + .child_count = child_count_val, \ + .dynamic_precedence = dynamic_precedence_val, \ + } \ + } \ +} #define ACCEPT_INPUT() \ { \ diff --git a/src/compiler/build_tables/build_parse_table.cc b/src/compiler/build_tables/build_parse_table.cc index c5519555..db540247 100644 --- a/src/compiler/build_tables/build_parse_table.cc +++ b/src/compiler/build_tables/build_parse_table.cc @@ -55,7 +55,8 @@ class ParseTableBuilder { Symbol::non_terminal(0); Production start_production{ - ProductionStep{start_symbol, 0, rules::AssociativityNone}, + {ProductionStep{start_symbol, 0, rules::AssociativityNone}}, + 0 }; // Placeholder for error state @@ -281,9 +282,10 @@ class ParseTableBuilder { for (ParseAction &action : actions) { if (action.type == ParseActionTypeReduce) { - if (has_fragile_production(action.production)) + if (has_fragile_production(action.production)) { action.fragile = true; - action.production = NULL; + } + action.production = nullptr; } } @@ -586,7 +588,7 @@ class ParseTableBuilder { } description += " (" + symbol_name(action.symbol); - for (const ProductionStep &step : *action.production) { + for (const ProductionStep &step : action.production->steps) { description += " " + symbol_name(step.symbol); } description += ")"; diff --git a/src/compiler/build_tables/parse_item.cc b/src/compiler/build_tables/parse_item.cc index baf10a00..b672bb1b 100644 --- a/src/compiler/build_tables/parse_item.cc +++ b/src/compiler/build_tables/parse_item.cc @@ -60,6 +60,10 @@ int ParseItem::precedence() const { } } +int ParseItem::dynamic_precedence() const { + return production->dynamic_precedence; +} + rules::Associativity ParseItem::associativity() const { if (is_done()) { if (production->empty()) { diff --git a/src/compiler/build_tables/parse_item.h b/src/compiler/build_tables/parse_item.h index 020afc07..5133970d 100644 --- a/src/compiler/build_tables/parse_item.h +++ b/src/compiler/build_tables/parse_item.h @@ -26,6 +26,7 @@ struct ParseItem { rules::Symbol lhs() const; rules::Symbol next_symbol() const; int precedence() const; + int dynamic_precedence() const; rules::Associativity associativity() const; bool is_done() const; diff --git a/src/compiler/generate_code/c_code.cc b/src/compiler/generate_code/c_code.cc index 2d6c22c9..b2359233 100644 --- a/src/compiler/generate_code/c_code.cc +++ b/src/compiler/generate_code/c_code.cc @@ -490,12 +490,17 @@ class CCodeGenerator { break; case ParseActionTypeReduce: if (action.fragile) { - add("REDUCE_FRAGILE(" + symbol_id(action.symbol) + ", " + - to_string(action.consumed_symbol_count) + ")"); + add("REDUCE_FRAGILE"); } else { - add("REDUCE(" + symbol_id(action.symbol) + ", " + - to_string(action.consumed_symbol_count) + ")"); + add("REDUCE"); } + + add("("); + add(symbol_id(action.symbol)); + add(", "); + add(to_string(action.consumed_symbol_count)); + add(", " + to_string(action.dynamic_precedence)); + add(")"); break; case ParseActionTypeRecover: add("RECOVER(" + to_string(action.state_index) + ")"); diff --git a/src/compiler/parse_grammar.cc b/src/compiler/parse_grammar.cc index aa1e2fb8..7589904c 100644 --- a/src/compiler/parse_grammar.cc +++ b/src/compiler/parse_grammar.cc @@ -184,6 +184,20 @@ ParseRuleResult parse_rule(json_value *rule_json) { return Rule(Metadata::prec_right(precedence_json.u.integer, result.rule)); } + if (type == "PREC_DYNAMIC") { + json_value precedence_json = rule_json->operator[]("value"); + if (precedence_json.type != json_integer) { + return "Precedence value must be an integer"; + } + + json_value content_json = rule_json->operator[]("content"); + auto result = parse_rule(&content_json); + if (!result.error_message.empty()) { + return "Invalid precedence content: " + result.error_message; + } + return Rule(Metadata::prec_dynamic(precedence_json.u.integer, result.rule)); + } + return "Unknown rule type: " + type; } diff --git a/src/compiler/parse_table.cc b/src/compiler/parse_table.cc index ffa57760..82c4429c 100644 --- a/src/compiler/parse_table.cc +++ b/src/compiler/parse_table.cc @@ -13,25 +13,14 @@ using std::vector; using std::function; using rules::Symbol; -ParseAction::ParseAction(ParseActionType type, ParseStateId state_index, - Symbol symbol, size_t consumed_symbol_count, - const Production *production) - : type(type), - extra(false), - fragile(false), - state_index(state_index), - symbol(symbol), - consumed_symbol_count(consumed_symbol_count), - production(production) {} - ParseAction::ParseAction() - : type(ParseActionTypeError), + : production(nullptr), + consumed_symbol_count(0), + symbol(rules::NONE()), + type(ParseActionTypeError), extra(false), fragile(false), - state_index(-1), - symbol(rules::NONE()), - consumed_symbol_count(0), - production(nullptr) {} + state_index(-1) {} ParseAction ParseAction::Error() { return ParseAction(); @@ -44,12 +33,17 @@ ParseAction ParseAction::Accept() { } ParseAction ParseAction::Shift(ParseStateId state_index) { - return ParseAction(ParseActionTypeShift, state_index, rules::NONE(), 0, nullptr); + ParseAction result; + result.type = ParseActionTypeShift; + result.state_index = state_index; + return result; } ParseAction ParseAction::Recover(ParseStateId state_index) { - return ParseAction(ParseActionTypeRecover, state_index, rules::NONE(), 0, - nullptr); + ParseAction result; + result.type = ParseActionTypeRecover; + result.state_index = state_index; + return result; } ParseAction ParseAction::ShiftExtra() { @@ -61,8 +55,13 @@ ParseAction ParseAction::ShiftExtra() { ParseAction ParseAction::Reduce(Symbol symbol, size_t consumed_symbol_count, const Production &production) { - return ParseAction(ParseActionTypeReduce, 0, symbol, consumed_symbol_count, - &production); + ParseAction result; + result.type = ParseActionTypeReduce; + result.symbol = symbol; + result.consumed_symbol_count = consumed_symbol_count; + result.production = &production; + result.dynamic_precedence = production.dynamic_precedence; + return result; } int ParseAction::precedence() const { diff --git a/src/compiler/parse_table.h b/src/compiler/parse_table.h index c00969d2..555dad31 100644 --- a/src/compiler/parse_table.h +++ b/src/compiler/parse_table.h @@ -24,9 +24,6 @@ enum ParseActionType { struct ParseAction { ParseAction(); - ParseAction(ParseActionType type, ParseStateId state_index, - rules::Symbol symbol, size_t consumed_symbol_count, - const Production *); static ParseAction Accept(); static ParseAction Error(); static ParseAction Shift(ParseStateId state_index); @@ -39,13 +36,14 @@ struct ParseAction { rules::Associativity associativity() const; int precedence() const; + const Production *production; + size_t consumed_symbol_count; + rules::Symbol symbol; + int dynamic_precedence; ParseActionType type; bool extra; bool fragile; ParseStateId state_index; - rules::Symbol symbol; - size_t consumed_symbol_count; - const Production *production; }; struct ParseTableEntry { diff --git a/src/compiler/prepare_grammar/flatten_grammar.cc b/src/compiler/prepare_grammar/flatten_grammar.cc index 71b19f21..ae802287 100644 --- a/src/compiler/prepare_grammar/flatten_grammar.cc +++ b/src/compiler/prepare_grammar/flatten_grammar.cc @@ -26,7 +26,7 @@ class FlattenRule { void apply(const Rule &rule) { rule.match( [&](const rules::Symbol &symbol) { - production.push_back(ProductionStep{ + production.steps.push_back(ProductionStep{ symbol, precedence_stack.back(), associativity_stack.back() @@ -42,6 +42,10 @@ class FlattenRule { associativity_stack.push_back(metadata.params.associativity); } + if (metadata.params.dynamic_precedence > production.dynamic_precedence) { + production.dynamic_precedence = metadata.params.dynamic_precedence; + } + apply(*metadata.rule); if (metadata.params.has_precedence) { diff --git a/src/compiler/rules/metadata.cc b/src/compiler/rules/metadata.cc index ff98a54b..08baa8da 100644 --- a/src/compiler/rules/metadata.cc +++ b/src/compiler/rules/metadata.cc @@ -51,6 +51,12 @@ Metadata Metadata::prec_right(int precedence, const Rule &rule) { return Metadata{rule, params}; } +Metadata Metadata::prec_dynamic(int dynamic_precedence, const Rule &rule) { + MetadataParams params; + params.dynamic_precedence = dynamic_precedence; + return Metadata{rule, params}; +} + Metadata Metadata::separator(const Rule &rule) { MetadataParams params; params.has_precedence = true; diff --git a/src/compiler/rules/metadata.h b/src/compiler/rules/metadata.h index 0d55dfd2..af0b3e4e 100644 --- a/src/compiler/rules/metadata.h +++ b/src/compiler/rules/metadata.h @@ -14,6 +14,7 @@ enum Associativity { struct MetadataParams { int precedence; + int dynamic_precedence; Associativity associativity; bool has_precedence; bool has_associativity; @@ -23,8 +24,8 @@ struct MetadataParams { bool is_main_token; inline MetadataParams() : - precedence{0}, associativity{AssociativityNone}, has_precedence{false}, - has_associativity{false}, is_token{false}, is_string{false}, + precedence{0}, dynamic_precedence{0}, associativity{AssociativityNone}, + has_precedence{false}, has_associativity{false}, is_token{false}, is_string{false}, is_active{false}, is_main_token{false} {} inline bool operator==(const MetadataParams &other) const { @@ -33,6 +34,7 @@ struct MetadataParams { associativity == other.associativity && has_precedence == other.has_precedence && has_associativity == other.has_associativity && + dynamic_precedence == other.dynamic_precedence && is_token == other.is_token && is_string == other.is_string && is_active == other.is_active && @@ -54,6 +56,7 @@ struct Metadata { static Metadata prec(int precedence, const Rule &rule); static Metadata prec_left(int precedence, const Rule &rule); static Metadata prec_right(int precedence, const Rule &rule); + static Metadata prec_dynamic(int precedence, const Rule &rule); static Metadata separator(const Rule &rule); static Metadata main_token(const Rule &rule); @@ -63,4 +66,4 @@ struct Metadata { } // namespace rules } // namespace tree_sitter -#endif // COMPILER_RULES_METADATA_H_ \ No newline at end of file +#endif // COMPILER_RULES_METADATA_H_ diff --git a/src/compiler/syntax_grammar.h b/src/compiler/syntax_grammar.h index 4c177240..4eeba90c 100644 --- a/src/compiler/syntax_grammar.h +++ b/src/compiler/syntax_grammar.h @@ -11,8 +11,9 @@ namespace tree_sitter { struct ProductionStep { inline bool operator==(const ProductionStep &other) const { - return symbol == other.symbol && precedence == other.precedence && - associativity == other.associativity; + return symbol == other.symbol && + precedence == other.precedence && + associativity == other.associativity; } rules::Symbol symbol; @@ -20,7 +21,21 @@ struct ProductionStep { rules::Associativity associativity; }; -typedef std::vector Production; +struct Production { + std::vector steps; + int dynamic_precedence = 0; + + inline bool operator==(const Production &other) const { + return steps == other.steps && dynamic_precedence == other.dynamic_precedence; + } + + inline ProductionStep &back() { return steps.back(); } + inline const ProductionStep &back() const { return steps.back(); } + inline bool empty() const { return steps.empty(); } + inline size_t size() const { return steps.size(); } + inline const ProductionStep &operator[](int i) const { return steps[i]; } + inline const ProductionStep &at(int i) const { return steps[i]; } +}; struct SyntaxVariable { std::string name; diff --git a/src/runtime/parser.c b/src/runtime/parser.c index 13ef9a76..2b01dddb 100644 --- a/src/runtime/parser.c +++ b/src/runtime/parser.c @@ -437,22 +437,36 @@ static Tree *parser__get_lookahead(Parser *self, StackVersion version, } static bool parser__select_tree(Parser *self, Tree *left, Tree *right) { - if (!left) - return true; - if (!right) - return false; + if (!left) return true; + if (!right) return false; + if (right->error_cost < left->error_cost) { LOG("select_smaller_error symbol:%s, over_symbol:%s", SYM_NAME(right->symbol), SYM_NAME(left->symbol)); return true; } + if (left->error_cost < right->error_cost) { LOG("select_smaller_error symbol:%s, over_symbol:%s", SYM_NAME(left->symbol), SYM_NAME(right->symbol)); return false; } - if (left->error_cost > 0) return -1; + if (right->dynamic_precedence > left->dynamic_precedence) { + LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", + SYM_NAME(right->symbol), right->dynamic_precedence, SYM_NAME(left->symbol), + left->dynamic_precedence); + return true; + } + + if (left->dynamic_precedence > right->dynamic_precedence) { + LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", + SYM_NAME(left->symbol), left->dynamic_precedence, SYM_NAME(right->symbol), + right->dynamic_precedence); + return false; + } + + if (left->error_cost > 0) return true; int comparison = ts_tree_compare(left, right); switch (comparison) { @@ -544,7 +558,8 @@ static bool parser__switch_children(Parser *self, Tree *tree, static StackPopResult parser__reduce(Parser *self, StackVersion version, TSSymbol symbol, unsigned count, - bool fragile, bool allow_skipping) { + bool fragile, int dynamic_precedence, + bool allow_skipping) { uint32_t initial_version_count = ts_stack_version_count(self->stack); StackPopResult pop = ts_stack_pop_count(self->stack, version, count); @@ -587,6 +602,8 @@ static StackPopResult parser__reduce(Parser *self, StackVersion version, } } + parent->dynamic_precedence += dynamic_precedence; + TSStateId state = ts_stack_top_state(self->stack, slice.version); TSStateId next_state = ts_language_next_state(language, state, symbol); if (fragile || self->is_split || pop.slices.size > 1 || initial_version_count > 1) { @@ -929,6 +946,7 @@ static bool parser__do_potential_reductions(Parser *self, StackVersion version) ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){ .symbol = action.params.symbol, .count = action.params.child_count, + .dynamic_precedence = action.params.dynamic_precedence }); default: break; @@ -939,8 +957,10 @@ static bool parser__do_potential_reductions(Parser *self, StackVersion version) bool did_reduce = false; for (uint32_t i = 0; i < self->reduce_actions.size; i++) { ReduceAction action = self->reduce_actions.contents[i]; - StackPopResult reduction = - parser__reduce(self, version, action.symbol, action.count, true, false); + StackPopResult reduction = parser__reduce( + self, version, action.symbol, action.count, true, + action.dynamic_precedence, false + ); if (reduction.stopped_at_error) { ts_tree_array_delete(&reduction.slices.contents[0].trees); ts_stack_remove_version(self->stack, reduction.slices.contents[0].version); @@ -1180,12 +1200,13 @@ static void parser__advance(Parser *self, StackVersion version, unsigned child_count = action.params.child_count; TSSymbol symbol = action.params.symbol; + unsigned dynamic_precedence = action.params.dynamic_precedence; bool fragile = action.fragile; LOG("reduce sym:%s, child_count:%u", SYM_NAME(symbol), child_count); StackPopResult reduction = - parser__reduce(self, version, symbol, child_count, fragile, true); + parser__reduce(self, version, symbol, child_count, fragile, dynamic_precedence, true); StackSlice slice = *array_front(&reduction.slices); if (reduction.stopped_at_error) { reduction_stopped_at_error = true; diff --git a/src/runtime/reduce_action.h b/src/runtime/reduce_action.h index 8fc1f533..aad1f619 100644 --- a/src/runtime/reduce_action.h +++ b/src/runtime/reduce_action.h @@ -11,6 +11,7 @@ extern "C" { typedef struct { uint32_t count; TSSymbol symbol; + int dynamic_precedence; } ReduceAction; typedef Array(ReduceAction) ReduceActionSet; diff --git a/src/runtime/tree.c b/src/runtime/tree.c index b76680e6..af9a5477 100644 --- a/src/runtime/tree.c +++ b/src/runtime/tree.c @@ -150,6 +150,7 @@ void ts_tree_set_children(Tree *self, uint32_t child_count, Tree **children) { self->visible_child_count = 0; self->error_cost = 0; self->has_external_tokens = false; + self->dynamic_precedence = 0; for (uint32_t i = 0; i < child_count; i++) { Tree *child = children[i]; @@ -165,6 +166,7 @@ void ts_tree_set_children(Tree *self, uint32_t child_count, Tree **children) { } self->error_cost += child->error_cost; + self->dynamic_precedence += child->dynamic_precedence; if (child->visible) { self->visible_child_count++; diff --git a/src/runtime/tree.h b/src/runtime/tree.h index e69fcba7..3b217722 100644 --- a/src/runtime/tree.h +++ b/src/runtime/tree.h @@ -46,6 +46,7 @@ typedef struct Tree { } first_leaf; uint32_t ref_count; + int dynamic_precedence; bool visible : 1; bool named : 1; bool extra : 1; diff --git a/test/compiler/build_tables/parse_item_set_builder_test.cc b/test/compiler/build_tables/parse_item_set_builder_test.cc index 8583c7b1..ab1efed2 100644 --- a/test/compiler/build_tables/parse_item_set_builder_test.cc +++ b/test/compiler/build_tables/parse_item_set_builder_test.cc @@ -25,25 +25,25 @@ describe("ParseItemSetBuilder", []() { it("adds items at the beginnings of referenced rules", [&]() { SyntaxGrammar grammar{{ SyntaxVariable{"rule0", VariableTypeNamed, { - Production({ + Production{{ {Symbol::non_terminal(1), 0, AssociativityNone}, {Symbol::terminal(11), 0, AssociativityNone}, - }), + }, 0}, }}, SyntaxVariable{"rule1", VariableTypeNamed, { - Production({ + Production{{ {Symbol::terminal(12), 0, AssociativityNone}, {Symbol::terminal(13), 0, AssociativityNone}, - }), - Production({ + }, 0}, + Production{{ {Symbol::non_terminal(2), 0, AssociativityNone}, - }) + }, 0} }}, SyntaxVariable{"rule2", VariableTypeNamed, { - Production({ + Production{{ {Symbol::terminal(14), 0, AssociativityNone}, {Symbol::terminal(15), 0, AssociativityNone}, - }) + }, 0} }}, }, {}, {}, {}}; @@ -84,17 +84,17 @@ describe("ParseItemSetBuilder", []() { it("handles rules with empty productions", [&]() { SyntaxGrammar grammar{{ SyntaxVariable{"rule0", VariableTypeNamed, { - Production({ + Production{{ {Symbol::non_terminal(1), 0, AssociativityNone}, {Symbol::terminal(11), 0, AssociativityNone}, - }), + }, 0}, }}, SyntaxVariable{"rule1", VariableTypeNamed, { - Production({ + Production{{ {Symbol::terminal(12), 0, AssociativityNone}, {Symbol::terminal(13), 0, AssociativityNone}, - }), - Production({}) + }, 0}, + Production{{}, 0} }}, }, {}, {}, {}}; diff --git a/test/compiler/prepare_grammar/flatten_grammar_test.cc b/test/compiler/prepare_grammar/flatten_grammar_test.cc index e9c90716..7b77caa8 100644 --- a/test/compiler/prepare_grammar/flatten_grammar_test.cc +++ b/test/compiler/prepare_grammar/flatten_grammar_test.cc @@ -34,21 +34,63 @@ describe("flatten_grammar", []() { AssertThat(result.name, Equals("test")); AssertThat(result.type, Equals(VariableTypeNamed)); AssertThat(result.productions, Equals(vector({ - Production({ + Production{{ {Symbol::non_terminal(1), 0, AssociativityNone}, {Symbol::non_terminal(2), 101, AssociativityLeft}, {Symbol::non_terminal(3), 102, AssociativityRight}, {Symbol::non_terminal(4), 101, AssociativityLeft}, {Symbol::non_terminal(6), 0, AssociativityNone}, {Symbol::non_terminal(7), 0, AssociativityNone}, - }), - Production({ + }, 0}, + Production{{ {Symbol::non_terminal(1), 0, AssociativityNone}, {Symbol::non_terminal(2), 101, AssociativityLeft}, {Symbol::non_terminal(5), 101, AssociativityLeft}, {Symbol::non_terminal(6), 0, AssociativityNone}, {Symbol::non_terminal(7), 0, AssociativityNone}, + }, 0} + }))); + }); + + it("stores the maximum dynamic precedence specified in each production", [&]() { + SyntaxVariable result = flatten_rule({ + "test", + VariableTypeNamed, + Rule::seq({ + Symbol::non_terminal(1), + Metadata::prec_dynamic(101, Rule::seq({ + Symbol::non_terminal(2), + Rule::choice({ + Metadata::prec_dynamic(102, Rule::seq({ + Symbol::non_terminal(3), + Symbol::non_terminal(4) + })), + Symbol::non_terminal(5), + }), + Symbol::non_terminal(6), + })), + Symbol::non_terminal(7), }) + }); + + AssertThat(result.name, Equals("test")); + AssertThat(result.type, Equals(VariableTypeNamed)); + AssertThat(result.productions, Equals(vector({ + Production{{ + {Symbol::non_terminal(1), 0, AssociativityNone}, + {Symbol::non_terminal(2), 0, AssociativityNone}, + {Symbol::non_terminal(3), 0, AssociativityNone}, + {Symbol::non_terminal(4), 0, AssociativityNone}, + {Symbol::non_terminal(6), 0, AssociativityNone}, + {Symbol::non_terminal(7), 0, AssociativityNone}, + }, 102}, + Production{{ + {Symbol::non_terminal(1), 0, AssociativityNone}, + {Symbol::non_terminal(2), 0, AssociativityNone}, + {Symbol::non_terminal(5), 0, AssociativityNone}, + {Symbol::non_terminal(6), 0, AssociativityNone}, + {Symbol::non_terminal(7), 0, AssociativityNone}, + }, 101} }))); }); @@ -63,10 +105,10 @@ describe("flatten_grammar", []() { }); AssertThat(result.productions, Equals(vector({ - Production({ + Production{{ {Symbol::non_terminal(1), 101, AssociativityLeft}, - {Symbol::non_terminal(2), 101, AssociativityLeft}, - }) + {Symbol::non_terminal(2), 101, AssociativityLeft}, + }, 0} }))); result = flatten_rule({ @@ -78,9 +120,9 @@ describe("flatten_grammar", []() { }); AssertThat(result.productions, Equals(vector({ - Production({ + Production{{ {Symbol::non_terminal(1), 101, AssociativityLeft}, - }) + }, 0} }))); }); }); diff --git a/test/fixtures/test_grammars/dynamic_precedence/corpus.txt b/test/fixtures/test_grammars/dynamic_precedence/corpus.txt new file mode 100644 index 00000000..242efc14 --- /dev/null +++ b/test/fixtures/test_grammars/dynamic_precedence/corpus.txt @@ -0,0 +1,25 @@ +=============================== +Declarations +=============================== + +int * x + +--- + +(program (declaration + (type (identifier)) + (declarator (identifier)))) + +=============================== +Expressions +=============================== + +int * x * y + +--- + +(program (expression + (expression + (expression (identifier)) + (expression (identifier))) + (expression (identifier)))) diff --git a/test/fixtures/test_grammars/dynamic_precedence/grammar.json b/test/fixtures/test_grammars/dynamic_precedence/grammar.json new file mode 100644 index 00000000..381ed4c2 --- /dev/null +++ b/test/fixtures/test_grammars/dynamic_precedence/grammar.json @@ -0,0 +1,73 @@ +{ + "name": "dynamic_precedence", + + "conflicts": [ + ["expression", "type"] + ], + + "extras": [ + {"type": "PATTERN", "value": "\\s"} + ], + + "rules": { + "program": { + "type": "CHOICE", + "members": [ + {"type": "SYMBOL", "name": "declaration"}, + {"type": "SYMBOL", "name": "expression"}, + ] + }, + + "expression": { + "type": "PREC_LEFT", + "value": 0, + "content": { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + {"type": "SYMBOL", "name": "expression"}, + {"type": "STRING", "value": "*"}, + {"type": "SYMBOL", "name": "expression"} + ] + }, + { + "type": "SYMBOL", + "name": "identifier" + } + ] + } + }, + + "declaration": { + "type": "SEQ", + "members": [ + {"type": "SYMBOL", "name": "type"}, + {"type": "SYMBOL", "name": "declarator"} + ] + }, + + "declarator": { + "type": "PREC_DYNAMIC", + "value": 1, + "content": { + "type": "SEQ", + "members": [ + {"type": "STRING", "value": "*"}, + {"type": "SYMBOL", "name": "identifier"} + ] + } + }, + + "type": { + "type": "SYMBOL", + "name": "identifier" + }, + + "identifier": { + "type": "PATTERN", + "value": "[a-zA-Z]+" + } + } +} diff --git a/test/fixtures/test_grammars/dynamic_precedence/readme.md b/test/fixtures/test_grammars/dynamic_precedence/readme.md new file mode 100644 index 00000000..a48a7997 --- /dev/null +++ b/test/fixtures/test_grammars/dynamic_precedence/readme.md @@ -0,0 +1 @@ +This grammar contains a conflict that is resolved at runtime. The PREC_DYNAMIC rule is used to indicate that the `declarator` rule should be preferred to the `expression` rule at runtime. diff --git a/test/helpers/stream_methods.cc b/test/helpers/stream_methods.cc index 23a03d21..f88ccaee 100644 --- a/test/helpers/stream_methods.cc +++ b/test/helpers/stream_methods.cc @@ -136,9 +136,14 @@ ostream &operator<<(ostream &stream, const Variable &variable) { return stream << "(Variable " << variable.name << " " << variable.rule << ")"; } +ostream &operator<<(ostream &stream, const Production &production) { + return stream << "(Production " << production.steps << " " << + to_string(production.dynamic_precedence) << ")"; +} + ostream &operator<<(ostream &stream, const SyntaxVariable &variable) { return stream << "(Variable " << variable.name << " " << variable.productions << - " " << to_string(variable.type) << "}"; + " " << to_string(variable.type) << ")"; } ostream &operator<<(ostream &stream, const LexicalVariable &variable) { diff --git a/test/helpers/stream_methods.h b/test/helpers/stream_methods.h index 49853c19..07fcd6e0 100644 --- a/test/helpers/stream_methods.h +++ b/test/helpers/stream_methods.h @@ -110,6 +110,7 @@ ostream &operator<<(ostream &, const InputGrammar &); ostream &operator<<(ostream &, const CompileError &); ostream &operator<<(ostream &, const ExternalToken &); ostream &operator<<(ostream &, const ProductionStep &); +ostream &operator<<(ostream &, const Production &); ostream &operator<<(ostream &, const PrecedenceRange &); ostream &operator<<(ostream &, const Variable &); ostream &operator<<(ostream &, const LexicalVariable &); diff --git a/test/integration/test_grammars.cc b/test/integration/test_grammars.cc index e2334bc7..c08af27c 100644 --- a/test/integration/test_grammars.cc +++ b/test/integration/test_grammars.cc @@ -13,7 +13,7 @@ vector test_languages = list_directory(grammars_dir_path); for (auto &language_name : test_languages) { if (language_name == "readme.md") continue; - describe(("test language: " + language_name).c_str(), [&]() { + describe(("test grammar: " + language_name).c_str(), [&]() { string directory_path = grammars_dir_path + "/" + language_name; string grammar_path = directory_path + "/grammar.json"; string grammar_json = read_file(grammar_path);