Make conflict messages explicit about precedence combinations
This commit is contained in:
parent
42ba70a5a0
commit
cab1bd3ac5
2 changed files with 134 additions and 30 deletions
|
|
@ -90,15 +90,13 @@ describe("compile_grammar", []() {
|
|||
|
||||
Possible interpretations:
|
||||
|
||||
(math_operation expression '+' expression) • '+' …
|
||||
|
||||
expression '+' (math_operation expression • '+' expression)
|
||||
1: (math_operation expression '+' expression) • '+' …
|
||||
2: expression '+' (math_operation expression • '+' expression)
|
||||
|
||||
Possible resolutions:
|
||||
|
||||
Specify left or right associativity in the rules: math_operation
|
||||
|
||||
Add a conflict for the rules: math_operation
|
||||
1: Specify a left or right associativity in `math_operation`
|
||||
2: Add a conflict for these rules: `math_operation`
|
||||
)MESSAGE")));
|
||||
|
||||
result = ts_compile_grammar(fill_template(grammar_template, {
|
||||
|
|
@ -201,17 +199,15 @@ describe("compile_grammar", []() {
|
|||
|
||||
Possible interpretations:
|
||||
|
||||
(expression identifier) • '{' …
|
||||
|
||||
(function_call identifier • block)
|
||||
1: (expression identifier) • '{' …
|
||||
2: (function_call identifier • block)
|
||||
|
||||
Possible resolutions:
|
||||
|
||||
Use different precedences in the rules: expression function_call
|
||||
|
||||
Specify left or right associativity in the rules: expression
|
||||
|
||||
Add a conflict for the rules: expression function_call
|
||||
1: Specify a higher precedence in `function_call` than in the other rules.
|
||||
2: Specify a higher precedence in `expression` than in the other rules.
|
||||
3: Specify a left or right associativity in `expression`
|
||||
4: Add a conflict for these rules: `expression` `function_call`
|
||||
)MESSAGE")));
|
||||
|
||||
// Giving function calls lower precedence than expressions causes `bar`
|
||||
|
|
@ -246,6 +242,88 @@ describe("compile_grammar", []() {
|
|||
"(identifier) "
|
||||
"(block (expression (identifier)))))))");
|
||||
});
|
||||
|
||||
it("does not allow conflicting precedences", [&]() {
|
||||
string grammar_template = R"JSON({
|
||||
"name": "conflicting_precedence_example",
|
||||
|
||||
"rules": {
|
||||
"expression": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{"type": "SYMBOL", "name": "sum"},
|
||||
{"type": "SYMBOL", "name": "product"},
|
||||
{"type": "SYMBOL", "name": "other_thing"}
|
||||
]
|
||||
},
|
||||
|
||||
"sum": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": 0,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{"type": "SYMBOL", "name": "expression"},
|
||||
{"type": "STRING", "value": "+"},
|
||||
{"type": "SYMBOL", "name": "expression"}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"product": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": 1,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{"type": "SYMBOL", "name": "expression"},
|
||||
{"type": "STRING", "value": "*"},
|
||||
{"type": "SYMBOL", "name": "expression"}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"other_thing": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": -1,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{"type": "SYMBOL", "name": "expression"},
|
||||
{"type": "STRING", "value": "*"},
|
||||
{"type": "STRING", "value": "*"}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"identifier": {
|
||||
"type": "PATTERN",
|
||||
"value": "[a-zA-Z]+"
|
||||
}
|
||||
}
|
||||
})JSON";
|
||||
|
||||
TSCompileResult result = ts_compile_grammar(fill_template(grammar_template, {
|
||||
}).c_str());
|
||||
|
||||
AssertThat(result.error_message, Equals(dedent(R"MESSAGE(
|
||||
Unresolved conflict for symbol sequence:
|
||||
|
||||
expression '+' expression • '*' …
|
||||
|
||||
Possible interpretations:
|
||||
|
||||
1: (sum expression '+' expression) • '*' …
|
||||
2: expression '+' (product expression • '*' expression)
|
||||
3: expression '+' (other_thing expression • '*' '*')
|
||||
|
||||
Possible resolutions:
|
||||
|
||||
1: Specify a higher precedence in `product` and `other_thing` than in the other rules.
|
||||
2: Specify a higher precedence in `sum` than in the other rules.
|
||||
3: Add a conflict for these rules: `sum` `product` `other_thing`
|
||||
)MESSAGE")));
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the grammar's start symbol is a token", [&]() {
|
||||
|
|
|
|||
|
|
@ -363,6 +363,7 @@ class ParseTableBuilder {
|
|||
ParseTableEntry &entry = parse_table.states[state_id].terminal_entries[lookahead];
|
||||
int reduction_precedence = entry.actions.front().precedence();
|
||||
set<ParseItem> shift_items;
|
||||
bool considered_associativity = false;
|
||||
|
||||
for (const ParseAction &action : entry.actions)
|
||||
if (action.type == ParseActionTypeReduce)
|
||||
|
|
@ -406,6 +407,7 @@ class ParseTableBuilder {
|
|||
// associative, prefer the shift.
|
||||
else if (shift_precedence.min == reduction_precedence &&
|
||||
shift_precedence.max == reduction_precedence) {
|
||||
considered_associativity = true;
|
||||
bool has_non_associative_reductions = false;
|
||||
bool has_left_associative_reductions = false;
|
||||
bool has_right_associative_reductions = false;
|
||||
|
|
@ -435,8 +437,6 @@ class ParseTableBuilder {
|
|||
entry.actions.pop_back();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "Mismatched precedence";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -473,8 +473,11 @@ class ParseTableBuilder {
|
|||
|
||||
description += "Possible interpretations:\n\n";
|
||||
|
||||
size_t interpretation_count = 1;
|
||||
for (const ParseAction &action : entry.actions) {
|
||||
if (action.type == ParseActionTypeReduce) {
|
||||
description += " " + to_string(interpretation_count++) + ":";
|
||||
|
||||
for (size_t i = 0; i < earliest_starting_item.step_index - action.consumed_symbol_count; i++) {
|
||||
description += " " + symbol_name(earliest_starting_item.production->at(i).symbol);
|
||||
}
|
||||
|
|
@ -485,11 +488,13 @@ class ParseTableBuilder {
|
|||
}
|
||||
description += ")";
|
||||
description += " \u2022 " + symbol_name(Symbol(lookahead, true)) + " \u2026";
|
||||
description += "\n\n";
|
||||
description += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for (const ParseItem &shift_item : shift_items) {
|
||||
description += " " + to_string(interpretation_count++) + ":";
|
||||
|
||||
for (size_t i = 0; i < earliest_starting_item.step_index - shift_item.step_index; i++) {
|
||||
description += " " + symbol_name(earliest_starting_item.production->at(i).symbol);
|
||||
}
|
||||
|
|
@ -501,32 +506,53 @@ class ParseTableBuilder {
|
|||
description += " " + symbol_name(shift_item.production->at(i).symbol);
|
||||
}
|
||||
description += ")";
|
||||
description += "\n\n";
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
description += "Possible resolutions:\n\n";
|
||||
description += "\nPossible resolutions:\n\n";
|
||||
|
||||
size_t resolution_count = 1;
|
||||
if (actual_conflict.size() > 1) {
|
||||
description += " Use different precedences in the rules:";
|
||||
for (const Symbol &conflict_symbol : actual_conflict) {
|
||||
description += " " + symbol_name(conflict_symbol);
|
||||
if (!shift_items.empty()) {
|
||||
description += " " + to_string(resolution_count++) + ": ";
|
||||
description += "Specify a higher precedence in";
|
||||
bool is_first = true;
|
||||
for (const ParseItem &shift_item : shift_items) {
|
||||
if (!is_first) description += " and";
|
||||
description += " `" + symbol_name(shift_item.lhs()) + "`";
|
||||
is_first = false;
|
||||
}
|
||||
description += " than in the other rules.\n";
|
||||
}
|
||||
description += "\n\n";
|
||||
}
|
||||
|
||||
if (shift_items.size() > 0) {
|
||||
description += " Specify left or right associativity in the rules:";
|
||||
for (const ParseAction &action : entry.actions) {
|
||||
if (action.type == ParseActionTypeReduce) {
|
||||
description += " " + symbol_name(action.symbol);
|
||||
description += " " + to_string(resolution_count++) + ": ";
|
||||
description += "Specify a higher precedence in `";
|
||||
description += symbol_name(action.symbol);
|
||||
description += "` than in the other rules.\n";
|
||||
}
|
||||
}
|
||||
description += "\n\n";
|
||||
}
|
||||
|
||||
description += " Add a conflict for the rules:";
|
||||
if (considered_associativity) {
|
||||
description += " " + to_string(resolution_count++) + ": ";
|
||||
description += "Specify a left or right associativity in";
|
||||
for (const ParseAction &action : entry.actions) {
|
||||
bool is_first = true;
|
||||
if (action.type == ParseActionTypeReduce) {
|
||||
if (!is_first) description += " and";
|
||||
description += " `" + symbol_name(action.symbol) + "`";
|
||||
is_first = false;
|
||||
}
|
||||
}
|
||||
description += "\n";
|
||||
}
|
||||
|
||||
description += " " + to_string(resolution_count++) + ": ";
|
||||
description += "Add a conflict for these rules:";
|
||||
for (const Symbol &conflict_symbol : actual_conflict) {
|
||||
description += " " + symbol_name(conflict_symbol);
|
||||
description += " `" + symbol_name(conflict_symbol) + "`";
|
||||
}
|
||||
description += "\n";
|
||||
return description;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue