Treat parse conflicts as errors in grammar compilation

For now, only reduce/reduce conflicts w/ no tie-breaking precedence
are treated as errors. The rest are dropped, because shift/reduce
conflicts are currently very common because we don't have a way
of specifying associativity along w/ precedence.
This commit is contained in:
Max Brunsfeld 2015-03-15 20:31:41 -07:00
parent 8bd11e1b58
commit 9a198562e0
20 changed files with 1394 additions and 1408 deletions

View file

@ -1,70 +1,63 @@
#include "compiler/build_tables/action_takes_precedence.h"
#include <iostream>
namespace tree_sitter {
namespace build_tables {
using std::pair;
pair<bool, bool> action_takes_precedence(const ParseAction &new_action,
const ParseAction &old_action,
const rules::Symbol &symbol) {
pair<bool, ConflictType>
action_takes_precedence(const ParseAction &new_action,
const ParseAction &old_action,
const rules::Symbol &symbol) {
if (new_action.type < old_action.type) {
auto opposite =
action_takes_precedence(old_action, new_action, symbol);
return { !opposite.first, opposite.second };
}
bool has_precedence = false, has_conflict = false;
switch (old_action.type) {
case ParseActionTypeError:
has_precedence = true;
break;
return { true, ConflictTypeNone };
case ParseActionTypeShift: {
int min_precedence = *old_action.precedence_values.begin();
int max_precedence = *old_action.precedence_values.rbegin();
switch (new_action.type) {
case ParseActionTypeReduce: {
int new_precedence = *new_action.precedence_values.rbegin();
if (new_precedence < max_precedence) {
if (new_precedence > min_precedence)
has_conflict = true;
} else if (new_precedence > max_precedence) {
has_precedence = true;
} else {
has_conflict = true;
}
break;
case ParseActionTypeShift:
if (new_action.type == ParseActionTypeReduce) {
int min_precedence = *old_action.precedence_values.begin();
int max_precedence = *old_action.precedence_values.rbegin();
int new_precedence = *new_action.precedence_values.rbegin();
if (new_precedence < min_precedence)
return { false, ConflictTypeResolved };
else if (new_precedence > max_precedence)
return { true, ConflictTypeResolved };
else {
// TODO: Add associativity annotations. In the event of a precedence
// tie, return ConflictTypeError unless there is an associativity
// annotation to break the tie.
return { false, ConflictTypeResolved };
}
default:
break;
}
break;
}
case ParseActionTypeReduce:
switch (new_action.type) {
case ParseActionTypeReduce: {
int old_precedence = *old_action.precedence_values.begin();
int new_precedence = *new_action.precedence_values.begin();
if (new_precedence > old_precedence) {
has_precedence = true;
} else if (new_precedence == old_precedence) {
has_precedence = new_action.symbol.index < old_action.symbol.index;
has_conflict = true;
}
break;
if (new_action.type == ParseActionTypeReduce) {
int old_precedence = *old_action.precedence_values.begin();
int new_precedence = *new_action.precedence_values.begin();
if (new_precedence > old_precedence) {
return { true, ConflictTypeResolved };
} else if (new_precedence < old_precedence) {
return { false, ConflictTypeResolved };
} else {
return { false, ConflictTypeError };
}
default:
break;
}
default:
break;
}
return { has_precedence, has_conflict };
return { false, ConflictTypeNone };
}
bool action_takes_precedence(const LexAction &new_action,

View file

@ -10,9 +10,16 @@
namespace tree_sitter {
namespace build_tables {
std::pair<bool, bool> action_takes_precedence(const ParseAction &new_action,
const ParseAction &old_action,
const rules::Symbol &symbol);
enum ConflictType {
ConflictTypeNone,
ConflictTypeResolved,
ConflictTypeError
};
std::pair<bool, ConflictType>
action_takes_precedence(const ParseAction &new_action,
const ParseAction &old_action,
const rules::Symbol &symbol);
bool action_takes_precedence(const LexAction &new_action,
const LexAction &old_action);

View file

@ -57,17 +57,18 @@ static string action_description(const ParseAction &action,
}
}
Conflict build_conflict(const ParseAction &left, const ParseAction &right,
string build_conflict(const ParseAction &left, const ParseAction &right,
const ParseItemSet &item_set, const Symbol &sym,
const SyntaxGrammar &grammar,
const LexicalGrammar &lex_grammar) {
if (right < left)
return build_conflict(right, left, item_set, sym, grammar, lex_grammar);
return Conflict(symbol_name(sym, grammar, lex_grammar) + ": " +
action_description(left, item_set, grammar, lex_grammar) +
" / " +
action_description(right, item_set, grammar, lex_grammar));
return symbol_name(sym, grammar, lex_grammar) +
": " +
action_description(left, item_set, grammar, lex_grammar) +
" / " +
action_description(right, item_set, grammar, lex_grammar);
}
} // namespace build_tables

View file

@ -13,9 +13,9 @@ class LexicalGrammar;
namespace build_tables {
Conflict build_conflict(const ParseAction &left, const ParseAction &right,
const ParseItemSet &item_set, const rules::Symbol &,
const SyntaxGrammar &, const LexicalGrammar &);
std::string build_conflict(const ParseAction &, const ParseAction &,
const ParseItemSet &, const rules::Symbol &,
const SyntaxGrammar &, const LexicalGrammar &);
} // namespace build_tables
} // namespace tree_sitter

View file

@ -35,14 +35,14 @@ class ParseTableBuilder {
vector<vector<Symbol>> productions;
vector<pair<ParseItemSet, ParseStateId>> item_sets_to_process;
ParseTable parse_table;
std::set<Conflict> conflicts;
std::set<string> conflicts;
public:
ParseTableBuilder(const SyntaxGrammar &grammar,
const LexicalGrammar &lex_grammar)
: grammar(grammar), lex_grammar(lex_grammar) {}
pair<ParseTable, vector<Conflict>> build() {
pair<ParseTable, const GrammarError *> build() {
auto start_symbol = grammar.rules.empty()
? make_shared<Symbol>(0, rules::SymbolOptionToken)
: make_shared<Symbol>(0);
@ -59,6 +59,12 @@ class ParseTableBuilder {
add_reduce_actions(item_set, state_id);
add_shift_actions(item_set, state_id);
add_shift_extra_actions(state_id);
if (!conflicts.empty())
return {
parse_table,
new GrammarError(GrammarErrorTypeParseConflict, *conflicts.begin())
};
}
for (ParseStateId state = 0; state < parse_table.states.size(); state++)
@ -67,7 +73,7 @@ class ParseTableBuilder {
parse_table.symbols.insert(rules::ERROR());
parse_table.symbols.insert(rules::DOCUMENT());
return { parse_table, conflicts_vector() };
return { parse_table, nullptr };
}
private:
@ -162,13 +168,18 @@ class ParseTableBuilder {
auto result = action_takes_precedence(action, current_action->second,
symbol);
if (result.second) {
record_conflict(symbol, current_action->second, action, item_set);
if (action.type == ParseActionTypeReduce)
parse_table.fragile_production_ids.insert(action.production_id);
if (current_action->second.type == ParseActionTypeReduce)
parse_table.fragile_production_ids.insert(current_action->second.production_id);
switch (result.second) {
case ConflictTypeResolved:
if (action.type == ParseActionTypeReduce)
parse_table.fragile_production_ids.insert(action.production_id);
if (current_action->second.type == ParseActionTypeReduce)
parse_table.fragile_production_ids.insert(current_action->second.production_id);
break;
case ConflictTypeError:
record_conflict(symbol, current_action->second, action, item_set);
break;
default:
break;
}
return result.first;
@ -197,18 +208,17 @@ class ParseTableBuilder {
void record_conflict(const Symbol &sym, const ParseAction &left,
const ParseAction &right, const ParseItemSet &item_set) {
conflicts.insert(
build_conflict(left, right, item_set, sym, grammar, lex_grammar));
conflicts.insert(build_conflict(left, right, item_set, sym, grammar, lex_grammar));
}
vector<Conflict> conflicts_vector() const {
vector<Conflict> result;
vector<string> conflicts_vector() const {
vector<string> result;
result.insert(result.end(), conflicts.begin(), conflicts.end());
return result;
}
};
pair<ParseTable, vector<Conflict>> build_parse_table(
pair<ParseTable, const GrammarError *> build_parse_table(
const SyntaxGrammar &grammar, const LexicalGrammar &lex_grammar) {
return ParseTableBuilder(grammar, lex_grammar).build();
}

View file

@ -13,8 +13,8 @@ class LexicalGrammar;
namespace build_tables {
std::pair<ParseTable, std::vector<Conflict>> build_parse_table(
const SyntaxGrammar &, const LexicalGrammar &);
std::pair<ParseTable, const GrammarError *>
build_parse_table(const SyntaxGrammar &, const LexicalGrammar &);
} // namespace build_tables
} // namespace tree_sitter

View file

@ -6,17 +6,18 @@
namespace tree_sitter {
namespace build_tables {
using std::string;
using std::tuple;
using std::vector;
using std::make_tuple;
tuple<ParseTable, LexTable, vector<Conflict>> build_tables(
const SyntaxGrammar &grammar, const LexicalGrammar &lex_grammar) {
tuple<ParseTable, LexTable, const GrammarError *>
build_tables(const SyntaxGrammar &grammar, const LexicalGrammar &lex_grammar) {
auto parse_table_result = build_parse_table(grammar, lex_grammar);
ParseTable parse_table = parse_table_result.first;
vector<Conflict> conflicts = parse_table_result.second;
const GrammarError *error = parse_table_result.second;
LexTable lex_table = build_lex_table(&parse_table, lex_grammar);
return make_tuple(parse_table, lex_table, conflicts);
return make_tuple(parse_table, lex_table, error);
}
} // namespace build_tables

View file

@ -1,6 +1,7 @@
#ifndef COMPILER_BUILD_TABLES_BUILD_TABLES_H_
#define COMPILER_BUILD_TABLES_BUILD_TABLES_H_
#include <string>
#include <utility>
#include <vector>
#include "tree_sitter/compiler.h"
@ -14,8 +15,8 @@ class LexicalGrammar;
namespace build_tables {
std::tuple<ParseTable, LexTable, std::vector<Conflict>> build_tables(
const SyntaxGrammar &, const LexicalGrammar &);
std::tuple<ParseTable, LexTable, const GrammarError *>
build_tables(const SyntaxGrammar &, const LexicalGrammar &);
} // namespace build_tables
} // namespace tree_sitter

View file

@ -7,32 +7,35 @@
namespace tree_sitter {
using std::tuple;
using std::pair;
using std::string;
using std::vector;
using std::get;
using std::make_tuple;
tuple<string, vector<Conflict>, const GrammarError *> compile(
const Grammar &grammar, std::string name) {
pair<string, const GrammarError *>
compile(const Grammar &grammar, std::string name) {
auto prepare_grammar_result = prepare_grammar::prepare_grammar(grammar);
const SyntaxGrammar &syntax_grammar = get<0>(prepare_grammar_result);
const LexicalGrammar &lexical_grammar = get<1>(prepare_grammar_result);
const GrammarError *error = get<2>(prepare_grammar_result);
if (error)
return make_tuple("", vector<Conflict>(), error);
return {"", error};
auto table_build_result =
build_tables::build_tables(syntax_grammar, lexical_grammar);
const ParseTable &parse_table = get<0>(table_build_result);
const LexTable &lex_table = get<1>(table_build_result);
const vector<Conflict> &conflicts = get<2>(table_build_result);
error = get<2>(table_build_result);
if (error)
return {"", error};
string code = generate_code::c_code(name, parse_table, lex_table,
syntax_grammar, lexical_grammar);
return make_tuple(code, conflicts, nullptr);
return {code, nullptr};
}
} // namespace tree_sitter

View file

@ -1,22 +0,0 @@
#include <string>
#include "tree_sitter/compiler.h"
namespace tree_sitter {
using std::string;
Conflict::Conflict(string description) : description(description) {}
bool Conflict::operator==(const tree_sitter::Conflict &other) const {
return other.description == description;
}
bool Conflict::operator<(const tree_sitter::Conflict &other) const {
return other.description < description;
}
std::ostream &operator<<(std::ostream &stream, const Conflict &conflict) {
return stream << "#<conflict " + conflict.description + ">";
}
} // namespace tree_sitter

View file

@ -58,11 +58,11 @@ rule_ptr err(const rule_ptr &rule) {
return choice({ rule, ERROR().copy() });
}
rule_ptr prec(int precedence, rule_ptr rule) {
rule_ptr prec(int precedence, const rule_ptr &rule) {
return metadata(rule, { { PRECEDENCE, precedence } });
}
rule_ptr token(rule_ptr rule) {
rule_ptr token(const rule_ptr &rule) {
return metadata(rule, { { IS_TOKEN, 1 } });
}