Make conflict messages explicit about precedence combinations

This commit is contained in:
Max Brunsfeld 2016-11-18 17:05:16 -08:00
parent 42ba70a5a0
commit cab1bd3ac5
2 changed files with 134 additions and 30 deletions

View file

@ -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", [&]() {

View file

@ -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;