perf(generate): represent the grammar IR as a flat, pooled arena

Replace the owned `Rule`-tree grammar with a flat, pooled representation. `Rule`
nodes live in a `RulePool` arena and reference their children and params by
index (`RuleId`). Strings are interned in a `StrPool` and referred to by `StrId`.
Productions are stored as flat `ProductionStep`/`Production` slices indexed per
variable, rather than as nested vectors hanging off each `SyntaxVariable`. This
drops the per-rule heap allocation of the owned-tree form and is the groundwork
for the later table-building optimizations.

`parse_grammar` builds the pool, the prepare passes rewrite nodes in place, and
`prepare_grammar` returns a `PreparedGrammar` bundle that owns the run's `StrPool`
alongside the grammars. `build_tables`, `node_types`, and `render` borrow that
pool and resolve `StrId`s only at the output boundary.

Shows an average ~10% walltime reduction when combined with the previous
commit, with nearly all of the savings in `build_tables`. The `prepare`
stage is also ~70% faster and much more stable, but this contributes
much less to overall generate time. Improving early stages such as
`prepare` will be important for future interactive uses.
This commit is contained in:
Will Lillis 2026-08-09 11:51:45 -05:00
parent e72c8594fb
commit fb2052f02d
25 changed files with 6040 additions and 4270 deletions

View file

@ -52,6 +52,7 @@ similar_names = "allow"
string_lit_as_bytes = "allow"
struct_excessive_bools = "allow"
too_many_lines = "allow"
tuple_array_conversions = "allow"
[workspace.lints.rust]
mismatched_lifetime_syntaxes = "allow"

View file

@ -29,6 +29,7 @@ use crate::{
nfa::{CharacterSet, NfaCursor},
node_types::VariableInfo,
rules::{AliasMap, Symbol, SymbolType, TokenSet},
strpool::StrPool,
tables::{LexTable, ParseAction, ParseTable, ParseTableEntry},
};
@ -49,20 +50,21 @@ pub fn build_tables(
simple_aliases: &AliasMap,
variable_info: &[VariableInfo],
inlines: &InlinedProductionMap,
str_pool: &StrPool,
report_symbol_name: Option<&str>,
optimizations: OptLevel,
diagnostics: &mut Vec<Diagnostic>,
) -> BuildTableResult<Tables> {
let item_key_map = ItemKeyMap::new(syntax_grammar, inlines);
let item_key_map = ItemKeyMap::new(syntax_grammar, str_pool);
let item_set_builder =
ParseItemSetBuilder::new(syntax_grammar, lexical_grammar, inlines, &item_key_map);
let following_tokens =
get_following_tokens(syntax_grammar, lexical_grammar, inlines, &item_set_builder);
let following_tokens = get_following_tokens(syntax_grammar, lexical_grammar, &item_set_builder);
let (mut parse_table, parse_state_info) = build_parse_table(
syntax_grammar,
lexical_grammar,
item_set_builder,
variable_info,
str_pool,
diagnostics,
)?;
let token_conflict_map = TokenConflictMap::new(lexical_grammar, following_tokens);
@ -73,6 +75,7 @@ pub fn build_tables(
syntax_grammar.word_token,
&token_conflict_map,
&coincident_token_index,
str_pool,
);
populate_error_state(
&mut parse_table,
@ -81,6 +84,7 @@ pub fn build_tables(
&coincident_token_index,
&token_conflict_map,
&keywords,
str_pool,
);
populate_used_symbols(&mut parse_table, syntax_grammar, lexical_grammar);
minimize_parse_table(
@ -90,6 +94,7 @@ pub fn build_tables(
simple_aliases,
&token_conflict_map,
&keywords,
str_pool,
optimizations,
);
let lex_tables = build_lex_table(
@ -109,6 +114,7 @@ pub fn build_tables(
lexical_grammar,
&parse_table,
&parse_state_info,
str_pool,
report_symbol_name,
);
}
@ -128,25 +134,20 @@ pub fn build_tables(
fn get_following_tokens(
syntax_grammar: &SyntaxGrammar,
lexical_grammar: &LexicalGrammar,
inlines: &InlinedProductionMap,
builder: &ParseItemSetBuilder,
) -> Vec<TokenSet> {
let n_terminals = lexical_grammar.variables.len();
let n_externals = syntax_grammar.external_tokens.len();
let mut result = vec![TokenSet::with_capacity(n_terminals, n_externals); n_terminals];
let productions = syntax_grammar
.variables
.iter()
.flat_map(|v| &v.productions)
.chain(&inlines.productions);
let all_tokens = (0..result.len())
.map(Symbol::terminal)
.collect::<TokenSet>();
for production in productions {
for i in 1..production.steps.len() {
let left_tokens = builder.last_set(&production.steps[i - 1].symbol);
let right_tokens = builder.first_set(&production.steps[i].symbol);
let right_reserved_tokens = builder.reserved_first_set(&production.steps[i].symbol);
for production in &syntax_grammar.productions {
let steps = &syntax_grammar.steps[production.step_range()];
for i in 1..steps.len() {
let left_tokens = builder.last_set(&steps[i - 1].symbol());
let right_tokens = builder.first_set(&steps[i].symbol());
let right_reserved_tokens = builder.reserved_first_set(&steps[i].symbol());
for left_token in left_tokens.iter() {
if left_token.is_terminal() {
result[left_token.index].insert_all_terminals(right_tokens);
@ -175,6 +176,7 @@ fn populate_error_state(
coincident_token_index: &CoincidentTokenIndex,
token_conflict_map: &TokenConflictMap,
keywords: &TokenSet,
str_pool: &StrPool,
) {
let state = &mut parse_table.states[0];
let n = lexical_grammar.variables.len();
@ -193,7 +195,7 @@ fn populate_error_state(
} else {
debug!(
"error recovery - token {} has no conflicts",
lexical_grammar.variables[i].name
str_pool.resolve(lexical_grammar.variables[i].name)
);
Some(Symbol::terminal(i))
}
@ -219,13 +221,14 @@ fn populate_error_state(
{
debug!(
"error recovery - exclude token {} because of conflict with {}",
lexical_grammar.variables[i].name, lexical_grammar.variables[t.index].name
str_pool.resolve(lexical_grammar.variables[i].name),
str_pool.resolve(lexical_grammar.variables[t.index].name)
);
continue;
}
debug!(
"error recovery - include token {}",
lexical_grammar.variables[i].name
str_pool.resolve(lexical_grammar.variables[i].name)
);
state
.terminal_entries
@ -335,6 +338,7 @@ fn identify_keywords(
word_token: Option<Symbol>,
token_conflict_map: &TokenConflictMap,
coincident_token_index: &CoincidentTokenIndex,
str_pool: &StrPool,
) -> TokenSet {
if word_token.is_none() {
return TokenSet::new();
@ -357,7 +361,7 @@ fn identify_keywords(
{
debug!(
"Keywords - add candidate {}",
lexical_grammar.variables[i].name
str_pool.resolve(lexical_grammar.variables[i].name)
);
Some(Symbol::terminal(i))
} else {
@ -376,8 +380,8 @@ fn identify_keywords(
{
debug!(
"Keywords - exclude {} because it matches the same string as {}",
lexical_grammar.variables[token.index].name,
lexical_grammar.variables[other_token.index].name
str_pool.resolve(lexical_grammar.variables[token.index].name),
str_pool.resolve(lexical_grammar.variables[other_token.index].name)
);
return false;
}
@ -419,8 +423,8 @@ fn identify_keywords(
) {
debug!(
"Keywords - exclude {} because of conflict with {}",
lexical_grammar.variables[token.index].name,
lexical_grammar.variables[other_index].name
str_pool.resolve(lexical_grammar.variables[token.index].name),
str_pool.resolve(lexical_grammar.variables[other_index].name)
);
return false;
}
@ -428,7 +432,7 @@ fn identify_keywords(
debug!(
"Keywords - include {}",
lexical_grammar.variables[token.index].name,
str_pool.resolve(lexical_grammar.variables[token.index].name),
);
true
})
@ -462,6 +466,7 @@ fn report_state_info<'a>(
lexical_grammar: &LexicalGrammar,
parse_table: &ParseTable,
parse_state_info: &[ParseStateInfo<'a>],
str_pool: &StrPool,
report_symbol_name: &'a str,
) {
let mut all_state_indices = BTreeSet::new();
@ -486,13 +491,13 @@ fn report_state_info<'a>(
let max_symbol_name_length = syntax_grammar
.variables
.iter()
.map(|v| v.name.len())
.map(|v| str_pool.resolve(v.name).len())
.max()
.unwrap();
for (symbol, states) in &symbols_with_state_indices {
info!(
"{:width$}\t{}",
syntax_grammar.variables[symbol.index].name,
str_pool.resolve(syntax_grammar.variables[symbol.index].name),
states.len(),
width = max_symbol_name_length
);
@ -505,7 +510,9 @@ fn report_state_info<'a>(
symbols_with_state_indices
.iter()
.find_map(|(symbol, state_indices)| {
if syntax_grammar.variables[symbol.index].name == report_symbol_name {
if str_pool.resolve(syntax_grammar.variables[symbol.index].name)
== report_symbol_name
{
Some(state_indices)
} else {
None
@ -528,11 +535,11 @@ fn report_state_info<'a>(
.iter()
.map(|symbol| {
if symbol.is_terminal() {
lexical_grammar.variables[symbol.index].name.clone()
str_pool.resolve(lexical_grammar.variables[symbol.index].name)
} else if symbol.is_external() {
syntax_grammar.external_tokens[symbol.index].name.clone()
str_pool.resolve(syntax_grammar.external_tokens[symbol.index].name)
} else {
syntax_grammar.variables[symbol.index].name.clone()
str_pool.resolve(syntax_grammar.variables[symbol.index].name)
}
})
.collect::<Vec<_>>()
@ -540,7 +547,7 @@ fn report_state_info<'a>(
);
info!(
"\nitems:\n{}",
item::ParseItemSetDisplay(item_set, syntax_grammar, lexical_grammar),
item::ParseItemSetDisplay(item_set, syntax_grammar, lexical_grammar, str_pool),
);
}
}

View file

@ -15,9 +15,11 @@ use super::{
};
use crate::{
Diagnostic,
build_tables::item::prec_display,
grammars::{LexicalGrammar, PrecedenceEntry, ReservedWordSetId, SyntaxGrammar, VariableType},
node_types::VariableInfo,
rules::{Associativity, Precedence, Symbol, SymbolType, TokenSet},
strpool::StrPool,
tables::{
FieldLocation, GotoAction, ParseAction, ParseState, ParseStateId, ParseTable,
ParseTableEntry, ProductionInfo, ProductionInfoId,
@ -63,6 +65,7 @@ struct ParseTableBuilder<'a> {
non_terminal_extra_states: Vec<(Symbol, usize)>,
actual_conflicts: FxHashSet<Vec<Symbol>>,
parse_table: ParseTable,
str_pool: &'a StrPool,
}
pub type BuildTableResult<T> = Result<T, ParseTableBuilderError>;
@ -244,6 +247,7 @@ impl<'a> ParseTableBuilder<'a> {
lexical_grammar: &'a LexicalGrammar,
item_set_builder: ParseItemSetBuilder<'a>,
variable_info: &'a [VariableInfo],
str_pool: &'a StrPool,
) -> Self {
Self {
syntax_grammar,
@ -263,6 +267,7 @@ impl<'a> ParseTableBuilder<'a> {
production_infos: Vec::new(),
max_aliased_production_length: 1,
},
str_pool,
}
}
@ -299,16 +304,19 @@ impl<'a> ParseTableBuilder<'a> {
.iter()
.filter(|s| s.is_non_terminal())
{
let variable = &self.syntax_grammar.variables[extra_non_terminal.index];
for production in &variable.productions {
for prod_id in self
.syntax_grammar
.variable_prod_ids(extra_non_terminal.index)
{
let production = self.syntax_grammar.production(prod_id);
non_terminal_extra_item_sets_by_first_terminal
.entry(production.first_symbol().unwrap())
.or_insert_with(ParseItemSet::default)
.insert(ParseItem {
variable_index: extra_non_terminal.index as u32,
production,
prod_id,
step_index: 1,
keys: self.item_set_builder.key_map.keys_for(production),
keys: self.item_set_builder.key_map.keys_for(prod_id),
has_preceding_inherited_fields: false,
})
.lookaheads
@ -426,7 +434,7 @@ impl<'a> ParseTableBuilder<'a> {
// If the item is unfinished, then this state has a transition for the item's
// next symbol. Advance the item to its next step and insert the resulting
// item into the successor item set.
if let Some(next_symbol) = item.symbol() {
if let Some(next_symbol) = item.symbol(self.syntax_grammar) {
let mut successor = item.successor();
let successor_set = if next_symbol.is_non_terminal() {
let variable = &self.syntax_grammar.variables[next_symbol.index];
@ -489,13 +497,13 @@ impl<'a> ParseTableBuilder<'a> {
ParseAction::Reduce {
symbol,
child_count: item.step_index as u16,
dynamic_precedence: item.production.dynamic_precedence,
dynamic_precedence: item.production(self.syntax_grammar).dynamic_precedence,
production_id: production_id as u16,
}
};
let precedence = item.precedence();
let associativity = item.associativity();
let precedence = item.precedence(self.syntax_grammar);
let associativity = item.associativity(self.syntax_grammar);
for lookahead in lookaheads.iter() {
let table_entry = self.parse_table.states[state_id]
.terminal_entries
@ -513,7 +521,7 @@ impl<'a> ParseTableBuilder<'a> {
self.syntax_grammar,
precedence,
&[symbol],
&reduction_info.precedence,
reduction_info.precedence,
&reduction_info.symbols,
) {
Ordering::Greater => {
@ -530,7 +538,7 @@ impl<'a> ParseTableBuilder<'a> {
}
}
reduction_info.precedence.clone_from(precedence);
reduction_info.precedence = precedence;
if let Err(i) = reduction_info.symbols.binary_search(&symbol) {
reduction_info.symbols.insert(i, symbol);
}
@ -627,9 +635,9 @@ impl<'a> ParseTableBuilder<'a> {
let parent_symbol_names = parent_symbols
.iter()
.map(|&variable_index| {
self.syntax_grammar.variables[variable_index as usize]
.name
.clone()
self.str_pool
.resolve(self.syntax_grammar.variables[variable_index as usize].name)
.to_string()
})
.collect::<Vec<_>>();
@ -678,9 +686,9 @@ impl<'a> ParseTableBuilder<'a> {
.entries
.iter()
.filter_map(|entry| {
if let Some(next_step) = entry.item.step() {
if next_step.symbol == keyword_capture_token {
Some(next_step.reserved_word_set_id)
if let Some(next_step) = entry.item.step(self.syntax_grammar) {
if next_step.symbol() == keyword_capture_token {
Some(ReservedWordSetId(usize::from(next_step.reserved)))
} else {
None
}
@ -721,17 +729,17 @@ impl<'a> ParseTableBuilder<'a> {
// REDUCE-REDUCE conflicts where all actions have the *same*
// precedence, and there can still be SHIFT/REDUCE conflicts.
let mut considered_associativity = false;
let mut shift_precedence = Vec::<(&Precedence, Symbol)>::new();
let mut shift_precedence = Vec::<(Precedence, Symbol)>::new();
let mut conflicting_items = BTreeSet::new();
for ParseItemSetEntry {
item, lookaheads, ..
} in &item_set.entries
{
if let Some(step) = item.step() {
if let Some(step) = item.step(self.syntax_grammar) {
if item.step_index > 0
&& self
.item_set_builder
.first_set(&step.symbol)
.first_set(&step.symbol())
.contains(&conflicting_lookahead)
{
if item.variable_index != u32::MAX {
@ -739,7 +747,7 @@ impl<'a> ParseTableBuilder<'a> {
}
let p = (
item.precedence(),
item.precedence(self.syntax_grammar),
Symbol::non_terminal(item.variable_index as usize),
);
if let Err(i) = shift_precedence.binary_search(&p) {
@ -778,7 +786,7 @@ impl<'a> ParseTableBuilder<'a> {
self.syntax_grammar,
p.0,
&[p.1],
&reduction_info.precedence,
reduction_info.precedence,
&reduction_info.symbols,
) {
Ordering::Greater => shift_is_more = true,
@ -901,23 +909,29 @@ impl<'a> ParseTableBuilder<'a> {
.map(|symbol| self.symbol_name(symbol))
.collect::<Vec<_>>();
let variable_name = self.syntax_grammar.variables[item.variable_index as usize]
.name
.clone();
let variable_name = self
.str_pool
.resolve(self.syntax_grammar.variables[item.variable_index as usize].name)
.to_string();
let production_step_symbols = item
.production
.production(self.syntax_grammar)
.steps
.iter()
.map(|step| self.symbol_name(&step.symbol))
.map(|step| self.symbol_name(&step.symbol()))
.collect::<Vec<_>>();
let precedence = match item.precedence() {
let precedence = match item.precedence(self.syntax_grammar) {
Precedence::None => None,
_ => Some(item.precedence().to_string()),
_ => Some(prec_display(
item.precedence(self.syntax_grammar),
self.str_pool,
)),
};
let associativity = item.associativity().map(|assoc| format!("{assoc:?}"));
let associativity = item
.associativity(self.syntax_grammar)
.map(|assoc| format!("{assoc:?}"));
Interpretation {
preceding_symbols,
@ -1000,17 +1014,17 @@ impl<'a> ParseTableBuilder<'a> {
fn compare_precedence(
grammar: &SyntaxGrammar,
left: &Precedence,
left: Precedence,
left_symbols: &[Symbol],
right: &Precedence,
right: Precedence,
right_symbols: &[Symbol],
) -> Ordering {
let precedence_entry_matches =
|entry: &PrecedenceEntry, precedence: &Precedence, symbols: &[Symbol]| -> bool {
|entry: &PrecedenceEntry, precedence: Precedence, symbols: &[Symbol]| -> bool {
match entry {
PrecedenceEntry::Name(n) => {
if let Precedence::Name(p) = precedence {
n == p
*n == p
} else {
false
}
@ -1024,9 +1038,9 @@ impl<'a> ParseTableBuilder<'a> {
match (left, right) {
// Integer precedences can be compared to other integer precedences,
// and to the default precedence, which is zero.
(Precedence::Integer(l), Precedence::Integer(r)) if *l != 0 || *r != 0 => l.cmp(r),
(Precedence::Integer(l), Precedence::None) if *l != 0 => l.cmp(&0),
(Precedence::None, Precedence::Integer(r)) if *r != 0 => 0.cmp(r),
(Precedence::Integer(l), Precedence::Integer(r)) if l != 0 || r != 0 => l.cmp(&r),
(Precedence::Integer(l), Precedence::None) if l != 0 => l.cmp(&0),
(Precedence::None, Precedence::Integer(r)) if r != 0 => 0.cmp(&r),
// Named precedences can be compared to other named precedences.
_ => grammar
@ -1066,7 +1080,7 @@ impl<'a> ParseTableBuilder<'a> {
.iter()
.filter_map(|ParseItemSetEntry { item, .. }| {
let variable_index = item.variable_index as usize;
if item.symbol() == Some(symbol)
if item.symbol(self.syntax_grammar) == Some(symbol)
&& !self.syntax_grammar.variables[variable_index].is_auxiliary()
{
Some(Symbol::non_terminal(variable_index))
@ -1087,12 +1101,17 @@ impl<'a> ParseTableBuilder<'a> {
field_map: BTreeMap::new(),
};
for (i, step) in item.production.steps.iter().enumerate() {
production_info.alias_sequence.push(step.alias.clone());
if let Some(field_name) = &step.field_name {
for (i, step) in item
.production(self.syntax_grammar)
.steps
.iter()
.enumerate()
{
production_info.alias_sequence.push(step.alias());
if let Some(field_name) = step.field() {
production_info
.field_map
.entry(field_name.clone())
.entry(field_name)
.or_default()
.push(FieldLocation {
index: i,
@ -1100,16 +1119,16 @@ impl<'a> ParseTableBuilder<'a> {
});
}
if step.symbol.kind == SymbolType::NonTerminal
&& !self.syntax_grammar.variables[step.symbol.index]
if step.symbol().kind == SymbolType::NonTerminal
&& !self.syntax_grammar.variables[step.symbol().index]
.kind
.is_visible()
{
let info = &self.variable_info[step.symbol.index];
for field_name in info.fields.keys() {
let info = &self.variable_info[step.symbol().index];
for &field_name in info.fields.keys() {
production_info
.field_map
.entry(field_name.clone())
.entry(field_name)
.or_default()
.push(FieldLocation {
index: i,
@ -1123,8 +1142,11 @@ impl<'a> ParseTableBuilder<'a> {
production_info.alias_sequence.pop();
}
if item.production.steps.len() > self.parse_table.max_aliased_production_length {
self.parse_table.max_aliased_production_length = item.production.steps.len();
if item.production(self.syntax_grammar).steps.len()
> self.parse_table.max_aliased_production_length
{
self.parse_table.max_aliased_production_length =
item.production(self.syntax_grammar).steps.len();
}
if let Some(index) = self
@ -1143,16 +1165,20 @@ impl<'a> ParseTableBuilder<'a> {
fn symbol_name(&self, symbol: &Symbol) -> String {
match symbol.kind {
SymbolType::End | SymbolType::EndOfNonTerminalExtra => "EOF".to_string(),
SymbolType::External => self.syntax_grammar.external_tokens[symbol.index]
.name
.clone(),
SymbolType::NonTerminal => self.syntax_grammar.variables[symbol.index].name.clone(),
SymbolType::External => self
.str_pool
.resolve(self.syntax_grammar.external_tokens[symbol.index].name)
.to_string(),
SymbolType::NonTerminal => self
.str_pool
.resolve(self.syntax_grammar.variables[symbol.index].name)
.to_string(),
SymbolType::Terminal => {
let variable = &self.lexical_grammar.variables[symbol.index];
if variable.kind == VariableType::Named {
variable.name.clone()
self.str_pool.resolve(variable.name).to_string()
} else {
format!("'{}'", variable.name)
format!("'{}'", self.str_pool.resolve(variable.name))
}
}
}
@ -1164,6 +1190,7 @@ pub fn build_parse_table<'a>(
lexical_grammar: &'a LexicalGrammar,
item_set_builder: ParseItemSetBuilder<'a>,
variable_info: &'a [VariableInfo],
str_pool: &'a StrPool,
diagnostics: &mut Vec<Diagnostic>,
) -> BuildTableResult<(ParseTable, Vec<ParseStateInfo<'a>>)> {
ParseTableBuilder::new(
@ -1171,6 +1198,7 @@ pub fn build_parse_table<'a>(
lexical_grammar,
item_set_builder,
variable_info,
str_pool,
)
.build(diagnostics)
}

View file

@ -1,12 +1,11 @@
use std::fmt;
use crate::{
grammars::LexicalGrammar,
rules::Symbol,
strpool::StrPool,
tables::{ParseStateId, ParseTable},
};
pub struct CoincidentTokenIndex<'a> {
pub struct CoincidentTokenIndex {
entries: Vec<Vec<ParseStateId>>,
/// Flat bitset for fast [`contains()`](Self::contains) checks. Indexed as `a * n + b`
/// (both `(a,b)` and `(b,a)` bits are set, so no min/max normalization needed).
@ -15,18 +14,16 @@ pub struct CoincidentTokenIndex<'a> {
/// Row `a` spans `[a * row_words .. (a+1) * row_words]`.
/// Bit `b` is set iff tokens `a` and `b` are coincident in some parse state.
pub(crate) row_bits: Vec<u64>,
grammar: &'a LexicalGrammar,
n: usize,
}
impl<'a> CoincidentTokenIndex<'a> {
impl<'a> CoincidentTokenIndex {
#[must_use]
pub fn new(table: &ParseTable, lexical_grammar: &'a LexicalGrammar) -> Self {
let n = lexical_grammar.variables.len();
let row_words = n.div_ceil(64);
let mut result = Self {
n,
grammar: lexical_grammar,
entries: vec![Vec::new(); n * n],
contains_bits: vec![0u64; (n * n).div_ceil(64)],
row_bits: vec![0u64; n * row_words],
@ -85,18 +82,26 @@ impl<'a> CoincidentTokenIndex<'a> {
}
}
impl fmt::Debug for CoincidentTokenIndex<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[expect(dead_code, reason = "Debugging aid")]
pub struct CoincidentTokenIndexDisplay<'a>(CoincidentTokenIndex, &'a LexicalGrammar, StrPool);
impl std::fmt::Debug for CoincidentTokenIndexDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "CoincidentTokenIndex {{")?;
for i in 0..self.n {
for i in 0..self.0.n {
let mut coincident = Vec::new();
for j in 0..self.n {
if self.contains(Symbol::terminal(i), Symbol::terminal(j)) {
coincident.push(&self.grammar.variables[j].name);
for j in 0..self.0.n {
if self.0.contains(Symbol::terminal(i), Symbol::terminal(j)) {
coincident.push(self.2.resolve(self.1.variables[j].name));
}
}
if !coincident.is_empty() {
writeln!(f, " {}: {:?},", self.grammar.variables[i].name, coincident)?;
writeln!(
f,
" {}: {:?},",
self.2.resolve(self.1.variables[i].name),
coincident
)?;
}
}
write!(f, "}}")?;

View file

@ -2,33 +2,33 @@ use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
sync::LazyLock,
};
use rustc_hash::FxHashMap;
use crate::{
grammars::{
InlinedProductionMap, LexicalGrammar, NO_RESERVED_WORDS, Production, ProductionStep,
ReservedWordSetId, SyntaxGrammar,
},
grammars::{LexicalGrammar, ProdRef, ProductionStep, ReservedWordSetId, SyntaxGrammar},
rules::{Associativity, Precedence, Symbol, SymbolType, TokenSet},
strpool::StrPool,
};
static START_PRODUCTION: LazyLock<Production> = LazyLock::new(|| Production {
dynamic_precedence: 0,
steps: vec![ProductionStep {
symbol: Symbol {
index: 0,
kind: SymbolType::NonTerminal,
},
precedence: Precedence::None,
associativity: None,
alias: None,
field_name: None,
reserved_word_set_id: NO_RESERVED_WORDS,
}],
});
const START_STEPS: [ProductionStep; 1] = [ProductionStep {
sym_index: 0,
prec_val: 0,
alias: 0,
field: 0,
reserved: ProductionStep::NO_RESERVED_WORDS,
flags: SymbolType::NonTerminal as u8,
}];
pub const START_PRODUCTION_ID: u32 = u32::MAX;
const fn start_production() -> ProdRef<'static> {
ProdRef {
steps: &START_STEPS,
dynamic_precedence: 0,
}
}
/// Precomputed identity keys for one `(production, dot)` pair.
///
@ -47,40 +47,40 @@ pub struct DotKeys {
/// Identity keys for every `(production, dot)` in a grammar (all grammar productions,
/// every inlined production, and the augmented start production).
pub struct ItemKeyMap {
keys: FxHashMap<*const Production, Box<[DotKeys]>>,
keys: Vec<Box<[DotKeys]>>,
start: Box<[DotKeys]>,
}
impl ItemKeyMap {
pub fn new(grammar: &SyntaxGrammar, inlines: &InlinedProductionMap) -> Self {
let mut prods = Vec::<&Production>::with_capacity(
1 + grammar.variables.len() + inlines.productions.len(),
);
prods.push(&START_PRODUCTION);
for var in &grammar.variables {
prods.extend(var.productions.iter());
}
prods.extend(inlines.productions.iter());
pub fn new(grammar: &SyntaxGrammar, str_pool: &StrPool) -> Self {
let prod = |slot: usize| -> ProdRef {
if slot == 0 {
start_production()
} else {
grammar.production(slot as u32 - 1)
}
};
let slot_count = grammar.productions.len() + 1;
let mut contents: Vec<(u32, u32)> = Vec::with_capacity(prods.len());
for (pi, p) in prods.iter().enumerate() {
for dot in 0..=p.steps.len() {
let mut contents: Vec<(u32, u32)> = Vec::with_capacity(slot_count);
for pi in 0..slot_count {
for dot in 0..=prod(pi).steps.len() {
contents.push((pi as u32, dot as u32));
}
}
let content = |&(pi, dot): &(u32, u32)| ItemContent {
production: prods[pi as usize],
production: prod(pi as usize),
dot: dot as usize,
str_pool,
};
contents.sort_unstable_by(|a, b| content(a).cmp(&content(b)));
let mut slot_keys: Vec<Box<[DotKeys]>> = prods
.iter()
.map(|p| vec![DotKeys::default(); p.steps.len() + 1].into_boxed_slice())
.collect();
let mut slot_keys: Vec<Box<[DotKeys]>> = (0..slot_count)
.map(|pi| vec![DotKeys::default(); prod(pi).steps.len() + 1].into_boxed_slice())
.collect::<Vec<_>>();
// Dense ids in sorted order: equal content shares an id, distinct content gets the next up
let mut cmp_id = 0u32;
let mut prev: Option<(u32, u32)> = None;
let mut cmp_id = 0;
let mut prev = None;
for &(pi, dot) in &contents {
if let Some(p) = prev
&& content(&p) != content(&(pi, dot))
@ -91,33 +91,33 @@ impl ItemKeyMap {
prev = Some((pi, dot));
}
// Refine each cmp class by preceding symbols: read only under
// Refine each cmp class by preceding symbols: read only under
// `has_preceding_inherited_fields`.
let mut sym_classes: FxHashMap<(u32, Vec<Symbol>), u32> = FxHashMap::default();
let mut sym_classes = FxHashMap::default();
for &(pi, dot) in &contents {
let syms: Vec<Symbol> = prods[pi as usize].steps[..dot as usize]
let syms = prod(pi as usize).steps[..dot as usize]
.iter()
.map(|s| s.symbol)
.collect();
.map(|s| s.symbol())
.collect::<Vec<_>>();
let next = sym_classes.len() as u32;
let keys = &mut slot_keys[pi as usize][dot as usize];
keys.eq_with_syms = *sym_classes.entry((keys.cmp, syms)).or_insert(next);
}
let mut slots = slot_keys.into_iter();
let start = slots.next().unwrap();
let keys = prods[1..]
.iter()
.zip(slots)
.map(|(p, ks)| (core::ptr::from_ref::<Production>(p), ks))
.collect();
Self { keys, start }
let start = slot_keys.remove(0);
Self {
keys: slot_keys,
start,
}
}
/// The keys slice (indexed by `dot`) for a production of this grammar.
pub fn keys_for(&self, production: &Production) -> &[DotKeys] {
&self.keys[&core::ptr::from_ref::<Production>(production)]
pub fn keys_for(&self, id: u32) -> &[DotKeys] {
if id == START_PRODUCTION_ID {
&self.start
} else {
&self.keys[id as usize]
}
}
pub fn start_keys(&self) -> &[DotKeys] {
@ -129,28 +129,67 @@ impl ItemKeyMap {
/// `(production, dot)` directly. Ranking the set of all keys by this order makes
/// the dense `cmp` order-preserving. Item ordering only ever compares same-dot pairs,
/// and the dot comparison keeps the order total across dots so ranks are well defined.
#[derive(Eq)]
struct ItemContent<'a> {
production: &'a Production,
production: ProdRef<'a>,
dot: usize,
str_pool: &'a StrPool,
}
impl Eq for ItemContent<'_> {}
impl ItemContent<'_> {
fn prec(&self) -> &Precedence {
fn prec(&self) -> Precedence {
if self.dot > 0 {
&self.production.steps[self.dot - 1].precedence
self.production.steps[self.dot - 1].precedence()
} else {
&Precedence::None
Precedence::None
}
}
fn assoc(&self) -> Option<Associativity> {
if self.dot > 0 {
self.production.steps[self.dot - 1].associativity
self.production.steps[self.dot - 1].associativity()
} else {
None
}
}
fn prec_cmp(&self, a: Precedence, b: Precedence) -> Ordering {
match (a, b) {
(Precedence::None, Precedence::None) => Ordering::Equal,
(Precedence::Integer(a), Precedence::Integer(b)) => a.cmp(&b),
(Precedence::Name(a), Precedence::Name(b)) => {
self.str_pool.resolve(a).cmp(self.str_pool.resolve(b))
}
(Precedence::None, _) | (Precedence::Integer(_), Precedence::Name(_)) => Ordering::Less,
(_, Precedence::None) | (Precedence::Name(_), Precedence::Integer(_)) => {
Ordering::Greater
}
}
}
fn alias_cmp(&self, a: ProductionStep, b: ProductionStep) -> Ordering {
let key = |step: ProductionStep| {
step.alias()
.map(|alias| (self.str_pool.resolve(alias.value), alias.is_named))
};
key(a).cmp(&key(b))
}
fn field_cmp(&self, a: ProductionStep, b: ProductionStep) -> Ordering {
let key = |step: ProductionStep| step.field().map(|field| self.str_pool.resolve(field));
key(a).cmp(&key(b))
}
fn step_cmp(&self, a: ProductionStep, b: ProductionStep) -> Ordering {
a.symbol()
.cmp(&b.symbol())
.then_with(|| self.prec_cmp(a.precedence(), b.precedence()))
.then_with(|| a.associativity().cmp(&b.associativity()))
.then_with(|| self.alias_cmp(a, b))
.then_with(|| self.field_cmp(a, b))
.then_with(|| a.reserved.cmp(&b.reserved))
}
}
impl Ord for ItemContent<'_> {
@ -164,20 +203,17 @@ impl Ord for ItemContent<'_> {
.len()
.cmp(&other.production.steps.len())
})
.then_with(|| self.prec().cmp(other.prec()))
.then_with(|| self.prec_cmp(self.prec(), other.prec()))
.then_with(|| self.assoc().cmp(&other.assoc()))
.then_with(|| self.dot.cmp(&other.dot))
.then_with(|| {
let steps = self.production.steps.iter().zip(&other.production.steps);
for (i, (sa, sb)) in steps.enumerate() {
let steps = self.production.steps.iter().zip(other.production.steps);
for (i, (&sa, &sb)) in steps.enumerate() {
let o = if i < self.dot {
sa.alias
.cmp(&sb.alias)
.then_with(|| sa.field_name.cmp(&sb.field_name))
self.alias_cmp(sa, sb).then_with(|| self.field_cmp(sa, sb))
} else {
sa.cmp(sb)
self.step_cmp(sa, sb)
};
if o != Ordering::Equal {
return o;
}
@ -206,8 +242,8 @@ pub struct ParseItem<'a> {
pub variable_index: u32,
/// The number of symbols that have already been matched.
pub step_index: u32,
/// The production being matched.
pub production: &'a Production,
/// The id of the production being matched.
pub prod_id: u32,
/// The `production`'s identity keys, indexed by `step_index`.
pub keys: &'a [DotKeys],
/// A boolean indicating whether any of the already-matched children were
@ -254,18 +290,21 @@ pub struct ParseItemDisplay<'a>(
pub &'a ParseItem<'a>,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
pub &'a StrPool,
);
pub struct TokenSetDisplay<'a>(
pub &'a TokenSet,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
pub &'a StrPool,
);
pub struct ParseItemSetDisplay<'a>(
pub &'a ParseItemSet<'a>,
pub &'a SyntaxGrammar,
pub &'a LexicalGrammar,
pub &'a StrPool,
);
impl<'a> ParseItem<'a> {
@ -273,38 +312,52 @@ impl<'a> ParseItem<'a> {
pub fn start(key_map: &'a ItemKeyMap) -> Self {
ParseItem {
variable_index: u32::MAX,
production: &START_PRODUCTION,
prod_id: START_PRODUCTION_ID,
keys: key_map.start_keys(),
step_index: 0,
has_preceding_inherited_fields: false,
}
}
/// The production being matched.
#[must_use]
pub fn step(&self) -> Option<&'a ProductionStep> {
self.production.steps.get(self.step_index as usize)
pub fn production<'g>(&self, grammar: &'g SyntaxGrammar) -> ProdRef<'g> {
if self.prod_id == START_PRODUCTION_ID {
start_production()
} else {
grammar.production(self.prod_id)
}
}
#[must_use]
pub fn symbol(&self) -> Option<Symbol> {
self.step().map(|step| step.symbol)
pub fn step(&self, grammar: &SyntaxGrammar) -> Option<ProductionStep> {
self.production(grammar)
.steps
.get(self.step_index as usize)
.copied()
}
#[must_use]
pub fn associativity(&self) -> Option<Associativity> {
self.prev_step().and_then(|step| step.associativity)
pub fn symbol(&self, grammar: &SyntaxGrammar) -> Option<Symbol> {
self.step(grammar).map(ProductionStep::symbol)
}
#[must_use]
pub fn precedence(&self) -> &Precedence {
self.prev_step()
.map_or(&Precedence::None, |step| &step.precedence)
pub fn associativity(&self, grammar: &SyntaxGrammar) -> Option<Associativity> {
self.prev_step(grammar)
.and_then(ProductionStep::associativity)
}
#[must_use]
pub fn prev_step(&self) -> Option<&'a ProductionStep> {
pub fn precedence(&self, grammar: &SyntaxGrammar) -> Precedence {
self.prev_step(grammar)
.map_or(Precedence::None, ProductionStep::precedence)
}
#[must_use]
pub fn prev_step(&self, grammar: &SyntaxGrammar) -> Option<ProductionStep> {
if self.step_index > 0 {
Some(&self.production.steps[self.step_index as usize - 1])
Some(self.production(grammar).steps[self.step_index as usize - 1])
} else {
None
}
@ -312,7 +365,7 @@ impl<'a> ParseItem<'a> {
#[must_use]
pub const fn is_done(&self) -> bool {
self.step_index as usize == self.production.steps.len()
self.step_index as usize + 1 == self.keys.len()
}
#[must_use]
@ -325,7 +378,7 @@ impl<'a> ParseItem<'a> {
pub const fn successor(&self) -> Self {
ParseItem {
variable_index: self.variable_index,
production: self.production,
prod_id: self.prod_id,
keys: self.keys,
step_index: self.step_index + 1,
has_preceding_inherited_fields: self.has_preceding_inherited_fields,
@ -335,13 +388,9 @@ impl<'a> ParseItem<'a> {
/// Create an item identical to this one, but with a different production.
/// This is used when dynamically "inlining" certain symbols in a production.
#[must_use]
pub const fn substitute_production(
&self,
production: &'a Production,
keys: &'a [DotKeys],
) -> Self {
pub const fn substitute_production(&self, prod_id: u32, keys: &'a [DotKeys]) -> Self {
let mut result = *self;
result.production = production;
result.prod_id = prod_id;
result.keys = keys;
result
}
@ -380,6 +429,14 @@ impl<'a> ParseItemSet<'a> {
}
}
pub fn prec_display(prec: Precedence, str_pool: &StrPool) -> String {
match prec {
Precedence::None => "none".to_string(),
Precedence::Integer(i) => i.to_string(),
Precedence::Name(sid) => format!("'{}'", str_pool.resolve(sid)),
}
}
impl fmt::Display for ParseItemDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if self.0.is_augmented() {
@ -388,60 +445,71 @@ impl fmt::Display for ParseItemDisplay<'_> {
write!(
f,
"{} →",
self.1.variables[self.0.variable_index as usize].name
self.3
.resolve(self.1.variables[self.0.variable_index as usize].name)
)?;
}
for (i, step) in self.0.production.steps.iter().enumerate() {
let production = self.0.production(self.1);
for (i, step) in production.steps.iter().enumerate() {
let symbol = step.symbol();
if i == self.0.step_index as usize {
write!(f, "")?;
if !step.precedence.is_none()
|| step.associativity.is_some()
|| step.reserved_word_set_id != ReservedWordSetId::default()
if step.precedence() != Precedence::None
|| step.associativity().is_some()
|| step.reserved != 0
{
write!(f, " (")?;
if !step.precedence.is_none() {
write!(f, " {}", step.precedence)?;
if step.precedence() != Precedence::None {
write!(f, " {}", prec_display(step.precedence(), self.3))?;
}
if let Some(associativity) = step.associativity {
if let Some(associativity) = step.associativity() {
write!(f, " {associativity:?}")?;
}
if step.reserved_word_set_id != ReservedWordSetId::default() {
write!(f, "reserved: {}", step.reserved_word_set_id)?;
if step.reserved != 0 {
write!(f, "reserved: {}", step.reserved)?;
}
write!(f, " )")?;
}
}
write!(f, " ")?;
if step.symbol.is_terminal() {
if let Some(variable) = self.2.variables.get(step.symbol.index) {
write!(f, "{}", variable.name)?;
if symbol.is_terminal() {
if let Some(variable) = self.2.variables.get(symbol.index) {
write!(f, "{}", self.3.resolve(variable.name))?;
} else {
write!(f, "terminal-{}", step.symbol.index)?;
write!(f, "terminal-{}", symbol.index)?;
}
} else if step.symbol.is_external() {
write!(f, "{}", self.1.external_tokens[step.symbol.index].name)?;
} else if symbol.is_external() {
write!(
f,
"{}",
self.3.resolve(self.1.external_tokens[symbol.index].name)
)?;
} else {
write!(f, "{}", self.1.variables[step.symbol.index].name)?;
write!(f, "{}", self.3.resolve(self.1.variables[symbol.index].name))?;
}
if let Some(alias) = &step.alias {
write!(f, "@{}", alias.value)?;
if let Some(alias) = &step.alias() {
write!(f, "@{}", self.3.resolve(alias.value))?;
}
}
if self.0.is_done() {
write!(f, "")?;
if let Some(step) = self.0.production.steps.last() {
if let Some(associativity) = step.associativity {
if step.precedence.is_none() {
if let Some(&step) = production.steps.last() {
if let Some(associativity) = step.associativity() {
if step.precedence() == Precedence::None {
write!(f, " ({associativity:?})")?;
} else {
write!(f, " ({} {associativity:?})", step.precedence)?;
write!(
f,
" ({} {associativity:?})",
prec_display(step.precedence(), self.3)
)?;
}
} else if !step.precedence.is_none() {
write!(f, " ({})", step.precedence)?;
} else if step.precedence() != Precedence::None {
write!(f, " ({})", prec_display(step.precedence(), self.3))?;
}
}
}
@ -486,14 +554,22 @@ impl fmt::Display for TokenSetDisplay<'_> {
if symbol.is_terminal() {
if let Some(variable) = self.2.variables.get(symbol.index) {
write!(f, "{}", display_variable_name(&variable.name))?;
write!(
f,
"{}",
display_variable_name(self.3.resolve(variable.name))
)?;
} else {
write!(f, "terminal-{}", symbol.index)?;
}
} else if symbol.is_external() {
write!(f, "{}", self.1.external_tokens[symbol.index].name)?;
write!(
f,
"{}",
self.3.resolve(self.1.external_tokens[symbol.index].name)
)?;
} else {
write!(f, "{}", self.1.variables[symbol.index].name)?;
write!(f, "{}", self.3.resolve(self.1.variables[symbol.index].name))?;
}
}
write!(f, "]")?;
@ -507,8 +583,8 @@ impl fmt::Display for ParseItemSetDisplay<'_> {
write!(
f,
"{}\t{}",
ParseItemDisplay(&entry.item, self.1, self.2),
TokenSetDisplay(&entry.lookaheads, self.1, self.2),
ParseItemDisplay(&entry.item, self.1, self.2, self.3),
TokenSetDisplay(&entry.lookaheads, self.1, self.2, self.3),
)?;
if entry.following_reserved_word_set != ReservedWordSetId::default() {
write!(

View file

@ -8,6 +8,7 @@ use super::item::{
use crate::{
grammars::{InlinedProductionMap, LexicalGrammar, ReservedWordSetId, SyntaxGrammar},
rules::{Symbol, SymbolType, TokenSet},
strpool::StrPool,
};
#[derive(Clone, Debug, PartialEq, Eq)]
@ -25,7 +26,6 @@ struct FollowSetInfo {
pub struct ParseItemSetBuilder<'a> {
syntax_grammar: &'a SyntaxGrammar,
lexical_grammar: &'a LexicalGrammar,
first_sets: FxHashMap<Symbol, TokenSet>,
reserved_first_sets: FxHashMap<Symbol, ReservedWordSetId>,
last_sets: FxHashMap<Symbol, TokenSet>,
@ -50,7 +50,6 @@ impl<'a> ParseItemSetBuilder<'a> {
) -> Self {
let mut result = Self {
syntax_grammar,
lexical_grammar,
first_sets: FxHashMap::default(),
reserved_first_sets: FxHashMap::default(),
last_sets: FxHashMap::default(),
@ -107,14 +106,16 @@ impl<'a> ParseItemSetBuilder<'a> {
symbols_to_process.clear();
symbols_to_process.push(symbol);
while let Some(sym) = symbols_to_process.pop() {
for production in &syntax_grammar.variables[sym.index].productions {
if let Some(step) = production.steps.first() {
if step.symbol.is_terminal() || step.symbol.is_external() {
first_set.insert(step.symbol);
} else if processed_non_terminals.insert(step.symbol) {
symbols_to_process.push(step.symbol);
for prod_id in syntax_grammar.variable_prod_ids(sym.index) {
if let Some(step) = syntax_grammar.production(prod_id).steps.first() {
let symbol = step.symbol();
if symbol.is_terminal() || symbol.is_external() {
first_set.insert(symbol);
} else if processed_non_terminals.insert(symbol) {
symbols_to_process.push(symbol);
}
*reserved_first_set = (*reserved_first_set).max(step.reserved_word_set_id);
*reserved_first_set =
(*reserved_first_set).max(ReservedWordSetId(step.reserved as usize));
}
}
}
@ -125,12 +126,13 @@ impl<'a> ParseItemSetBuilder<'a> {
symbols_to_process.clear();
symbols_to_process.push(symbol);
while let Some(sym) = symbols_to_process.pop() {
for production in &syntax_grammar.variables[sym.index].productions {
if let Some(step) = production.steps.last() {
if step.symbol.is_terminal() || step.symbol.is_external() {
last_set.insert(step.symbol);
} else if processed_non_terminals.insert(step.symbol) {
symbols_to_process.push(step.symbol);
for prod_id in syntax_grammar.variable_prod_ids(sym.index) {
if let Some(step) = syntax_grammar.production(prod_id).steps.last() {
let symbol = step.symbol();
if symbol.is_terminal() || symbol.is_external() {
last_set.insert(symbol);
} else if processed_non_terminals.insert(symbol) {
symbols_to_process.push(symbol);
}
}
}
@ -191,15 +193,16 @@ impl<'a> ParseItemSetBuilder<'a> {
continue;
}
for production in &syntax_grammar.variables[sym_ix].productions {
for prod_id in syntax_grammar.variable_prod_ids(sym_ix) {
let production = syntax_grammar.production(prod_id);
if let Some(symbol) = production.first_symbol()
&& symbol.is_non_terminal()
{
if let Some(next_step) = production.steps.get(1) {
stack.push((
symbol.index,
&result.first_sets[&next_step.symbol],
result.reserved_first_sets[&next_step.symbol],
&result.first_sets[&next_step.symbol()],
result.reserved_first_sets[&next_step.symbol()],
false,
));
} else {
@ -218,32 +221,26 @@ impl<'a> ParseItemSetBuilder<'a> {
// lookahead info, as *additions* associated with non-terminal `i`.
let additions_for_non_terminal = &mut result.transitive_closure_additions[i];
for (&variable_index, follow_set_info) in &follow_set_info_by_non_terminal {
let variable = &syntax_grammar.variables[variable_index];
let non_terminal = Symbol::non_terminal(variable_index);
let variable_index = variable_index as u32;
if syntax_grammar.variables_to_inline.contains(&non_terminal) {
continue;
}
for production in &variable.productions {
for prod_id in syntax_grammar.variable_prod_ids(variable_index as usize) {
let item = ParseItem {
variable_index,
production,
keys: key_map.keys_for(production),
prod_id,
keys: key_map.keys_for(prod_id),
step_index: 0,
has_preceding_inherited_fields: false,
};
if let Some(inlined_productions) =
inlines.inlined_productions(item.production, item.step_index)
{
for production in inlined_productions {
if let Some(ids) = inlines.inlined_prod_ids(item.prod_id, item.step_index) {
for &id in ids {
find_or_push(
additions_for_non_terminal,
TransitiveClosureAddition {
item: item.substitute_production(
production,
key_map.keys_for(production),
),
item: item.substitute_production(id, key_map.keys_for(id)),
info: follow_set_info.clone(),
},
);
@ -268,18 +265,17 @@ impl<'a> ParseItemSetBuilder<'a> {
pub fn transitive_closure(&self, item_set: &ParseItemSet<'a>) -> ParseItemSet<'a> {
let mut result = ParseItemSet::default();
for entry in &item_set.entries {
if let Some(productions) = self
if let Some(ids) = self
.inlines
.inlined_productions(entry.item.production, entry.item.step_index)
.inlined_prod_ids(entry.item.prod_id, entry.item.step_index)
{
for production in productions {
for &id in ids {
self.add_item(
&mut result,
&ParseItemSetEntry {
item: entry.item.substitute_production(
production,
self.key_map.keys_for(production),
),
item: entry
.item
.substitute_production(id, self.key_map.keys_for(id)),
lookaheads: entry.lookaheads.clone(),
following_reserved_word_set: entry.following_reserved_word_set,
},
@ -309,23 +305,23 @@ impl<'a> ParseItemSetBuilder<'a> {
}
fn add_item(&self, set: &mut ParseItemSet<'a>, entry: &ParseItemSetEntry<'a>) {
if let Some(step) = entry.item.step()
&& step.symbol.is_non_terminal()
if let Some(step) = entry.item.step(self.syntax_grammar)
&& step.symbol().is_non_terminal()
{
let next_step = entry.item.successor().step();
let next_step = entry.item.successor().step(self.syntax_grammar);
// Determine which tokens can follow this non-terminal.
let (following_tokens, following_reserved_tokens) = if let Some(next_step) = next_step {
(
self.first_sets.get(&next_step.symbol).unwrap(),
*self.reserved_first_sets.get(&next_step.symbol).unwrap(),
self.first_sets.get(&next_step.symbol()).unwrap(),
*self.reserved_first_sets.get(&next_step.symbol()).unwrap(),
)
} else {
(&entry.lookaheads, entry.following_reserved_word_set)
};
// Use the pre-computed *additions* to expand the non-terminal.
for addition in &self.transitive_closure_additions[step.symbol.index] {
for addition in &self.transitive_closure_additions[step.symbol().index] {
let entry = set.insert(addition.item);
entry.lookaheads.insert_all(&addition.info.lookaheads);
@ -359,50 +355,65 @@ impl<'a> ParseItemSetBuilder<'a> {
}
}
impl fmt::Debug for ParseItemSetBuilder<'_> {
#[expect(dead_code, reason = "Debugging aid")]
struct ParseItemSetBuilderDisplay<'a>(
pub &'a ParseItemSetBuilder<'a>,
pub &'a LexicalGrammar,
pub &'a StrPool,
);
impl fmt::Debug for ParseItemSetBuilderDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "ParseItemSetBuilder {{")?;
writeln!(f, " first_sets: {{")?;
for (symbol, first_set) in &self.first_sets {
for (symbol, first_set) in &self.0.first_sets {
let name = match symbol.kind {
SymbolType::NonTerminal => &self.syntax_grammar.variables[symbol.index].name,
SymbolType::External => &self.syntax_grammar.external_tokens[symbol.index].name,
SymbolType::Terminal => &self.lexical_grammar.variables[symbol.index].name,
SymbolType::NonTerminal => self
.2
.resolve(self.0.syntax_grammar.variables[symbol.index].name),
SymbolType::External => self
.2
.resolve(self.0.syntax_grammar.external_tokens[symbol.index].name),
SymbolType::Terminal => self.2.resolve(self.1.variables[symbol.index].name),
SymbolType::End | SymbolType::EndOfNonTerminalExtra => "END",
};
writeln!(
f,
" first({name:?}): {}",
TokenSetDisplay(first_set, self.syntax_grammar, self.lexical_grammar)
TokenSetDisplay(first_set, self.0.syntax_grammar, self.1, self.2)
)?;
}
writeln!(f, " }}")?;
writeln!(f, " last_sets: {{")?;
for (symbol, last_set) in &self.last_sets {
for (symbol, last_set) in &self.0.last_sets {
let name = match symbol.kind {
SymbolType::NonTerminal => &self.syntax_grammar.variables[symbol.index].name,
SymbolType::External => &self.syntax_grammar.external_tokens[symbol.index].name,
SymbolType::Terminal => &self.lexical_grammar.variables[symbol.index].name,
SymbolType::NonTerminal => self
.2
.resolve(self.0.syntax_grammar.variables[symbol.index].name),
SymbolType::External => self
.2
.resolve(self.0.syntax_grammar.external_tokens[symbol.index].name),
SymbolType::Terminal => self.2.resolve(self.1.variables[symbol.index].name),
SymbolType::End | SymbolType::EndOfNonTerminalExtra => "END",
};
writeln!(
f,
" last({name:?}): {}",
TokenSetDisplay(last_set, self.syntax_grammar, self.lexical_grammar)
TokenSetDisplay(last_set, self.0.syntax_grammar, self.1, self.2)
)?;
}
writeln!(f, " }}")?;
writeln!(f, " additions: {{")?;
for (i, variable) in self.syntax_grammar.variables.iter().enumerate() {
writeln!(f, " {}: {{", variable.name)?;
for addition in &self.transitive_closure_additions[i] {
for (i, variable) in self.0.syntax_grammar.variables.iter().enumerate() {
writeln!(f, " {}: {{", self.2.resolve(variable.name))?;
for addition in &self.0.transitive_closure_additions[i] {
writeln!(
f,
" {}",
ParseItemDisplay(&addition.item, self.syntax_grammar, self.lexical_grammar)
ParseItemDisplay(&addition.item, self.0.syntax_grammar, self.1, self.2)
)?;
}
writeln!(f, " }},")?;

View file

@ -10,6 +10,7 @@ use crate::{
dedup::split_state_id_groups,
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
rules::{AliasMap, Symbol, SymbolType, TokenSet},
strpool::StrPool,
tables::{GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry},
};
@ -67,6 +68,10 @@ impl SymbolKey {
}
}
#[expect(
clippy::too_many_arguments,
reason = "all parameters are required for parse table minimization"
)]
pub fn minimize_parse_table(
parse_table: &mut ParseTable,
syntax_grammar: &SyntaxGrammar,
@ -74,6 +79,7 @@ pub fn minimize_parse_table(
simple_aliases: &AliasMap,
token_conflict_map: &TokenConflictMap,
keywords: &TokenSet,
str_pool: &StrPool,
optimizations: OptLevel,
) {
let mut minimizer = Minimizer {
@ -83,6 +89,7 @@ pub fn minimize_parse_table(
token_conflict_map,
keywords,
simple_aliases,
str_pool,
};
if optimizations.contains(OptLevel::MergeStates) {
minimizer.merge_compatible_states();
@ -96,19 +103,20 @@ struct Minimizer<'a> {
parse_table: &'a mut ParseTable,
syntax_grammar: &'a SyntaxGrammar,
lexical_grammar: &'a LexicalGrammar,
token_conflict_map: &'a TokenConflictMap<'a>,
token_conflict_map: &'a TokenConflictMap,
keywords: &'a TokenSet,
simple_aliases: &'a AliasMap,
str_pool: &'a StrPool,
}
impl Minimizer<'_> {
fn remove_unit_reductions(&mut self) {
let mut aliased_symbols = FxHashSet::default();
for variable in &self.syntax_grammar.variables {
for production in &variable.productions {
for step in &production.steps {
if step.alias.is_some() {
aliased_symbols.insert(step.symbol);
for i in 0..self.syntax_grammar.variables.len() {
for prod_id in self.syntax_grammar.variable_prod_ids(i) {
for step in self.syntax_grammar.production(prod_id).steps {
if step.alias().is_some() {
aliased_symbols.insert(step.symbol());
}
}
}
@ -444,7 +452,10 @@ impl Minimizer<'_> {
if group1 != group2 {
debug!(
"split states {} {} - successors for {} are split: {s1} {s2}",
state1.id, state2.id, self.syntax_grammar.variables[idx1].name,
state1.id,
state2.id,
self.str_pool
.resolve(self.syntax_grammar.variables[idx1].name),
);
return true;
}
@ -596,13 +607,16 @@ impl Minimizer<'_> {
false
}
fn symbol_name(&self, symbol: &Symbol) -> &String {
fn symbol_name<'a>(&'a self, symbol: &Symbol) -> &'a str {
if symbol.is_non_terminal() {
&self.syntax_grammar.variables[symbol.index].name
self.str_pool
.resolve(self.syntax_grammar.variables[symbol.index].name)
} else if symbol.is_external() {
&self.syntax_grammar.external_tokens[symbol.index].name
self.str_pool
.resolve(self.syntax_grammar.external_tokens[symbol.index].name)
} else {
&self.lexical_grammar.variables[symbol.index].name
self.str_pool
.resolve(self.lexical_grammar.variables[symbol.index].name)
}
}

View file

@ -1,4 +1,4 @@
use std::{cmp::Ordering, fmt};
use std::cmp::Ordering;
use rustc_hash::FxHashSet;
@ -9,6 +9,7 @@ use crate::{
grammars::{LexicalGrammar, SyntaxGrammar},
nfa::{CharacterSet, NfaCursor, NfaTransition},
rules::TokenSet,
strpool::StrPool,
};
bitflags! {
@ -24,13 +25,15 @@ bitflags! {
}
}
pub struct TokenConflictMap<'a> {
pub struct TokenConflictMap {
n: usize,
status_matrix: Vec<TokenConflictStatus>,
#[expect(dead_code, reason = "Debugging aid")]
following_tokens: Vec<TokenSet>,
#[allow(dead_code, reason = "Debugging/test aid")]
starting_chars_by_index: Vec<CharacterSet>,
#[expect(dead_code, reason = "Debugging aid")]
following_chars_by_index: Vec<CharacterSet>,
grammar: &'a LexicalGrammar,
/// Per-row bitsets for fast batch conflict checks.
/// Row `i` spans `[i * row_words .. (i+1) * row_words]`.
/// Bit `j` is set iff [`does_conflict(i, j)`](Self::does_conflict) || [`does_match_prefix(i, j)`](Self::does_match_prefix).
@ -40,7 +43,7 @@ pub struct TokenConflictMap<'a> {
pub(crate) row_words: usize,
}
impl<'a> TokenConflictMap<'a> {
impl TokenConflictMap {
/// Create a token conflict map based on a lexical grammar, which describes the structure
/// of each token, and a `following_token` map, which indicates which tokens may appear
/// immediately after each other token.
@ -48,7 +51,7 @@ impl<'a> TokenConflictMap<'a> {
/// This analyzes the possible kinds of overlap between each pair of tokens and stores
/// them in a matrix.
#[must_use]
pub fn new(grammar: &'a LexicalGrammar, following_tokens: Vec<TokenSet>) -> Self {
pub fn new(grammar: &LexicalGrammar, following_tokens: Vec<TokenSet>) -> Self {
let mut cursor = NfaCursor::new(&grammar.nfa, Vec::new());
let starting_chars = get_starting_chars(&mut cursor, grammar);
let following_chars = get_following_chars(&starting_chars, &following_tokens);
@ -103,13 +106,12 @@ impl<'a> TokenConflictMap<'a> {
}
}
TokenConflictMap {
Self {
n,
status_matrix,
following_tokens,
starting_chars_by_index: starting_chars,
following_chars_by_index: following_chars,
grammar,
conflict_or_prefix_bits,
overlap_either_bits,
row_words,
@ -225,52 +227,59 @@ impl<'a> TokenConflictMap<'a> {
}
}
impl fmt::Debug for TokenConflictMap<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[expect(dead_code, reason = "Debugging aid")]
struct TokenConflictMapDisplay<'a>(
pub &'a TokenConflictMap,
pub &'a LexicalGrammar,
pub &'a StrPool,
);
impl std::fmt::Debug for TokenConflictMapDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "TokenConflictMap {{")?;
let syntax_grammar = SyntaxGrammar::default();
writeln!(f, " following_tokens: {{")?;
for (i, following_tokens) in self.following_tokens.iter().enumerate() {
for (i, following_tokens) in self.0.following_tokens.iter().enumerate() {
writeln!(
f,
" follow({:?}): {},",
self.grammar.variables[i].name,
TokenSetDisplay(following_tokens, &syntax_grammar, self.grammar)
self.1.variables[i].name,
TokenSetDisplay(following_tokens, &syntax_grammar, self.1, self.2)
)?;
}
writeln!(f, " }},")?;
writeln!(f, " starting_characters: {{")?;
for i in 0..self.n {
for i in 0..self.0.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[i].name, self.starting_chars_by_index[i]
self.1.variables[i].name, self.0.starting_chars_by_index[i]
)?;
}
writeln!(f, " }},")?;
writeln!(f, " following_characters: {{")?;
for i in 0..self.n {
for i in 0..self.0.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[i].name, self.following_chars_by_index[i]
self.1.variables[i].name, self.0.following_chars_by_index[i]
)?;
}
writeln!(f, " }},")?;
writeln!(f, " status_matrix: {{")?;
for i in 0..self.n {
writeln!(f, " {:?}: {{", self.grammar.variables[i].name)?;
for j in 0..self.n {
for i in 0..self.0.n {
writeln!(f, " {:?}: {{", self.1.variables[i].name)?;
for j in 0..self.0.n {
writeln!(
f,
" {:?}: {:?},",
self.grammar.variables[j].name,
self.status_matrix[matrix_index(self.n, i, j)]
self.1.variables[j].name,
self.0.status_matrix[matrix_index(self.0.n, i, j)]
)?;
}
writeln!(f, " }},")?;
@ -490,29 +499,36 @@ fn compute_conflict_status(
mod tests {
use super::*;
use crate::{
grammars::{Variable, VariableType},
prepare_grammar::{ExtractedLexicalGrammar, expand_tokens},
rules::{Precedence, Rule, Symbol},
grammars::VariableType,
prepare_grammar::{LexicalToken, expand_tokens},
rules::{Precedence, RulePool, Symbol},
};
#[test]
fn test_starting_characters() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: Vec::new(),
variables: vec![
Variable {
name: "token_0".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("[a-f]1|0x\\d", ""),
},
Variable {
name: "token_1".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("d*ef", ""),
},
],
})
.unwrap();
let mut pool = RulePool::default();
let empty = pool.intern("");
let t0 = {
let v = pool.intern("[a-f]1|0x\\d");
pool.pattern(v, empty)
};
let t1 = {
let v = pool.intern("d*ef");
pool.pattern(v, empty)
};
let vars = vec![
LexicalToken {
name: pool.intern("token_0"),
kind: VariableType::Named,
root: t0,
},
LexicalToken {
name: pool.intern("token_1"),
kind: VariableType::Named,
root: t1,
},
];
let grammar = expand_tokens(&mut pool, &vars, &[]).unwrap();
let token_map = TokenConflictMap::new(&grammar, Vec::new());
@ -528,29 +544,40 @@ mod tests {
#[test]
fn test_token_conflicts() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: Vec::new(),
variables: vec![
Variable {
name: "in".to_string(),
kind: VariableType::Named,
rule: Rule::string("in"),
},
Variable {
name: "identifier".to_string(),
kind: VariableType::Named,
rule: Rule::pattern("\\w+", ""),
},
Variable {
name: "instanceof".to_string(),
kind: VariableType::Named,
rule: Rule::string("instanceof"),
},
],
})
.unwrap();
let mut pool = RulePool::default();
let empty = pool.intern("");
let in_tok = {
let s = pool.intern("in");
pool.string(s)
};
let ident = {
let v = pool.intern("\\w+");
pool.pattern(v, empty)
};
let instanceof = {
let s = pool.intern("instanceof");
pool.string(s)
};
let vars = vec![
LexicalToken {
name: pool.intern("in"),
kind: VariableType::Named,
root: in_tok,
},
LexicalToken {
name: pool.intern("identifier"),
kind: VariableType::Named,
root: ident,
},
LexicalToken {
name: pool.intern("instanceof"),
kind: VariableType::Named,
root: instanceof,
},
];
let grammar = expand_tokens(&mut pool, &vars, &[]).unwrap();
let var = |name| index_of_var(&grammar, name);
let var = |name| index_of_var(&pool, &grammar, name);
let token_map = TokenConflictMap::new(
&grammar,
@ -567,40 +594,51 @@ mod tests {
],
);
// Given the string "in", the `in` token is preferred over the `identifier` token
// Given the string "in", the `in` token is preferrred over the `identifier` token
assert!(token_map.does_match_same_string(var("in"), var("identifier")));
assert!(!token_map.does_match_same_string(var("identifier"), var("in")));
// Depending on what character follows, the string "in" may be treated as part of an
// `identifier` token.
// `identifier` token
assert!(token_map.does_conflict(var("identifier"), var("in")));
// Depending on what character follows, the string "instanceof" may be treated as part of
// an `identifier` token.
// Depending on what character follows, the string "instanceof" may be treated as part
// of an `identifier` token
assert!(token_map.does_conflict(var("identifier"), var("instanceof")));
assert!(token_map.does_conflict(var("instanceof"), var("in")));
}
#[test]
fn test_token_conflicts_with_separators() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: vec![Rule::pattern("\\s", "")],
variables: vec![
Variable {
name: "x".to_string(),
kind: VariableType::Named,
rule: Rule::string("x"),
},
Variable {
name: "newline".to_string(),
kind: VariableType::Named,
rule: Rule::string("\n"),
},
],
})
.unwrap();
let mut pool = RulePool::default();
let empty = pool.intern("");
let sep = {
let v = pool.intern("\\s");
pool.pattern(v, empty)
};
let x = {
let s = pool.intern("x");
pool.string(s)
};
let newline = {
let s = pool.intern("\n");
pool.string(s)
};
let vars = vec![
LexicalToken {
name: pool.intern("x"),
kind: VariableType::Named,
root: x,
},
LexicalToken {
name: pool.intern("newline"),
kind: VariableType::Named,
root: newline,
},
];
let grammar = expand_tokens(&mut pool, &vars, &[sep]).unwrap();
let var = |name| index_of_var(&grammar, name);
let var = |name| index_of_var(&pool, &grammar, name);
let token_map = TokenConflictMap::new(&grammar, vec![TokenSet::new(); 4]);
@ -610,24 +648,36 @@ mod tests {
#[test]
fn test_token_conflicts_with_open_ended_tokens() {
let grammar = expand_tokens(ExtractedLexicalGrammar {
separators: vec![Rule::pattern("\\s", "")],
variables: vec![
Variable {
name: "x".to_string(),
kind: VariableType::Named,
rule: Rule::string("x"),
},
Variable {
name: "anything".to_string(),
kind: VariableType::Named,
rule: Rule::prec(Precedence::Integer(-1), Rule::pattern(".*", "")),
},
],
})
.unwrap();
let mut pool = RulePool::default();
let empty = pool.intern("");
let sep = {
let v = pool.intern("\\s");
pool.pattern(v, empty)
};
let x = {
let s = pool.intern("x");
pool.string(s)
};
let anything = {
let v = pool.intern(".*");
let pat = pool.pattern(v, empty);
pool.prec(Precedence::Integer(-1), pat)
};
let vars = vec![
LexicalToken {
name: pool.intern("x"),
kind: VariableType::Named,
root: x,
},
LexicalToken {
name: pool.intern("anything"),
kind: VariableType::Named,
root: anything,
},
];
let grammar = expand_tokens(&mut pool, &vars, &[sep]).unwrap();
let var = |name| index_of_var(&grammar, name);
let var = |name| index_of_var(&pool, &grammar, name);
let token_map = TokenConflictMap::new(&grammar, vec![TokenSet::new(); 4]);
@ -635,11 +685,11 @@ mod tests {
assert!(!token_map.does_match_shorter_or_longer(var("x"), var("anything")));
}
fn index_of_var(grammar: &LexicalGrammar, name: &str) -> usize {
fn index_of_var(pool: &RulePool, grammar: &LexicalGrammar, name: &str) -> usize {
grammar
.variables
.iter()
.position(|v| v.name == name)
.position(|v| pool.resolve(v.name) == name)
.unwrap()
}
}

View file

@ -10,7 +10,7 @@ use std::{
use bitflags::bitflags;
use node_types::VariableInfo;
use rules::{Alias, Symbol};
use rules::Symbol;
#[cfg(feature = "load")]
use semver::Version;
use serde::{Deserialize, Serialize};
@ -28,11 +28,12 @@ mod prepare_grammar;
mod quickjs;
mod render;
mod rules;
mod strpool;
mod tables;
pub use build_tables::ParseTableBuilderError;
use build_tables::build_tables;
use grammars::{InlinedProductionMap, InputGrammar, LexicalGrammar, SyntaxGrammar};
use grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar};
pub use node_types::{InvalidSupertypeError, SuperTypeCycleError, VariableInfoError};
pub use parse_grammar::ParseGrammarError;
use parse_grammar::parse_grammar;
@ -41,6 +42,10 @@ use prepare_grammar::prepare_grammar;
use render::render_c_code;
pub use render::{ABI_VERSION_MAX, ABI_VERSION_MIN, RenderError};
use crate::{
grammars::InputGrammar, prepare_grammar::PreparedGrammar, rules::Alias, strpool::StrPool,
};
struct JSONOutput {
#[cfg(feature = "load")]
node_types_json: String,
@ -49,6 +54,7 @@ struct JSONOutput {
inlines: InlinedProductionMap,
simple_aliases: BTreeMap<Symbol, Alias>,
variable_info: Vec<VariableInfo>,
str_pool: StrPool,
}
struct GeneratedParser {
@ -377,7 +383,7 @@ where
if !generate_parser {
let node_types_json =
generate_node_types_from_grammar(&input_grammar, diagnostics)?.node_types_json;
generate_node_types_from_grammar(input_grammar, diagnostics)?.node_types_json;
write_file(&src_path.join("node-types.json"), node_types_json)?;
return Ok(());
}
@ -389,7 +395,7 @@ where
c_code,
node_types_json,
} = generate_parser_for_grammar_with_opts(
&input_grammar,
input_grammar,
abi_version,
semantic_version.map(|v| (v.major as u8, v.minor as u8, v.patch as u8)),
report_symbol_name,
@ -415,32 +421,43 @@ pub fn generate_parser_for_grammar(
diagnostics: &mut Vec<Diagnostic>,
) -> GenerateResult<(String, String)> {
let input_grammar = parse_grammar(grammar_json, diagnostics)?;
let name = input_grammar.pool.resolve(input_grammar.name).to_string();
let parser = generate_parser_for_grammar_with_opts(
&input_grammar,
input_grammar,
LANGUAGE_VERSION,
semantic_version,
None,
optimizations,
diagnostics,
)?;
Ok((input_grammar.name, parser.c_code))
Ok((name, parser.c_code))
}
fn generate_node_types_from_grammar(
input_grammar: &InputGrammar,
input_grammar: InputGrammar,
diagnostics: &mut Vec<Diagnostic>,
) -> GenerateResult<JSONOutput> {
let (syntax_grammar, lexical_grammar, inlines, simple_aliases) =
prepare_grammar(input_grammar, diagnostics)?;
let variable_info =
node_types::get_variable_info(&syntax_grammar, &lexical_grammar, &simple_aliases)?;
let PreparedGrammar {
syntax_grammar,
lexical_grammar,
inlines,
default_aliases,
str_pool,
} = prepare_grammar(input_grammar, diagnostics)?;
let variable_info = node_types::get_variable_info(
&syntax_grammar,
&lexical_grammar,
&default_aliases,
&str_pool,
)?;
#[cfg(feature = "load")]
let node_types_json = node_types::generate_node_types_json(
&syntax_grammar,
&lexical_grammar,
&simple_aliases,
&default_aliases,
&variable_info,
&str_pool,
)?;
Ok(JSONOutput {
#[cfg(feature = "load")]
@ -448,19 +465,21 @@ fn generate_node_types_from_grammar(
syntax_grammar,
lexical_grammar,
inlines,
simple_aliases,
simple_aliases: default_aliases,
variable_info,
str_pool,
})
}
fn generate_parser_for_grammar_with_opts(
input_grammar: &InputGrammar,
input_grammar: InputGrammar,
abi_version: usize,
semantic_version: Option<(u8, u8, u8)>,
report_symbol_name: Option<&str>,
optimizations: OptLevel,
diagnostics: &mut Vec<Diagnostic>,
) -> GenerateResult<GeneratedParser> {
let grammar_name = input_grammar.name;
let JSONOutput {
syntax_grammar,
lexical_grammar,
@ -469,6 +488,7 @@ fn generate_parser_for_grammar_with_opts(
variable_info,
#[cfg(feature = "load")]
node_types_json,
str_pool,
} = generate_node_types_from_grammar(input_grammar, diagnostics)?;
let supertype_symbol_map =
node_types::get_supertype_symbol_map(&syntax_grammar, &simple_aliases, &variable_info);
@ -478,16 +498,18 @@ fn generate_parser_for_grammar_with_opts(
&simple_aliases,
&variable_info,
&inlines,
&str_pool,
report_symbol_name,
optimizations,
diagnostics,
)?;
let c_code = render_c_code(
&input_grammar.name,
grammar_name,
tables,
syntax_grammar,
lexical_grammar,
simple_aliases,
str_pool,
abi_version,
semantic_version,
supertype_symbol_map,

View file

@ -1,13 +1,14 @@
use std::{
collections::{BTreeMap, HashMap},
fmt,
};
use rustc_hash::FxHashMap;
use crate::node_types::ChildType;
use crate::{
node_types::ChildType,
rules::{Alias, AliasMap, Associativity, Precedence, RuleId, RulePool, SymbolType},
strpool::StrId,
};
use super::{
nfa::Nfa,
rules::{Alias, Associativity, Precedence, Rule, Symbol, TokenSet},
rules::{Symbol, TokenSet},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@ -18,46 +19,45 @@ pub enum VariableType {
Named,
}
// Input grammar
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Copy)]
pub struct Variable {
pub name: String,
pub kind: VariableType,
pub rule: Rule,
pub name: StrId,
pub root: RuleId,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum PrecedenceEntry {
Name(String),
Symbol(String),
Name(StrId),
Symbol(StrId),
}
#[derive(Debug, Default, PartialEq, Eq)]
/// The parsed grammar.
#[derive(Default)]
pub struct InputGrammar {
pub name: String,
pub pool: RulePool,
pub name: StrId,
pub variables: Vec<Variable>,
pub extra_symbols: Vec<Rule>,
pub expected_conflicts: Vec<Vec<String>>,
pub external_roots: Vec<RuleId>,
pub extra_roots: Vec<RuleId>,
pub reserved_sets: Vec<ReservedWordContext>,
pub supertype_names: Vec<StrId>,
pub conflict_names: Vec<Vec<StrId>>,
pub inline_names: Vec<StrId>,
pub word_name: Option<StrId>,
pub precedence_orderings: Vec<Vec<PrecedenceEntry>>,
pub external_tokens: Vec<Rule>,
pub variables_to_inline: Vec<String>,
pub supertype_symbols: Vec<String>,
pub word_token: Option<String>,
pub reserved_words: Vec<ReservedWordContext<Rule>>,
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ReservedWordContext<T> {
pub name: String,
pub reserved_words: Vec<T>,
#[derive(Clone)]
pub struct ReservedWordContext {
pub name: StrId,
pub roots: Vec<RuleId>,
}
// Extracted lexical grammar
#[derive(Debug, PartialEq, Eq)]
pub struct LexicalVariable {
pub name: String,
pub name: StrId,
pub kind: VariableType,
pub implicit_precedence: i32,
pub start_state: u32,
@ -69,63 +69,200 @@ pub struct LexicalGrammar {
pub variables: Vec<LexicalVariable>,
}
// Extracted syntax grammar
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub struct ProductionStep {
pub symbol: Symbol,
pub precedence: Precedence,
pub associativity: Option<Associativity>,
pub alias: Option<Alias>,
pub field_name: Option<String>,
pub reserved_word_set_id: ReservedWordSetId,
pub sym_index: u32,
pub prec_val: i32,
pub alias: u32,
pub field: u32,
pub reserved: u16,
pub flags: u8,
}
const _: () = assert!(std::mem::size_of::<ProductionStep>() == 20);
#[rustfmt::skip]
impl ProductionStep {
// `FStep::flags` bit layout
// - 0-2 kind (`SymbolType`)
// - 3-4 precedence tag (None 00, Integer 01, Name 10)
// - 5-6 associativity (None 00, Left 01, Right 10)
// - 7 alias `is_named`
const KIND_MASK: u8 = 0b0000_0111;
const PREC_INTEGER: u8 = 0b0000_1000;
const PREC_NAME: u8 = 0b0001_0000;
const PREC_MASK: u8 = 0b0001_1000;
const ASSOC_LEFT: u8 = 0b0010_0000;
const ASSOC_RIGHT: u8 = 0b0100_0000;
const ASSOC_MASK: u8 = 0b0110_0000;
const ALIAS_NAMED: u8 = 0b1000_0000;
/// `FStep::reserved` sentinel, meaning no reserved word set at all. Only the augmented
/// start production carries it, and it must never index the reserved-sets table.
pub const NO_RESERVED_WORDS: u16 = u16::MAX;
}
impl ProductionStep {
pub fn child_type(&self, default_aliases: &BTreeMap<Symbol, Alias>) -> ChildType {
if let Some(alias) = &self.alias {
ChildType::Aliased(alias.clone())
} else if let Some(alias) = default_aliases.get(&self.symbol) {
ChildType::Aliased(alias.clone())
/// Pack a production step's components into its flat `FStep` representation.
#[must_use]
pub fn pack(
symbol: Symbol,
prec: Precedence,
assoc: Option<Associativity>,
alias: Option<Alias>,
field: Option<StrId>,
reserved: u16,
) -> Self {
let mut step = Self {
sym_index: symbol.index as u32,
reserved,
flags: symbol.kind as u8,
..Default::default()
};
step.set_precedence(prec);
step.set_associativity(assoc);
step.set_alias(alias);
step.set_field(field);
step
}
#[must_use]
pub const fn symbol(self) -> Symbol {
Symbol {
kind: match self.flags & Self::KIND_MASK {
0 => SymbolType::External,
1 => SymbolType::End,
2 => SymbolType::EndOfNonTerminalExtra,
3 => SymbolType::Terminal,
_ => SymbolType::NonTerminal,
},
index: self.sym_index as usize,
}
}
#[must_use]
pub const fn precedence(self) -> Precedence {
match self.flags & Self::PREC_MASK {
Self::PREC_INTEGER => Precedence::Integer(self.prec_val),
Self::PREC_NAME => Precedence::Name(StrId::from_raw(self.prec_val as u32)),
_ => Precedence::None,
}
}
pub const fn set_precedence(&mut self, prec: Precedence) {
let (bits, val) = match prec {
Precedence::None => (0, 0),
Precedence::Integer(n) => (Self::PREC_INTEGER, n),
Precedence::Name(sid) => (Self::PREC_NAME, sid.raw() as i32),
};
self.prec_val = val;
self.flags = (self.flags & !Self::PREC_MASK) | bits;
}
#[must_use]
pub const fn associativity(self) -> Option<Associativity> {
match self.flags & Self::ASSOC_MASK {
Self::ASSOC_LEFT => Some(Associativity::Left),
Self::ASSOC_RIGHT => Some(Associativity::Right),
_ => None,
}
}
pub const fn set_associativity(&mut self, assoc: Option<Associativity>) {
let bits = match assoc {
None => 0,
Some(Associativity::Left) => Self::ASSOC_LEFT,
Some(Associativity::Right) => Self::ASSOC_RIGHT,
};
self.flags = (self.flags & !Self::ASSOC_MASK) | bits;
}
#[must_use]
pub fn alias(self) -> Option<Alias> {
(self.alias != 0).then(|| Alias {
value: StrId::from_raw(self.alias),
is_named: self.flags & Self::ALIAS_NAMED != 0,
})
}
pub fn set_alias(&mut self, alias: Option<Alias>) {
self.alias = alias.map_or(0, |a| a.value.raw());
self.set_alias_named(alias.is_some_and(|a| a.is_named));
}
pub const fn set_alias_named(&mut self, named: bool) {
self.flags = (self.flags & !Self::ALIAS_NAMED) | if named { Self::ALIAS_NAMED } else { 0 };
}
#[must_use]
pub fn field(self) -> Option<StrId> {
(self.field != 0).then(|| StrId::from_raw(self.field))
}
pub fn set_field(&mut self, field: Option<StrId>) {
self.field = field.map_or(0, StrId::raw);
}
pub fn child_type(&self, default_aliases: &AliasMap) -> ChildType {
if let Some(alias) = self.alias() {
ChildType::Aliased(alias)
} else if let Some(alias) = default_aliases.get(&self.symbol()) {
ChildType::Aliased(*alias)
} else {
ChildType::Normal(self.symbol)
ChildType::Normal(self.symbol())
}
}
}
// Extracted syntax grammar
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ReservedWordSetId(pub usize);
impl fmt::Display for ReservedWordSetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl std::fmt::Display for ReservedWordSetId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub const NO_RESERVED_WORDS: ReservedWordSetId = ReservedWordSetId(usize::MAX);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
/// A flattened production consisting of a step range and its dynamic precedence
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Production {
pub steps: Vec<ProductionStep>,
pub steps_start: u32,
pub steps_len: u32,
pub dynamic_precedence: i32,
}
#[derive(Default)]
pub struct InlinedProductionMap {
impl Production {
#[must_use]
pub const fn step_range(self) -> std::ops::Range<usize> {
self.steps_start as usize..(self.steps_start + self.steps_len) as usize
}
}
/// Flattened output: one backing store of steps, productions as `[start, len)`
/// ranges into it, and per-variable production ranges.
#[derive(Clone, Default)]
pub struct ProductionStore {
pub steps: Vec<ProductionStep>,
pub productions: Vec<Production>,
pub production_map: HashMap<(*const Production, u32), Vec<usize>>,
pub var_prods: Vec<(u32, u32)>,
}
#[derive(Debug, Default)]
pub struct InlinedProductionMap {
pub map: FxHashMap<(u32, u32), Vec<u32>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SyntaxVariable {
pub name: String,
pub name: StrId,
pub kind: VariableType,
pub productions: Vec<Production>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExternalToken {
pub name: String,
pub name: StrId,
pub kind: VariableType,
pub corresponding_internal_token: Option<Symbol>,
}
@ -141,92 +278,41 @@ pub struct SyntaxGrammar {
pub word_token: Option<Symbol>,
pub precedence_orderings: Vec<Vec<PrecedenceEntry>>,
pub reserved_word_sets: Vec<TokenSet>,
pub steps: Vec<ProductionStep>,
pub productions: Vec<Production>,
pub var_prods: Vec<(u32, u32)>,
}
#[cfg(test)]
impl ProductionStep {
impl SyntaxGrammar {
#[must_use]
pub fn new(symbol: Symbol) -> Self {
Self {
symbol,
precedence: Precedence::None,
associativity: None,
alias: None,
field_name: None,
reserved_word_set_id: ReservedWordSetId::default(),
pub fn production(&self, id: u32) -> ProdRef<'_> {
let p = self.productions[id as usize];
ProdRef {
steps: &self.steps[p.step_range()],
dynamic_precedence: p.dynamic_precedence,
}
}
/// The pooled production ids belonging to a variable
#[must_use]
pub fn with_prec(
mut self,
precedence: Precedence,
associativity: Option<Associativity>,
) -> Self {
self.precedence = precedence;
self.associativity = associativity;
self
}
#[must_use]
pub fn with_alias(mut self, value: &str, is_named: bool) -> Self {
self.alias = Some(Alias {
value: value.to_string(),
is_named,
});
self
}
#[must_use]
pub fn with_field_name(mut self, name: &str) -> Self {
self.field_name = Some(name.to_string());
self
pub fn variable_prod_ids(&self, variable_index: usize) -> std::ops::Range<u32> {
let (start, end) = self.var_prods[variable_index];
start..end
}
}
impl Production {
/// A production in the pooled [`SyntaxGrammar`] storage
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProdRef<'a> {
pub steps: &'a [ProductionStep],
pub dynamic_precedence: i32,
}
impl ProdRef<'_> {
#[must_use]
pub fn first_symbol(&self) -> Option<Symbol> {
self.steps.first().map(|s| s.symbol)
}
}
#[cfg(test)]
impl Variable {
#[must_use]
pub fn named(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Named,
rule,
}
}
#[must_use]
pub fn auxiliary(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Auxiliary,
rule,
}
}
#[must_use]
pub fn hidden(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Hidden,
rule,
}
}
#[must_use]
pub fn anonymous(name: &str, rule: Rule) -> Self {
Self {
name: name.to_string(),
kind: VariableType::Anonymous,
rule,
}
self.steps.first().map(|s| s.symbol())
}
}
@ -278,27 +364,7 @@ impl SyntaxVariable {
impl InlinedProductionMap {
#[must_use]
pub fn inlined_productions<'a>(
&'a self,
production: &Production,
step_index: u32,
) -> Option<impl Iterator<Item = &'a Production> + 'a> {
self.production_map
.get(&(std::ptr::from_ref::<Production>(production), step_index))
.map(|production_indices| {
production_indices
.iter()
.copied()
.map(move |index| &self.productions[index])
})
}
}
impl fmt::Display for PrecedenceEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Name(n) => write!(f, "'{n}'"),
Self::Symbol(s) => write!(f, "$.{s}"),
}
pub fn inlined_prod_ids(&self, prod_id: u32, step_index: u32) -> Option<&[u32]> {
self.map.get(&(prod_id, step_index)).map(Vec::as_slice)
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,9 @@ use thiserror::Error;
use crate::{
Diagnostic,
grammars::{InputGrammar, PrecedenceEntry, ReservedWordContext, Variable, VariableType},
rules::{Precedence, Rule},
grammars::{InputGrammar, PrecedenceEntry, ReservedWordContext, Variable},
rules::{Precedence, Rule, RuleId, RulePool},
strpool::StrId,
};
#[derive(Deserialize)]
@ -133,28 +134,6 @@ impl From<serde_json::Error> for ParseGrammarError {
}
}
/// Check if a rule is referenced by another rule.
///
/// This function is used to determine if a variable is used in a given rule,
/// and `is_other` indicates if the rule is an external, and if it is,
/// to not assume that a named symbol that is equal to itself means it's being referenced.
///
/// For example, if we have an external rule **and** a normal rule both called `foo`,
/// `foo` should not be thought of as directly used unless it's used within another rule.
fn rule_is_referenced(rule: &Rule, target: &str, is_external: bool) -> bool {
match rule {
Rule::NamedSymbol(name) => name == target && !is_external,
Rule::Choice(rules) | Rule::Seq(rules) => {
rules.iter().any(|r| rule_is_referenced(r, target, false))
}
Rule::Metadata { rule, .. } | Rule::Reserved { rule, .. } => {
rule_is_referenced(rule, target, is_external)
}
Rule::Repeat(inner) => rule_is_referenced(inner, target, false),
Rule::Blank | Rule::String(_) | Rule::Pattern(_, _) | Rule::Symbol(_) => false,
}
}
impl InputGrammar {
/// Strip unused rules from the grammar and clean up references to them
/// in the surrounding config. (conflicts, supertypes, inline, extras,
@ -170,97 +149,95 @@ impl InputGrammar {
// Extras count their top-level `NamedSymbol` as a use (so naming
// a rule directly in `extras` keeps it), but externals do not (the
// external entry is the rule itself, not a reference to one).
let used: FxHashSet<String> = {
let by_name: FxHashMap<&str, &Rule> = self
.variables
.iter()
.map(|v| (v.name.as_str(), &v.rule))
.collect();
let mut visited: FxHashSet<&str> = FxHashSet::default();
let mut stack: Vec<&str> = Vec::new();
let used: FxHashSet<StrId> = {
let by_name: FxHashMap<StrId, RuleId> =
self.variables.iter().map(|v| (v.name, v.root)).collect();
let mut visited: FxHashSet<StrId> = FxHashSet::default();
let mut stack: Vec<StrId> = Vec::new();
if let Some(first) = self.variables.first() {
stack.push(first.name.as_str());
stack.push(first.name);
}
if let Some(word) = self.word_token.as_deref() {
if let Some(word) = self.word_name {
stack.push(word);
}
for rule in &self.extra_symbols {
collect_referenced_names(rule, false, &mut stack);
for &root in &self.extra_roots {
self.pool.collect_referenced_ids(root, false, &mut stack);
}
for rule in &self.external_tokens {
collect_referenced_names(rule, true, &mut stack);
for &root in &self.external_roots {
self.pool.collect_referenced_ids(root, true, &mut stack);
}
// Reserved-word entries are uses of the named rule (the entry
// names a token to reserve in some context). Top-level
// `NamedSymbol` counts, same as for extras.
for ctx in &self.reserved_words {
for rule in &ctx.reserved_words {
collect_referenced_names(rule, false, &mut stack);
for set in &self.reserved_sets {
for &root in &set.roots {
self.pool.collect_referenced_ids(root, false, &mut stack);
}
}
while let Some(name) = stack.pop() {
if !visited.insert(name) {
continue;
}
if let Some(rule) = by_name.get(name) {
collect_referenced_names(rule, false, &mut stack);
if let Some(&root) = by_name.get(&name) {
self.pool.collect_referenced_ids(root, false, &mut stack);
}
}
visited.into_iter().map(String::from).collect()
visited
};
for v in &self.variables {
if !used.contains(v.name.as_str()) {
if !used.contains(&v.name) {
continue;
}
if !self
.extra_symbols
.extra_roots
.iter()
.any(|r| rule_is_referenced(r, &v.name, false))
.any(|&r| self.pool.rule_is_referenced(r, v.name, false))
{
continue;
}
let inner_rule = match &v.rule {
Rule::Metadata { rule, .. } => rule.as_ref(),
other => other,
let inner_root = match self.pool.node(v.root) {
Rule::Metadata { rule, .. } => rule,
_ => v.root,
};
let matches_empty = match inner_rule {
Rule::String(s) => s.is_empty(),
Rule::Pattern(value, _) => Regex::new(value).is_ok_and(|reg| reg.is_match("")),
let matches_empty = match self.pool.node(inner_root) {
Rule::String(s) => self.pool.resolve(s).is_empty(),
Rule::Pattern(value, _) => {
Regex::new(self.pool.resolve(value)).is_ok_and(|reg| reg.is_match(""))
}
_ => false,
};
if matches_empty {
diagnostics.push(Diagnostic::EmptyStringMatch(v.name.clone()));
diagnostics.push(Diagnostic::EmptyStringMatch(
self.pool.resolve(v.name).to_string(),
));
}
}
// Drop unused variables and clean up any references to them in the
// surrounding grammar config.
let dropped: Vec<String> = self
// Drop unused variables and clean up references to them in the config
let dropped: Vec<StrId> = self
.variables
.iter()
.filter(|v| !used.contains(v.name.as_str()))
.map(|v| v.name.clone())
.filter(|v| !used.contains(&v.name))
.map(|v| v.name)
.collect();
self.variables.retain(|v| used.contains(v.name.as_str()));
for name in &dropped {
self.expected_conflicts.retain(|r| !r.contains(name));
self.supertype_symbols.retain(|r| r != name);
self.variables_to_inline.retain(|r| r != name);
self.extra_symbols
.retain(|r| !rule_is_referenced(r, name, true));
self.external_tokens
.retain(|r| !rule_is_referenced(r, name, true));
self.precedence_orderings.retain(|r| {
!r.iter()
.any(|e| matches!(e, PrecedenceEntry::Symbol(s) if s == name))
self.variables.retain(|v| used.contains(&v.name));
for &name in &dropped {
self.conflict_names.retain(|c| !c.contains(&name));
self.supertype_names.retain(|&s| s != name);
self.inline_names.retain(|&s| s != name);
let pool = &self.pool;
self.extra_roots
.retain(|&r| !pool.rule_is_referenced(r, name, true));
self.external_roots
.retain(|&r| !pool.rule_is_referenced(r, name, true));
self.precedence_orderings.retain(|o| {
!o.iter()
.any(|e| matches!(e, PrecedenceEntry::Symbol(s) if *s == name))
});
// Prune entries but keep the context: an intentionally-empty
// reserved-word context is a meaningful marker that rule bodies
// may reference by name.
for ctx in &mut self.reserved_words {
ctx.reserved_words
.retain(|r| !rule_is_referenced(r, name, false));
for set in &mut self.reserved_sets {
set.roots
.retain(|&r| !pool.rule_is_referenced(r, name, false));
}
}
@ -268,54 +245,32 @@ impl InputGrammar {
}
}
/// Append every `NamedSymbol` name reachable in `rule` to `out`. If
/// `skip_top_level` is true, a `NamedSymbol` at the root of `rule` is
/// ignored (used for externals entries, which name themselves).
fn collect_referenced_names<'a>(rule: &'a Rule, skip_top_level: bool, out: &mut Vec<&'a str>) {
match rule {
Rule::NamedSymbol(name) => {
if !skip_top_level {
out.push(name.as_str());
}
}
Rule::Choice(rules) | Rule::Seq(rules) => {
for r in rules {
collect_referenced_names(r, false, out);
}
}
Rule::Metadata { rule, .. } | Rule::Reserved { rule, .. } => {
collect_referenced_names(rule, skip_top_level, out);
}
Rule::Repeat(inner) => collect_referenced_names(inner, false, out),
Rule::Blank | Rule::String(_) | Rule::Pattern(_, _) | Rule::Symbol(_) => {}
}
}
pub(crate) fn parse_grammar(
input: &str,
diagnostics: &mut Vec<Diagnostic>,
) -> ParseGrammarResult<InputGrammar> {
let grammar_json = serde_json::from_str::<GrammarJSON>(input)?;
let mut pool = RulePool::default();
let extra_symbols =
let extra_roots =
grammar_json
.extras
.into_iter()
.try_fold(Vec::<Rule>::new(), |mut acc, item| {
let rule = parse_rule(item, false, diagnostics)?;
if let Rule::String(ref value) = rule
&& value.is_empty()
.try_fold(Vec::<RuleId>::new(), |mut acc, item| {
let root = pool.parse_rule(item, false, diagnostics)?;
if let Rule::String(s) = pool.node(root)
&& pool.resolve(s).is_empty()
{
Err(ParseGrammarError::InvalidExtra)?;
}
acc.push(rule);
acc.push(root);
ParseGrammarResult::Ok(acc)
})?;
let external_tokens = grammar_json
let external_roots = grammar_json
.externals
.into_iter()
.map(|e| parse_rule(e, false, diagnostics))
.map(|e| pool.parse_rule(e, false, diagnostics))
.collect::<ParseGrammarResult<Vec<_>>>()?;
let mut precedence_orderings = Vec::with_capacity(grammar_json.precedences.len());
@ -323,8 +278,8 @@ pub(crate) fn parse_grammar(
let mut ordering = Vec::with_capacity(list.len());
for entry in list {
ordering.push(match entry {
RuleJSON::STRING { value } => PrecedenceEntry::Name(value),
RuleJSON::SYMBOL { name } => PrecedenceEntry::Symbol(name),
RuleJSON::STRING { value } => PrecedenceEntry::Name(pool.intern(&value)),
RuleJSON::SYMBOL { name } => PrecedenceEntry::Symbol(pool.intern(&name)),
_ => Err(ParseGrammarError::Unexpected)?,
});
}
@ -335,15 +290,15 @@ pub(crate) fn parse_grammar(
.rules
.into_iter()
.map(|(name, r)| {
let name = pool.intern(&name);
Ok(Variable {
name,
kind: VariableType::Named,
rule: parse_rule(serde_json::from_value(r)?, false, diagnostics)?,
root: pool.parse_rule(serde_json::from_value(r)?, false, diagnostics)?,
})
})
.collect::<ParseGrammarResult<Vec<_>>>()?;
let reserved_words = grammar_json
let reserved_sets = grammar_json
.reserved
.into_iter()
.map(|(name, rule_values)| {
@ -351,126 +306,171 @@ pub(crate) fn parse_grammar(
Err(ParseGrammarError::InvalidReservedWordSet)?
};
let mut reserved_words = Vec::with_capacity(rule_values.len());
let name = pool.intern(&name);
let mut roots = Vec::with_capacity(rule_values.len());
for value in rule_values {
reserved_words.push(parse_rule(
serde_json::from_value(value)?,
false,
diagnostics,
)?);
roots.push(pool.parse_rule(serde_json::from_value(value)?, false, diagnostics)?);
}
Ok(ReservedWordContext {
name,
reserved_words,
})
Ok(ReservedWordContext { name, roots })
})
.collect::<ParseGrammarResult<Vec<_>>>()?;
let supertype_names = grammar_json
.supertypes
.iter()
.map(|s| pool.intern(s))
.collect();
let conflict_names = grammar_json
.conflicts
.iter()
.map(|c| c.iter().map(|n| pool.intern(n)).collect())
.collect();
let inline_names = grammar_json.inline.iter().map(|n| pool.intern(n)).collect();
let word_name = grammar_json.word.as_deref().map(|w| pool.intern(w));
let name = pool.intern(&grammar_json.name);
let grammar = InputGrammar {
name: grammar_json.name,
word_token: grammar_json.word,
expected_conflicts: grammar_json.conflicts,
supertype_symbols: grammar_json.supertypes,
variables_to_inline: grammar_json.inline,
precedence_orderings,
pool,
name,
variables,
extra_symbols,
external_tokens,
reserved_words,
external_roots,
extra_roots,
reserved_sets,
supertype_names,
conflict_names,
inline_names,
word_name,
precedence_orderings,
}
.normalize(diagnostics);
Ok(grammar)
}
fn parse_rule(
json: RuleJSON,
is_token: bool,
diagnostics: &mut Vec<Diagnostic>,
) -> ParseGrammarResult<Rule> {
match json {
RuleJSON::ALIAS {
content,
value,
named,
} => parse_rule(*content, is_token, diagnostics).map(|r| Rule::alias(r, value, named)),
RuleJSON::BLANK => Ok(Rule::Blank),
RuleJSON::STRING { value } => Ok(Rule::String(value)),
RuleJSON::PATTERN { value, flags } => {
let processed_flags = flags.map_or(String::new(), |f| {
f.matches(|c| {
if c == 'i' {
true
} else {
// silently ignore unicode flags
if c != 'u' && c != 'v' {
diagnostics.push(Diagnostic::UnsupportedRegexFlag {
flag: c,
pattern: value.clone(),
});
}
false
}
})
.collect()
});
Ok(Rule::Pattern(value, processed_flags))
}
RuleJSON::SYMBOL { name } => {
if is_token {
Err(ParseGrammarError::UnexpectedRule(name))?
} else {
Ok(Rule::NamedSymbol(name))
impl RulePool {
fn parse_rule(
&mut self,
json: RuleJSON,
is_token: bool,
diagnostics: &mut Vec<Diagnostic>,
) -> ParseGrammarResult<RuleId> {
match json {
RuleJSON::ALIAS {
content,
named,
value,
} => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let value = self.intern(&value);
Ok(self.alias(content, value, named))
}
RuleJSON::BLANK => Ok(self.blank()),
RuleJSON::PATTERN { value, flags } => {
let processed_flags = flags.map_or(String::new(), |f| {
f.matches(|c| {
if c == 'i' {
true
} else {
// silently ignore unicode flags
if c != 'u' && c != 'v' {
diagnostics.push(Diagnostic::UnsupportedRegexFlag {
flag: c,
pattern: value.clone(),
});
}
false
}
})
.collect()
});
let value = self.intern(&value);
let flags = self.intern(&processed_flags);
Ok(self.pattern(value, flags))
}
RuleJSON::SYMBOL { name } => {
if is_token {
Err(ParseGrammarError::UnexpectedRule(name))?
} else {
let sid = self.intern(&name);
Ok(self.named_symbol(sid))
}
}
RuleJSON::CHOICE { members } => {
let members = members
.into_iter()
.map(|m| self.parse_rule(m, is_token, diagnostics))
.collect::<ParseGrammarResult<Vec<_>>>()?;
Ok(self.choice(&members))
}
RuleJSON::SEQ { members } => {
let members = members
.into_iter()
.map(|m| self.parse_rule(m, is_token, diagnostics))
.collect::<ParseGrammarResult<Vec<_>>>()?;
Ok(self.seq(&members))
}
RuleJSON::FIELD { name, content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let name = self.intern(&name);
Ok(self.field(name, content))
}
RuleJSON::REPEAT { content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let repeat = self.repeat(content);
let blank = self.blank();
Ok(self.choice(&[repeat, blank]))
}
RuleJSON::REPEAT1 { content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
Ok(self.repeat(content))
}
RuleJSON::PREC { value, content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let value = value.intern(self);
Ok(self.prec(value, content))
}
RuleJSON::PREC_LEFT { value, content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let value = value.intern(self);
Ok(self.prec_left(value, content))
}
RuleJSON::PREC_RIGHT { value, content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let value = value.intern(self);
Ok(self.prec_right(value, content))
}
RuleJSON::PREC_DYNAMIC { value, content } => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
Ok(self.prec_dynamic(value, content))
}
RuleJSON::RESERVED {
context_name,
content,
} => {
let content = self.parse_rule(*content, is_token, diagnostics)?;
let ctx = self.intern(&context_name);
Ok(self.reserved(content, ctx))
}
RuleJSON::TOKEN { content } => {
let content = self.parse_rule(*content, true, diagnostics)?;
Ok(self.token(content))
}
RuleJSON::IMMEDIATE_TOKEN { content } => {
let content = self.parse_rule(*content, true, diagnostics)?;
Ok(self.immediate_token(content))
}
RuleJSON::STRING { value } => {
let sid = self.intern(&value);
Ok(self.string(sid))
}
}
RuleJSON::CHOICE { members } => members
.into_iter()
.map(|m| parse_rule(m, is_token, diagnostics))
.collect::<ParseGrammarResult<Vec<_>>>()
.map(Rule::choice),
RuleJSON::FIELD { content, name } => {
parse_rule(*content, is_token, diagnostics).map(|r| Rule::field(name, r))
}
RuleJSON::SEQ { members } => members
.into_iter()
.map(|m| parse_rule(m, is_token, diagnostics))
.collect::<ParseGrammarResult<Vec<_>>>()
.map(Rule::seq),
RuleJSON::REPEAT1 { content } => {
parse_rule(*content, is_token, diagnostics).map(Rule::repeat)
}
RuleJSON::REPEAT { content } => parse_rule(*content, is_token, diagnostics)
.map(|m| Rule::choice(vec![Rule::repeat(m), Rule::Blank])),
RuleJSON::PREC { value, content } => {
parse_rule(*content, is_token, diagnostics).map(|r| Rule::prec(value.into(), r))
}
RuleJSON::PREC_LEFT { value, content } => {
parse_rule(*content, is_token, diagnostics).map(|r| Rule::prec_left(value.into(), r))
}
RuleJSON::PREC_RIGHT { value, content } => {
parse_rule(*content, is_token, diagnostics).map(|r| Rule::prec_right(value.into(), r))
}
RuleJSON::PREC_DYNAMIC { value, content } => {
parse_rule(*content, is_token, diagnostics).map(|r| Rule::prec_dynamic(value, r))
}
RuleJSON::RESERVED {
content,
context_name,
} => parse_rule(*content, is_token, diagnostics).map(|r| Rule::Reserved {
rule: Box::new(r),
context_name,
}),
RuleJSON::TOKEN { content } => parse_rule(*content, true, diagnostics).map(Rule::token),
RuleJSON::IMMEDIATE_TOKEN { content } => {
parse_rule(*content, true, diagnostics).map(Rule::immediate_token)
}
}
}
impl From<PrecedenceValueJSON> for Precedence {
fn from(val: PrecedenceValueJSON) -> Self {
match val {
PrecedenceValueJSON::Integer(i) => Self::Integer(i),
PrecedenceValueJSON::Name(i) => Self::Name(i),
impl PrecedenceValueJSON {
fn intern(self, pool: &mut RulePool) -> Precedence {
match self {
Self::Integer(i) => Precedence::Integer(i),
Self::Name(n) => Precedence::Name(pool.intern(&n)),
}
}
}
@ -502,21 +502,31 @@ mod tests {
)
.unwrap();
assert_eq!(grammar.name, "my_lang");
assert_eq!(
grammar.variables,
vec![
Variable {
name: "file".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::NamedSymbol("statement".to_string()))
},
Variable {
name: "statement".to_string(),
kind: VariableType::Named,
rule: Rule::String("foo".to_string())
},
]
);
assert_eq!(grammar.pool.resolve(grammar.name), "my_lang");
let names = grammar
.variables
.iter()
.map(|v| grammar.pool.resolve(v.name))
.collect::<Vec<_>>();
assert_eq!(names, ["file", "statement"]);
// file = repeat(named_symbol "statement")
let file_child = grammar.pool.node(grammar.variables[0].root);
let Rule::Repeat(inner) = file_child else {
panic!("Expected repeat, got {file_child:#?}");
};
let inner_node = grammar.pool.node(inner);
let Rule::NamedSymbol(name) = inner_node else {
panic!("Expected named symbol, got {inner_node:#?}");
};
assert_eq!(grammar.pool.resolve(name), "statement");
// statement = string "foo"
let statement_child = grammar.pool.node(grammar.variables[1].root);
let Rule::String(value) = statement_child else {
panic!("Expected string, got {statement_child:#?}");
};
assert_eq!(grammar.pool.resolve(value), "foo");
}
}

View file

@ -14,6 +14,8 @@ use std::{
pub use expand_tokens::ExpandTokensError;
pub use extract_tokens::ExtractTokensError;
#[cfg(test)] // TODO: Is this defined in the proper place?
pub use extract_tokens::LexicalToken;
pub use flatten_grammar::FlattenGrammarError;
use indexmap::IndexMap;
pub use intern_symbols::InternSymbolsError;
@ -22,6 +24,11 @@ use rustc_hash::{FxHashMap, FxHashSet};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{
grammars::{InputGrammar, PrecedenceEntry, ProductionStore},
strpool::StrPool,
};
pub use self::expand_tokens::expand_tokens;
use self::{
expand_repeats::expand_repeats, extract_default_aliases::extract_default_aliases,
@ -29,51 +36,12 @@ use self::{
intern_symbols::intern_symbols, process_inlines::process_inlines,
};
use super::{
grammars::{
ExternalToken, InlinedProductionMap, InputGrammar, LexicalGrammar, PrecedenceEntry,
SyntaxGrammar, Variable,
},
rules::{AliasMap, Precedence, Rule, Symbol},
Diagnostic,
grammars::{InlinedProductionMap, LexicalGrammar, SyntaxGrammar},
prepare_grammar::flatten_grammar::{FlattenState, assemble_syntax_grammar},
rules::{AliasMap, Precedence, Rule},
strpool::StrId,
};
use crate::{Diagnostic, grammars::ReservedWordContext};
pub struct IntermediateGrammar<T, U> {
variables: Vec<Variable>,
extra_symbols: Vec<T>,
expected_conflicts: Vec<Vec<Symbol>>,
precedence_orderings: Vec<Vec<PrecedenceEntry>>,
external_tokens: Vec<U>,
variables_to_inline: Vec<Symbol>,
supertype_symbols: Vec<Symbol>,
word_token: Option<Symbol>,
reserved_word_sets: Vec<ReservedWordContext<T>>,
}
pub type InternedGrammar = IntermediateGrammar<Rule, Variable>;
pub type ExtractedSyntaxGrammar = IntermediateGrammar<Symbol, ExternalToken>;
#[derive(Debug, PartialEq, Eq)]
pub struct ExtractedLexicalGrammar {
pub variables: Vec<Variable>,
pub separators: Vec<Rule>,
}
impl<T, U> Default for IntermediateGrammar<T, U> {
fn default() -> Self {
Self {
variables: Vec::default(),
extra_symbols: Vec::default(),
expected_conflicts: Vec::default(),
precedence_orderings: Vec::default(),
external_tokens: Vec::default(),
variables_to_inline: Vec::default(),
supertype_symbols: Vec::default(),
word_token: Option::default(),
reserved_word_sets: Vec::default(),
}
}
}
pub type PrepareGrammarResult<T> = Result<T, PrepareGrammarError>;
@ -91,14 +59,14 @@ pub enum PrepareGrammarError {
pub type ValidatePrecedenceResult<T> = Result<T, ValidatePrecedenceError>;
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
#[error(transparent)]
pub enum ValidatePrecedenceError {
Undeclared(#[from] UndeclaredPrecedenceError),
Ordering(#[from] ConflictingPrecedenceOrderingError),
}
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
pub struct IndirectRecursionError(pub Vec<String>);
impl std::fmt::Display for IndirectRecursionError {
@ -114,82 +82,89 @@ impl std::fmt::Display for IndirectRecursionError {
}
}
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
#[error("Undeclared precedence '{}' in rule '{}'", self.precedence, self.rule)]
pub struct UndeclaredPrecedenceError {
pub precedence: String,
pub rule: String,
}
impl std::fmt::Display for UndeclaredPrecedenceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Undeclared precedence '{}' in rule '{}'",
self.precedence, self.rule
)?;
Ok(())
}
}
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
#[error("Conflicting orderings for precedences {} and {}", self.precedence_1, self.precedence_2)]
pub struct ConflictingPrecedenceOrderingError {
pub precedence_1: String,
pub precedence_2: String,
}
impl std::fmt::Display for ConflictingPrecedenceOrderingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Conflicting orderings for precedences {} and {}",
self.precedence_1, self.precedence_2
)?;
Ok(())
}
pub struct PreparedGrammar {
pub syntax_grammar: SyntaxGrammar,
pub lexical_grammar: LexicalGrammar,
pub inlines: InlinedProductionMap,
pub default_aliases: AliasMap,
pub str_pool: StrPool,
}
/// Transform an input grammar into separate components that are ready
/// for parse table construction.
pub fn prepare_grammar(
input_grammar: &InputGrammar,
mut g: InputGrammar,
diagnostics: &mut Vec<Diagnostic>,
) -> PrepareGrammarResult<(
SyntaxGrammar,
LexicalGrammar,
InlinedProductionMap,
AliasMap,
)> {
validate_precedences(input_grammar)?;
validate_indirect_recursion(input_grammar)?;
) -> PrepareGrammarResult<PreparedGrammar> {
validate_precedences(&g)?;
validate_indirect_recursion(&g)?;
let interned_grammar = intern_symbols(input_grammar, diagnostics)?;
let (syntax_grammar, lexical_grammar) = extract_tokens(interned_grammar)?;
let syntax_grammar = expand_repeats(syntax_grammar);
let mut syntax_grammar = flatten_grammar(syntax_grammar)?;
let lexical_grammar = expand_tokens(lexical_grammar)?;
let default_aliases = extract_default_aliases(&mut syntax_grammar, &lexical_grammar);
let inlines = process_inlines(&syntax_grammar, &lexical_grammar)?;
Ok((syntax_grammar, lexical_grammar, inlines, default_aliases))
let interned_meta = intern_symbols(&mut g, diagnostics)?;
let mut ext_meta = extract_tokens(&mut g, &interned_meta)?;
expand_repeats(&mut g, &mut ext_meta);
let mut state = FlattenState::default();
let mut out = ProductionStore::default();
flatten_grammar(&g, &ext_meta, &mut state, &mut out)?;
let lexical_grammar = expand_tokens(
&mut g.pool,
&ext_meta.lexical_variables,
&ext_meta.separator_roots,
)?;
let default_aliases = extract_default_aliases(&g, &ext_meta, &mut out);
let inlines = process_inlines(&g, &ext_meta, &mut out)?;
let (syntax_grammar, str_pool) = assemble_syntax_grammar(g, ext_meta, out);
Ok(PreparedGrammar {
syntax_grammar,
lexical_grammar,
inlines,
default_aliases,
str_pool,
})
}
/// Check for indirect recursion cycles in the grammar that can cause infinite loops while
/// parsing. An indirect recursion cycle occurs when a non-terminal can derive itself through
/// a chain of single-symbol productions (e.g., A -> B, B -> A).
fn validate_indirect_recursion(grammar: &InputGrammar) -> Result<(), IndirectRecursionError> {
let mut epsilon_transitions: IndexMap<&str, BTreeSet<String>> = IndexMap::new();
let mut epsilon_transitions = IndexMap::new();
let mut stack = Vec::new();
for variable in &grammar.variables {
let productions = get_single_symbol_productions(&variable.rule);
// Filter out rules that *directly* reference themselves, as this doesn't
// cause a parsing loop.
let filtered: BTreeSet<String> = productions
.into_iter()
.filter(|s| s != &variable.name)
.collect();
epsilon_transitions.insert(variable.name.as_str(), filtered);
let mut productions = BTreeSet::new();
stack.clear();
stack.push(variable.root);
while let Some(id) = stack.pop() {
match grammar.pool.node(id) {
Rule::NamedSymbol(sid) if sid != variable.name => {
// Rules that *directly* reference themselves don't cause a parsing loop.
productions.insert(sid);
}
Rule::Choice(range) => stack.extend_from_slice(grammar.pool.child_slice(range)),
Rule::Metadata { rule, .. } => stack.push(rule),
_ => {}
}
}
epsilon_transitions.insert(variable.name, productions);
}
for start_symbol in epsilon_transitions.keys() {
for &start_symbol in epsilon_transitions.keys() {
let mut visited = BTreeSet::new();
let mut path = Vec::new();
if let Some((start_idx, end_idx)) =
@ -197,7 +172,7 @@ fn validate_indirect_recursion(grammar: &InputGrammar) -> Result<(), IndirectRec
{
let cycle_symbols = path[start_idx..=end_idx]
.iter()
.map(|s| (*s).to_string())
.map(|&s| grammar.pool.resolve(s).to_string())
.collect();
return Err(IndirectRecursionError(cycle_symbols));
}
@ -206,40 +181,28 @@ fn validate_indirect_recursion(grammar: &InputGrammar) -> Result<(), IndirectRec
Ok(())
}
fn get_single_symbol_productions(rule: &Rule) -> BTreeSet<String> {
match rule {
Rule::NamedSymbol(name) => BTreeSet::from([name.clone()]),
Rule::Choice(choices) => choices
.iter()
.flat_map(get_single_symbol_productions)
.collect(),
Rule::Metadata { rule, .. } => get_single_symbol_productions(rule),
_ => BTreeSet::new(),
}
}
/// Perform a depth-first search to detect cycles in single state transitions.
fn get_cycle<'a>(
current: &'a str,
transitions: &'a IndexMap<&'a str, BTreeSet<String>>,
visited: &mut BTreeSet<&'a str>,
path: &mut Vec<&'a str>,
fn get_cycle(
current: StrId,
transitions: &IndexMap<StrId, BTreeSet<StrId>>,
visited: &mut BTreeSet<StrId>,
path: &mut Vec<StrId>,
) -> Option<(usize, usize)> {
if let Some(first_idx) = path.iter().position(|s| *s == current) {
path.push(current);
return Some((first_idx, path.len() - 1));
}
if visited.contains(current) {
if visited.contains(&current) {
return None;
}
path.push(current);
visited.insert(current);
if let Some(next_symbols) = transitions.get(current) {
if let Some(next_symbols) = transitions.get(&current) {
for next in next_symbols {
if let Some(cycle) = get_cycle(next, transitions, visited, path) {
if let Some(cycle) = get_cycle(*next, transitions, visited, path) {
return Some(cycle);
}
}
@ -253,45 +216,30 @@ fn get_cycle<'a>(
/// within the `precedences` lists, and also that there are no conflicting
/// precedence orderings declared in those lists.
fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()> {
// Check that no rule contains a named precedence that is not present in
// any of the `precedences` lists.
fn validate(
rule_name: &str,
rule: &Rule,
names: &FxHashSet<&String>,
) -> ValidatePrecedenceResult<()> {
match rule {
Rule::Repeat(rule) => validate(rule_name, rule, names),
Rule::Seq(elements) | Rule::Choice(elements) => elements
.iter()
.try_for_each(|e| validate(rule_name, e, names)),
Rule::Metadata { rule, params } => {
if let Precedence::Name(n) = &params.precedence
&& !names.contains(n)
{
Err(UndeclaredPrecedenceError {
precedence: n.clone(),
rule: rule_name.to_string(),
})?;
}
validate(rule_name, rule, names)?;
Ok(())
}
_ => Ok(()),
let display = |e: &PrecedenceEntry| match *e {
PrecedenceEntry::Name(sid) => format!("'{}'", grammar.pool.resolve(sid)),
PrecedenceEntry::Symbol(sid) => format!("$.{}", grammar.pool.resolve(sid)),
};
let cmp = |a: PrecedenceEntry, b: PrecedenceEntry| match (a, b) {
(PrecedenceEntry::Name(a), PrecedenceEntry::Name(b))
| (PrecedenceEntry::Symbol(a), PrecedenceEntry::Symbol(b)) => {
grammar.pool.resolve(a).cmp(grammar.pool.resolve(b))
}
}
(PrecedenceEntry::Name(_), PrecedenceEntry::Symbol(_)) => Ordering::Less,
(PrecedenceEntry::Symbol(_), PrecedenceEntry::Name(_)) => Ordering::Greater,
};
// For any two precedence names `a` and `b`, if `a` comes before `b`
// in some list, then it cannot come *after* `b` in any list.
let mut pairs = FxHashMap::default();
for list in &grammar.precedence_orderings {
for (i, mut entry1) in list.iter().enumerate() {
for mut entry2 in list.iter().skip(i + 1) {
if entry2 == entry1 {
for (i, mut entry1) in list.iter().copied().enumerate() {
for mut entry2 in list.iter().copied().skip(i + 1) {
if entry1 == entry2 {
continue;
}
let mut ordering = Ordering::Greater;
if entry1 > entry2 {
if cmp(entry1, entry2).is_gt() {
ordering = Ordering::Less;
mem::swap(&mut entry1, &mut entry2);
}
@ -302,8 +250,8 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
hash_map::Entry::Occupied(e) => {
if e.get() != &ordering {
Err(ConflictingPrecedenceOrderingError {
precedence_1: entry1.to_string(),
precedence_2: entry2.to_string(),
precedence_1: display(&entry1),
precedence_2: display(&entry2),
})?;
}
}
@ -315,17 +263,37 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
let precedence_names = grammar
.precedence_orderings
.iter()
.flat_map(|l| l.iter())
.filter_map(|p| {
if let PrecedenceEntry::Name(n) = p {
Some(n)
} else {
None
}
.flatten()
.filter_map(|p| match *p {
PrecedenceEntry::Name(sid) => Some(sid),
PrecedenceEntry::Symbol(_) => None,
})
.collect::<FxHashSet<&String>>();
.collect::<FxHashSet<_>>();
let mut stack = Vec::new();
for variable in &grammar.variables {
validate(&variable.name, &variable.rule, &precedence_names)?;
stack.clear();
stack.push(variable.root);
while let Some(id) = stack.pop() {
match grammar.pool.node(id) {
Rule::Repeat(inner) => stack.push(inner),
Rule::Seq(range) | Rule::Choice(range) => {
stack.extend_from_slice(grammar.pool.child_slice(range));
}
Rule::Metadata { params, rule } => {
if let Precedence::Name(sid) = grammar.pool.params(params).precedence
&& !precedence_names.contains(&sid)
{
Err(UndeclaredPrecedenceError {
precedence: grammar.pool.resolve(sid).to_string(),
rule: grammar.pool.resolve(variable.name).to_string(),
})?;
}
stack.push(rule);
}
_ => {}
}
}
}
Ok(())
@ -334,89 +302,188 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
#[cfg(test)]
mod tests {
use super::*;
use crate::grammars::VariableType;
use crate::{
grammars::Variable,
rules::{RuleId, RulePool},
};
#[test]
fn test_validate_precedences_with_undeclared_precedence() {
let grammar = InputGrammar {
precedence_orderings: vec![
vec![
PrecedenceEntry::Name("a".to_string()),
PrecedenceEntry::Name("b".to_string()),
],
vec![
PrecedenceEntry::Name("b".to_string()),
PrecedenceEntry::Name("c".to_string()),
PrecedenceEntry::Name("d".to_string()),
],
fn test_validate_precedences_with_undeclared_precedences() {
let mut pool = RulePool::default();
// v1: seq(prec_left('b', "w"), prec('c', "x"))
let v1 = {
let w = leaf(&mut pool, "w");
let left = prec_left(&mut pool, "b", w);
let x = leaf(&mut pool, "x");
let right = prec(&mut pool, "c", x);
pool.seq(&[left, right])
};
// v2: repeat(choice(prec_left('omg', "y"), prec('c', "z")))
let v2 = {
let y = leaf(&mut pool, "y");
let left = prec_left(&mut pool, "omg", y);
let z = leaf(&mut pool, "z");
let right = prec(&mut pool, "c", z);
let choice = pool.choice(&[left, right]);
pool.repeat(choice)
};
let v1_name = pool.intern("v1");
let v2_name = pool.intern("v2");
let precedence_orderings = vec![
vec![name_entry(&mut pool, "a"), name_entry(&mut pool, "b")],
vec![
name_entry(&mut pool, "b"),
name_entry(&mut pool, "c"),
name_entry(&mut pool, "d"),
],
];
let grammar = InputGrammar {
variables: vec![
Variable {
name: "v1".to_string(),
kind: VariableType::Named,
rule: Rule::Seq(vec![
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
]),
name: v1_name,
root: v1,
},
Variable {
name: "v2".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::Choice(vec![
Rule::prec_left(Precedence::Name("omg".to_string()), Rule::string("y")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
])),
name: v2_name,
root: v2,
},
],
precedence_orderings,
pool,
..Default::default()
};
let result = validate_precedences(&grammar);
assert_eq!(
result.unwrap_err().to_string(),
"Undeclared precedence 'omg' in rule 'v2'",
validate_precedences(&grammar).unwrap_err(),
ValidatePrecedenceError::Undeclared(UndeclaredPrecedenceError {
precedence: "omg".to_string(),
rule: "v2".to_string()
})
);
}
#[test]
fn test_validate_precedences_with_conflicting_order() {
let mut pool = RulePool::default();
let precedence_orderings = vec![
vec![name_entry(&mut pool, "a"), name_entry(&mut pool, "b")],
vec![
name_entry(&mut pool, "b"),
name_entry(&mut pool, "c"),
name_entry(&mut pool, "a"),
],
];
let grammar = InputGrammar {
precedence_orderings: vec![
vec![
PrecedenceEntry::Name("a".to_string()),
PrecedenceEntry::Name("b".to_string()),
],
vec![
PrecedenceEntry::Name("b".to_string()),
PrecedenceEntry::Name("c".to_string()),
PrecedenceEntry::Name("a".to_string()),
],
],
variables: vec![
Variable {
name: "v1".to_string(),
kind: VariableType::Named,
rule: Rule::Seq(vec![
Rule::prec_left(Precedence::Name("b".to_string()), Rule::string("w")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("x")),
]),
},
Variable {
name: "v2".to_string(),
kind: VariableType::Named,
rule: Rule::repeat(Rule::Choice(vec![
Rule::prec_left(Precedence::Name("a".to_string()), Rule::string("y")),
Rule::prec(Precedence::Name("c".to_string()), Rule::string("z")),
])),
},
],
pool,
precedence_orderings,
..Default::default()
};
let result = validate_precedences(&grammar);
assert_eq!(
result.unwrap_err().to_string(),
"Conflicting orderings for precedences 'a' and 'b'",
validate_precedences(&grammar).unwrap_err(),
ValidatePrecedenceError::Ordering(ConflictingPrecedenceOrderingError {
precedence_1: "'a'".to_string(),
precedence_2: "'b'".to_string()
})
);
}
#[test]
fn test_validate_indirect_recursion() {
// a -> b -> a
let case1 = build_grammar(|p| {
let b_ref = named(p, "b");
let x = leaf(p, "x");
let a = p.choice(&[b_ref, x]);
let a_ref = named(p, "a");
let b = p.prec(Precedence::Integer(1), a_ref);
vec![
Variable {
name: p.intern("a"),
root: a,
},
Variable {
name: p.intern("b"),
root: b,
},
]
});
// A direct self-reference is allowed.
let case2 = build_grammar(|p| {
let a = named(p, "a");
vec![Variable {
name: p.intern("a"),
root: a,
}]
});
// b -> c -> d -> b, entered from a non-cycle start rule.
let case3 = build_grammar(|p| {
let x = leaf(p, "x");
let c_ref = named(p, "c");
let d_ref = named(p, "d");
let b_ref = named(p, "b");
vec![
Variable {
name: p.intern("a"),
root: x,
},
Variable {
name: p.intern("b"),
root: c_ref,
},
Variable {
name: p.intern("c"),
root: d_ref,
},
Variable {
name: p.intern("d"),
root: b_ref,
},
]
});
let err1 = IndirectRecursionError(vec!["a".to_string(), "b".to_string(), "a".to_string()]);
let err3 = IndirectRecursionError(vec![
"b".to_string(),
"c".to_string(),
"d".to_string(),
"b".to_string(),
]);
for (g, expected) in &[(case1, Err(err1)), (case2, Ok(())), (case3, Err(err3))] {
assert_eq!(*expected, validate_indirect_recursion(g));
}
}
fn named(pool: &mut RulePool, name: &str) -> RuleId {
let id = pool.intern(name);
pool.named_symbol(id)
}
fn leaf(pool: &mut RulePool, s: &str) -> RuleId {
let id = pool.intern(s);
pool.string(id)
}
fn prec(pool: &mut RulePool, name: &str, content: RuleId) -> RuleId {
let p = Precedence::Name(pool.intern(name));
pool.prec(p, content)
}
fn prec_left(pool: &mut RulePool, name: &str, content: RuleId) -> RuleId {
let p = Precedence::Name(pool.intern(name));
pool.prec_left(p, content)
}
fn name_entry(pool: &mut RulePool, name: &str) -> PrecedenceEntry {
PrecedenceEntry::Name(pool.intern(name))
}
fn build_grammar(build: impl FnOnce(&mut RulePool) -> Vec<Variable>) -> InputGrammar {
let mut pool = RulePool::default();
let variables = build(&mut pool);
InputGrammar {
pool,
variables,
..Default::default()
}
}
}

View file

@ -1,291 +1,459 @@
use std::mem;
use rustc_hash::FxHashMap;
use super::ExtractedSyntaxGrammar;
use crate::{
grammars::{Variable, VariableType},
rules::{Rule, Symbol},
grammars::{InputGrammar, Variable, VariableType},
prepare_grammar::extract_tokens::ExtractedGrammarMeta,
rules::{Rule, RuleId, RulePool, Symbol},
strpool::StrId,
};
#[derive(Default)]
struct Expander {
variable_name: String,
repeat_count_in_variable: usize,
preceding_symbol_count: usize,
auxiliary_variables: Vec<Variable>,
existing_repeats: FxHashMap<Rule, Symbol>,
preceding: usize,
aux: Vec<Variable>,
memo: FxHashMap<u64, Vec<(RuleId, Symbol)>>,
stack: Vec<Task>,
}
#[derive(Copy, Clone)]
enum Task {
Visit(RuleId),
Expand { id: RuleId, content: RuleId },
}
impl Expander {
fn expand_variable(&mut self, index: usize, variable: &mut Variable) -> bool {
self.variable_name.clear();
self.variable_name.push_str(&variable.name);
self.repeat_count_in_variable = 0;
let mut rule = Rule::Blank;
mem::swap(&mut rule, &mut variable.rule);
// In the special case of a hidden variable with a repetition at its top level,
// convert that rule itself into a binary tree structure instead of introducing
// another auxiliary rule.
if let (VariableType::Hidden, Rule::Repeat(repeated_content)) = (variable.kind, &rule) {
let inner_rule = self.expand_rule(repeated_content);
variable.rule = Self::wrap_rule_in_binary_tree(Symbol::non_terminal(index), inner_rule);
variable.kind = VariableType::Auxiliary;
return true;
}
variable.rule = self.expand_rule(&rule);
false
}
fn expand_rule(&mut self, rule: &Rule) -> Rule {
match rule {
// For choices, sequences, and metadata, descend into the child rules,
// replacing any nested repetitions.
Rule::Choice(elements) => Rule::Choice(
elements
.iter()
.map(|element| self.expand_rule(element))
.collect(),
),
Rule::Seq(elements) => Rule::Seq(
elements
.iter()
.map(|element| self.expand_rule(element))
.collect(),
),
Rule::Metadata { rule, params } => Rule::Metadata {
rule: Box::new(self.expand_rule(rule)),
params: params.clone(),
},
// For repetitions, introduce an auxiliary rule that contains the
// repeated content, but can also contain a recursive binary tree structure.
Rule::Repeat(content) => {
let inner_rule = self.expand_rule(content);
if let Some(existing_symbol) = self.existing_repeats.get(&inner_rule) {
return Rule::Symbol(*existing_symbol);
/// Post-order repeat expansion over one root. Children expand first, and `Reserved`
/// nodes are not descended.
fn expand_root(
&mut self,
pool: &mut RulePool,
root: RuleId,
var_name: StrId,
aux_repeat_counter: &mut u32,
) {
self.stack.clear();
self.stack.push(Task::Visit(root));
'walk: while let Some(task) = self.stack.pop() {
match task {
Task::Visit(id) => match pool.node(id) {
Rule::Repeat(content) => {
self.stack.push(Task::Expand { id, content });
self.stack.push(Task::Visit(content));
}
// For choices, sequences, and metadata, descend into the child rules,
// replacing any nested repetitions.
Rule::Seq(range) | Rule::Choice(range) => {
let base = self.stack.len();
for &c in pool.child_slice(range) {
self.stack.push(Task::Visit(c));
}
self.stack[base..].reverse();
}
Rule::Metadata { rule, .. } => self.stack.push(Task::Visit(rule)),
_ => {} // For primitive rules, don't change anything.
},
Task::Expand { id, content } => {
// For repetitions, introduce an auxiliary rule that contains the
// repeated content, but can also contain a recursive binary tree structure.
let hash = pool.subtree_hash(content);
if let Some(candidates) = self.memo.get(&hash) {
for &(node, symbol) in candidates {
if pool.subtree_eq(node, content) {
pool.set_node(id, Rule::from(symbol));
continue 'walk;
}
}
}
*aux_repeat_counter += 1;
let name = format!("{}_repeat{aux_repeat_counter}", pool.resolve(var_name));
let name = pool.intern(&name);
// Aux rules are appended after the original variables, so they occupy
// non-terminal indices `preceding..`.
let symbol = Symbol::non_terminal(self.preceding + self.aux.len());
self.memo.entry(hash).or_default().push((content, symbol));
let root = wrap_in_binary_tree(pool, symbol, content);
self.aux.push(Variable { name, root });
pool.set_node(id, Rule::from(symbol));
}
self.repeat_count_in_variable += 1;
let rule_name = format!(
"{}_repeat{}",
self.variable_name, self.repeat_count_in_variable
);
let repeat_symbol = Symbol::non_terminal(
self.preceding_symbol_count + self.auxiliary_variables.len(),
);
self.existing_repeats
.insert(inner_rule.clone(), repeat_symbol);
self.auxiliary_variables.push(Variable {
name: rule_name,
kind: VariableType::Auxiliary,
rule: Self::wrap_rule_in_binary_tree(repeat_symbol, inner_rule),
});
Rule::Symbol(repeat_symbol)
}
// For primitive rules, don't change anything.
_ => rule.clone(),
}
}
fn wrap_rule_in_binary_tree(symbol: Symbol, rule: Rule) -> Rule {
Rule::choice(vec![
Rule::Seq(vec![Rule::Symbol(symbol), Rule::Symbol(symbol)]),
rule,
])
}
}
pub(super) fn expand_repeats(mut grammar: ExtractedSyntaxGrammar) -> ExtractedSyntaxGrammar {
let mut expander = Expander {
variable_name: String::new(),
repeat_count_in_variable: 0,
preceding_symbol_count: grammar.variables.len(),
auxiliary_variables: Vec::new(),
existing_repeats: FxHashMap::default(),
};
for (i, variable) in grammar.variables.iter_mut().enumerate() {
let expanded_top_level_repetition = expander.expand_variable(i, variable);
// If a hidden variable had a top-level repetition and it was converted to
// a recursive rule, then it can't be inlined.
if expanded_top_level_repetition {
grammar
.variables_to_inline
.retain(|symbol| *symbol != Symbol::non_terminal(i));
/// Build a repeat aux body of `choice(seq(sym, sym), inner)`
fn wrap_in_binary_tree(pool: &mut RulePool, symbol: Symbol, inner: RuleId) -> RuleId {
let s1 = pool.push_node(Rule::from(symbol));
let s2 = pool.push_node(Rule::from(symbol));
let range = pool.push_children(&[s1, s2]);
let seq = pool.push_node(Rule::Seq(range));
let mut elements = vec![seq];
let mut stack = vec![inner];
while let Some(id) = stack.pop() {
if let Rule::Choice(range) = pool.node(id) {
let base = stack.len();
stack.extend_from_slice(pool.child_slice(range));
stack[base..].reverse();
} else if !elements.iter().any(|&e| pool.subtree_eq(e, id)) {
elements.push(id);
}
}
if elements.len() == 1 {
elements[0]
} else {
let range = pool.push_children(&elements);
pool.push_node(Rule::Choice(range))
}
}
grammar.variables.extend(expander.auxiliary_variables);
grammar
pub(super) fn expand_repeats(grammar: &mut InputGrammar, meta: &mut ExtractedGrammarMeta) {
let mut expander = Expander {
preceding: grammar.variables.len(),
..Default::default()
};
for i in 0..grammar.variables.len() {
let Variable { name, root } = grammar.variables[i];
let mut aux_repeat_count = 0;
// A hidden variable with a top level repetition becomes its own recursive binary
// tree instead of gaining an auxiliary rule, and can no longer be inlined.
if meta.kinds[i] == VariableType::Hidden
&& let Rule::Repeat(content) = grammar.pool.node(root)
{
expander.expand_root(&mut grammar.pool, content, name, &mut aux_repeat_count);
grammar.variables[i].root =
wrap_in_binary_tree(&mut grammar.pool, Symbol::non_terminal(i), content);
meta.kinds[i] = VariableType::Auxiliary;
meta.inline.retain(|s| *s != Symbol::non_terminal(i));
continue;
}
expander.expand_root(&mut grammar.pool, root, name, &mut aux_repeat_count);
}
for var in expander.aux {
grammar.variables.push(var);
meta.kinds.push(VariableType::Auxiliary);
}
}
#[cfg(test)]
mod tests {
use crate::rules::SymbolType;
use super::*;
#[test]
fn test_basic_repeat_expansion() {
// Repeats nested inside of sequences and choices are expanded.
let grammar = expand_repeats(build_grammar(vec![Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::choice(vec![
Rule::repeat(Rule::terminal(11)),
Rule::repeat(Rule::terminal(12)),
]),
Rule::terminal(13),
]),
)]));
let mut pool = RulePool::default();
let r0 = {
let a = term(&mut pool, 10);
let ch = {
let r1 = {
let t = term(&mut pool, 11);
pool.repeat(t)
};
let r2 = {
let t = term(&mut pool, 12);
pool.repeat(t)
};
pool.choice(&[r1, r2])
};
let b = term(&mut pool, 13);
pool.seq(&[a, ch, b])
};
let name = pool.intern("rule0");
let (mut grammar, meta) = expand(
pool,
vec![Variable { name, root: r0 }],
vec![VariableType::Named],
);
let names = grammar
.variables
.iter()
.map(|v| grammar.pool.resolve(v.name))
.collect::<Vec<_>>();
assert_eq!(names, ["rule0", "rule0_repeat1", "rule0_repeat2"]);
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::choice(vec![Rule::non_terminal(1), Rule::non_terminal(2),]),
Rule::terminal(13),
])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1),]),
Rule::terminal(11),
])
),
Variable::auxiliary(
"rule0_repeat2",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::terminal(12),
])
),
meta.kinds,
[
VariableType::Named,
VariableType::Auxiliary,
VariableType::Auxiliary
]
);
// rule0: seq(terminal(10), choice(non_terminal(1), non_terminal(2)), terminal(13))
let e0 = {
let pool = &mut grammar.pool;
let (a, b) = (term(pool, 10), term(pool, 13));
let ch = {
let (x, y) = (non_term(pool, 1), non_term(pool, 2));
pool.choice(&[x, y])
};
pool.seq(&[a, ch, b])
};
assert!(grammar.pool.subtree_eq(grammar.variables[0].root, e0));
// rule0_repeat1: choice(seq(nt1, nt1), terminal(11))
let e1 = {
let p = &mut grammar.pool;
let sq = {
let (x, y) = (non_term(p, 1), non_term(p, 1));
p.seq(&[x, y])
};
let t = term(p, 11);
p.choice(&[sq, t])
};
assert!(grammar.pool.subtree_eq(grammar.variables[1].root, e1));
// rule0_repeat2: choice(seq(nt2, nt2), terminal(12))
let e2 = {
let p = &mut grammar.pool;
let sq = {
let (x, y) = (non_term(p, 2), non_term(p, 2));
p.seq(&[x, y])
};
let t = term(p, 12);
p.choice(&[sq, t])
};
assert!(grammar.pool.subtree_eq(grammar.variables[2].root, e2));
}
#[test]
fn test_repeat_deduplication() {
// Terminal 4 appears inside of a repeat in three different places.
let grammar = expand_repeats(build_grammar(vec![
Variable::named(
"rule0",
Rule::choice(vec![
Rule::seq(vec![Rule::terminal(1), Rule::repeat(Rule::terminal(4))]),
Rule::seq(vec![Rule::terminal(2), Rule::repeat(Rule::terminal(4))]),
]),
),
Variable::named(
"rule1",
Rule::seq(vec![Rule::terminal(3), Rule::repeat(Rule::terminal(4))]),
),
]));
// repeat(terminal(4)) appears in 3 places. Only one aux rule is made
let mut pool = RulePool::default();
let r0 = {
let s1 = {
let t = term(&mut pool, 1);
let r = {
let x = term(&mut pool, 4);
pool.repeat(x)
};
pool.seq(&[t, r])
};
let s2 = {
let t = term(&mut pool, 2);
let r = {
let x = term(&mut pool, 4);
pool.repeat(x)
};
pool.seq(&[t, r])
};
pool.choice(&[s1, s2])
};
let r1 = {
let t = term(&mut pool, 3);
let r = {
let x = term(&mut pool, 4);
pool.repeat(x)
};
pool.seq(&[t, r])
};
let (n0, n1) = (pool.intern("rule0"), pool.intern("rule1"));
let variables = vec![
Variable { name: n0, root: r0 },
Variable { name: n1, root: r1 },
];
let (mut g, meta) = expand(pool, variables, vec![VariableType::Named; 2]);
// Only one auxiliary rule is created for repeating terminal 4.
let names = g
.variables
.iter()
.map(|v| g.pool.resolve(v.name))
.collect::<Vec<_>>();
assert_eq!(names, ["rule0", "rule1", "rule0_repeat1"]);
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::choice(vec![
Rule::seq(vec![Rule::terminal(1), Rule::non_terminal(2)]),
Rule::seq(vec![Rule::terminal(2), Rule::non_terminal(2)]),
])
),
Variable::named(
"rule1",
Rule::seq(vec![Rule::terminal(3), Rule::non_terminal(2),])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::terminal(4),
])
)
meta.kinds,
[
VariableType::Named,
VariableType::Named,
VariableType::Auxiliary
]
);
// rule0: choice(seq(t1, nt2), sseq(t2, nt2))
let e0 = {
let p = &mut g.pool;
let s1 = {
let (t, n) = (term(p, 1), non_term(p, 2));
p.seq(&[t, n])
};
let s2 = {
let (t, n) = (term(p, 2), non_term(p, 2));
p.seq(&[t, n])
};
p.choice(&[s1, s2])
};
assert!(g.pool.subtree_eq(g.variables[0].root, e0));
// rule1: seq(t3, nt2)
let e1 = {
let p = &mut g.pool;
let (t, n) = (term(p, 3), non_term(p, 2));
p.seq(&[t, n])
};
assert!(g.pool.subtree_eq(g.variables[1].root, e1));
// rule0_repeat1: choice(seq(nt2, nt2), terminal(4))
let e2 = {
let p = &mut g.pool;
let sq = {
let (x, y) = (non_term(p, 2), non_term(p, 2));
p.seq(&[x, y])
};
let t = term(p, 4);
p.choice(&[sq, t])
};
assert!(g.pool.subtree_eq(g.variables[2].root, e2));
}
#[test]
fn test_expansion_of_nested_repeats() {
let grammar = expand_repeats(build_grammar(vec![Variable::named(
"rule0",
Rule::seq(vec![
Rule::terminal(10),
Rule::repeat(Rule::seq(vec![
Rule::terminal(11),
Rule::repeat(Rule::terminal(12)),
])),
]),
)]));
// Nested repeats expand inside out. The inner one becomes `rule0_repeat1` (nt1),
// and then outer one (now referencing it) becomes rule0_repeat2
let mut pool = RulePool::default();
let r0 = {
let t10 = term(&mut pool, 10);
let outer = {
let t11 = term(&mut pool, 11);
let inner = {
let t = term(&mut pool, 12);
pool.repeat(t)
};
let content = pool.seq(&[t11, inner]);
pool.repeat(content)
};
pool.seq(&[t10, outer])
};
let name = pool.intern("rule0");
let (mut g, meta) = expand(
pool,
vec![Variable { name, root: r0 }],
vec![VariableType::Named],
);
let names = g
.variables
.iter()
.map(|v| g.pool.resolve(v.name))
.collect::<Vec<_>>();
assert_eq!(names, ["rule0", "rule0_repeat1", "rule0_repeat2"]);
assert_eq!(
grammar.variables,
vec![
Variable::named(
"rule0",
Rule::seq(vec![Rule::terminal(10), Rule::non_terminal(2),])
),
Variable::auxiliary(
"rule0_repeat1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1),]),
Rule::terminal(12),
])
),
Variable::auxiliary(
"rule0_repeat2",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2),]),
Rule::seq(vec![Rule::terminal(11), Rule::non_terminal(1),]),
])
),
meta.kinds,
[
VariableType::Named,
VariableType::Auxiliary,
VariableType::Auxiliary
]
);
// rule0: seq(terminal(10), non_terminal(2))
let e0 = {
let p = &mut g.pool;
let (t, n) = (term(p, 10), non_term(p, 2));
p.seq(&[t, n])
};
assert!(g.pool.subtree_eq(g.variables[0].root, e0));
// rule0_repeat2 (outer): choice(seq(nt2, nt2), seq(terminal(11), nt1))
let e2 = {
let p = &mut g.pool;
let sq1 = {
let (x, y) = (non_term(p, 2), non_term(p, 2));
p.seq(&[x, y])
};
let sq2 = {
let (t, n) = (term(p, 11), non_term(p, 1));
p.seq(&[t, n])
};
p.choice(&[sq1, sq2])
};
assert!(g.pool.subtree_eq(g.variables[2].root, e2));
}
#[test]
fn test_expansion_of_repeats_at_top_of_hidden_rules() {
let grammar = expand_repeats(build_grammar(vec![
Variable::named("rule0", Rule::non_terminal(1)),
Variable::hidden(
"_rule1",
Rule::repeat(Rule::choice(vec![Rule::terminal(11), Rule::terminal(12)])),
),
]));
assert_eq!(
grammar.variables,
vec![
Variable::named("rule0", Rule::non_terminal(1),),
Variable::auxiliary(
"_rule1",
Rule::choice(vec![
Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(1)]),
Rule::terminal(11),
Rule::terminal(12),
]),
),
]
// A hidden rule whos whole body is a repeat becomes its own recursive binary
// tree (using its own symbol) instead of gaining a separate aux rule, and is
// reclassified as Axuiliary.
let mut pool = RulePool::default();
let r0 = non_term(&mut pool, 1);
let r1 = {
let ch = {
let (a, b) = (term(&mut pool, 11), term(&mut pool, 12));
pool.choice(&[a, b])
};
pool.repeat(ch)
};
let (n0, n1) = (pool.intern("rule0"), pool.intern("_rule1"));
let variables = vec![
Variable { name: n0, root: r0 },
Variable { name: n1, root: r1 },
];
let (mut g, meta) = expand(
pool,
variables,
vec![VariableType::Named, VariableType::Hidden],
);
// No separate aux rule: _rule1 absorbed during recursion
let names = g
.variables
.iter()
.map(|v| g.pool.resolve(v.name))
.collect::<Vec<_>>();
assert_eq!(names, ["rule0", "_rule1"]);
assert_eq!(meta.kinds, [VariableType::Named, VariableType::Auxiliary]);
// rule0: non_terminal(1) (unchanged)
assert_eq!(
g.pool.node(g.variables[0].root),
Rule::Sym {
kind: SymbolType::NonTerminal,
index: 1
}
);
// _rule1: choice(seq(nt1, nt1), terminal(11), terminal(12)) (inner choice flattened)
let e1 = {
let p = &mut g.pool;
let sq = {
let (x, y) = (non_term(p, 1), non_term(p, 1));
p.seq(&[x, y])
};
let (a, b) = (term(p, 11), term(p, 12));
p.choice(&[sq, a, b])
};
assert!(g.pool.subtree_eq(g.variables[1].root, e1));
}
fn build_grammar(variables: Vec<Variable>) -> ExtractedSyntaxGrammar {
ExtractedSyntaxGrammar {
fn term(p: &mut RulePool, i: u32) -> RuleId {
p.push_node(Rule::Sym {
kind: SymbolType::Terminal,
index: i,
})
}
fn non_term(p: &mut RulePool, i: u32) -> RuleId {
p.push_node(Rule::Sym {
kind: SymbolType::NonTerminal,
index: i,
})
}
fn expand(
pool: RulePool,
variables: Vec<Variable>,
kinds: Vec<VariableType>,
) -> (InputGrammar, ExtractedGrammarMeta) {
let mut grammar = InputGrammar {
pool,
variables,
..Default::default()
}
};
let mut meta = ExtractedGrammarMeta {
kinds,
..Default::default()
};
expand_repeats(&mut grammar, &mut meta);
(grammar, meta)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,8 @@
use std::collections::BTreeMap;
use crate::{
grammars::{LexicalGrammar, SyntaxGrammar},
grammars::{InputGrammar, ProductionStore},
prepare_grammar::extract_tokens::ExtractedGrammarMeta,
rules::{Alias, AliasMap, Symbol, SymbolType},
};
@ -19,52 +22,50 @@ struct SymbolStatus {
// ensures that the children of an `ERROR` node have symbols that are consistent with the way that
// they would appear in a valid syntax tree.
pub(super) fn extract_default_aliases(
syntax_grammar: &mut SyntaxGrammar,
lexical_grammar: &LexicalGrammar,
g: &InputGrammar,
meta: &ExtractedGrammarMeta,
out: &mut ProductionStore,
) -> AliasMap {
let mut terminal_status_list = vec![SymbolStatus::default(); lexical_grammar.variables.len()];
let mut non_terminal_status_list =
vec![SymbolStatus::default(); syntax_grammar.variables.len()];
let mut external_status_list =
vec![SymbolStatus::default(); syntax_grammar.external_tokens.len()];
let mut terminal_status_list = vec![SymbolStatus::default(); meta.lexical_variables.len()];
let mut non_terminal_status_list = vec![SymbolStatus::default(); g.variables.len()];
let mut external_status_list = vec![SymbolStatus::default(); meta.external_tokens.len()];
// For each grammar symbol, find all of the aliases under which the symbol appears,
// and determine whether or not the symbol ever appears *unaliased*.
for variable in &syntax_grammar.variables {
for production in &variable.productions {
for step in &production.steps {
let status = match step.symbol.kind {
SymbolType::External => &mut external_status_list[step.symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[step.symbol.index],
SymbolType::Terminal => &mut terminal_status_list[step.symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
// Default aliases don't work for inlined variables.
if syntax_grammar.variables_to_inline.contains(&step.symbol) {
continue;
for prod in &out.productions {
for step in &out.steps[prod.step_range()] {
let symbol = step.symbol();
let status = match symbol.kind {
SymbolType::External => &mut external_status_list[symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[symbol.index],
SymbolType::Terminal => &mut terminal_status_list[symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
if let Some(alias) = &step.alias {
if let Some(count_for_alias) = status
.aliases
.iter_mut()
.find_map(|(a, count)| if a == alias { Some(count) } else { None })
{
*count_for_alias += 1;
} else {
status.aliases.push((alias.clone(), 1));
}
// Default aliases don't work for inlined variables.
if meta.inline.contains(&symbol) {
continue;
}
if let Some(alias) = step.alias() {
if let Some(count_for_alias) = status
.aliases
.iter_mut()
.find_map(|(a, count)| (*a == alias).then_some(count))
{
*count_for_alias += 1;
} else {
status.appears_unaliased = true;
status.aliases.push((alias, 1));
}
} else {
status.appears_unaliased = true;
}
}
}
for symbol in &syntax_grammar.extra_symbols {
for symbol in &meta.extra_symbols {
let status = match symbol.kind {
SymbolType::External => &mut external_status_list[symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[symbol.index],
@ -74,27 +75,27 @@ pub(super) fn extract_default_aliases(
status.appears_unaliased = true;
}
let symbols_with_statuses = (terminal_status_list
let symbols_with_statuses = terminal_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::terminal(i), status)))
.chain(
non_terminal_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::non_terminal(i), status)),
)
.chain(
external_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::external(i), status)),
);
.map(|(i, status)| (Symbol::terminal(i), status))
.chain(
non_terminal_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::non_terminal(i), status)),
)
.chain(
external_status_list
.iter_mut()
.enumerate()
.map(|(i, status)| (Symbol::external(i), status)),
);
// For each symbol that always appears aliased, find the alias that occurs most often,
// and designate that alias as the symbol's "default alias". Store all of these
// default aliases in a map that will be returned.
let mut result = AliasMap::new();
let mut result = BTreeMap::new();
for (symbol, status) in symbols_with_statuses {
if status.appears_unaliased {
status.aliases.clear();
@ -103,10 +104,10 @@ pub(super) fn extract_default_aliases(
.iter()
.enumerate()
.max_by_key(|(i, (_, count))| (count, -(*i as i64)))
.map(|(_, entry)| entry.clone())
.map(|(_, entry)| *entry)
{
status.aliases.clear();
status.aliases.push(default_entry.clone());
status.aliases.push(default_entry);
result.insert(symbol, default_entry.0);
}
}
@ -114,30 +115,31 @@ pub(super) fn extract_default_aliases(
// Wherever a symbol is aliased as its default alias, remove the usage of the alias,
// because it will now be redundant.
let mut alias_positions_to_clear = Vec::new();
for variable in &mut syntax_grammar.variables {
for &(p_start, p_end) in &out.var_prods {
alias_positions_to_clear.clear();
for (i, production) in variable.productions.iter().enumerate() {
for (j, step) in production.steps.iter().enumerate() {
let status = match step.symbol.kind {
SymbolType::External => &mut external_status_list[step.symbol.index],
SymbolType::NonTerminal => &mut non_terminal_status_list[step.symbol.index],
SymbolType::Terminal => &mut terminal_status_list[step.symbol.index],
let productions = &out.productions[p_start as usize..p_end as usize];
for (i, prod) in productions.iter().enumerate() {
for (j, step) in out.steps[prod.step_range()].iter().enumerate() {
let symbol = step.symbol();
let status = match symbol.kind {
SymbolType::External => &external_status_list[symbol.index],
SymbolType::Terminal => &terminal_status_list[symbol.index],
SymbolType::NonTerminal => &non_terminal_status_list[symbol.index],
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
panic!("Unexpected end token")
}
};
// If this step is aliased as the symbol's default alias, then remove that alias.
if step.alias.is_some()
&& step.alias.as_ref() == status.aliases.first().map(|t| &t.0)
{
if step.alias().is_some() && step.alias() == status.aliases.first().map(|t| t.0) {
let mut other_productions_must_use_this_alias_at_this_index = false;
for (other_i, other_production) in variable.productions.iter().enumerate() {
for (other_i, other_prod) in productions.iter().enumerate() {
let other_steps = &out.steps[other_prod.step_range()];
if other_i != i
&& other_production.steps.len() > j
&& other_production.steps[j].alias == step.alias
&& result.get(&other_production.steps[j].symbol) != step.alias.as_ref()
&& other_steps.len() > j
&& other_steps[j].alias() == step.alias()
&& result.get(&other_steps[j].symbol()) != step.alias().as_ref()
{
other_productions_must_use_this_alias_at_this_index = true;
break;
@ -145,14 +147,14 @@ pub(super) fn extract_default_aliases(
}
if !other_productions_must_use_this_alias_at_this_index {
alias_positions_to_clear.push((i, j));
alias_positions_to_clear.push(prod.steps_start as usize + j);
}
}
}
}
for (production_index, step_index) in &alias_positions_to_clear {
variable.productions[*production_index].steps[*step_index].alias = None;
for &step_index in &alias_positions_to_clear {
out.steps[step_index].set_alias(None);
}
}
@ -163,140 +165,109 @@ pub(super) fn extract_default_aliases(
mod tests {
use super::*;
use crate::{
grammars::{LexicalVariable, Production, ProductionStep, SyntaxVariable, VariableType},
nfa::Nfa,
grammars::{Production, ProductionStep, Variable, VariableType},
prepare_grammar::extract_tokens::LexicalToken,
rules::{Precedence, RulePool},
};
#[test]
fn test_extract_simple_aliases() {
let mut syntax_grammar = SyntaxGrammar {
variables: vec![
SyntaxVariable {
name: "v1".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)).with_alias("a1", true),
ProductionStep::new(Symbol::terminal(1)).with_alias("a2", true),
ProductionStep::new(Symbol::terminal(2)).with_alias("a3", true),
ProductionStep::new(Symbol::terminal(3)).with_alias("a4", true),
],
}],
},
SyntaxVariable {
name: "v2".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
// Token 0 is always aliased as "a1".
ProductionStep::new(Symbol::terminal(0)).with_alias("a1", true),
// Token 1 is aliased within rule `v1` above, but not here.
ProductionStep::new(Symbol::terminal(1)),
// Token 2 is aliased differently here than in `v1`. The alias from
// `v1` should be promoted to the default alias, because `v1` appears
// first in the grammar.
ProductionStep::new(Symbol::terminal(2)).with_alias("a5", true),
// Token 3 is also aliased differently here than in `v1`. In this case,
// this alias should be promoted to the default alias, because it is
// used a greater number of times (twice).
ProductionStep::new(Symbol::terminal(3)).with_alias("a6", true),
ProductionStep::new(Symbol::terminal(3)).with_alias("a6", true),
],
}],
},
],
let mut pool = RulePool::default();
let dummy = pool.intern("_");
let root = pool.blank();
// v1: every token aliased.
let v1 = vec![
aliased(&mut pool, Symbol::terminal(0), "a1"),
aliased(&mut pool, Symbol::terminal(1), "a2"),
aliased(&mut pool, Symbol::terminal(2), "a3"),
aliased(&mut pool, Symbol::terminal(3), "a4"),
];
// v2: t0 same alias, t1 unaliased, t2 aliased differently, t3 aliased twice as a6
let v2 = vec![
aliased(&mut pool, Symbol::terminal(0), "a1"),
plain(Symbol::terminal(1)),
aliased(&mut pool, Symbol::terminal(2), "a5"),
aliased(&mut pool, Symbol::terminal(3), "a6"),
aliased(&mut pool, Symbol::terminal(3), "a6"),
];
let mut out = ProductionStore::default();
for steps in [v1, v2] {
let ps = out.productions.len() as u32;
let steps_start = out.steps.len() as u32;
out.steps.extend_from_slice(&steps);
out.productions.push(Production {
steps_start,
steps_len: steps.len() as u32,
dynamic_precedence: 0,
});
out.var_prods.push((ps, out.productions.len() as u32));
}
let g = InputGrammar {
variables: (0..2).map(|_| Variable { name: dummy, root }).collect(),
pool,
..Default::default()
};
let meta = ExtractedGrammarMeta {
lexical_variables: (0..4)
.map(|_| LexicalToken {
name: dummy,
kind: VariableType::Anonymous,
root,
})
.collect(),
..Default::default()
};
let lexical_grammar = LexicalGrammar {
nfa: Nfa::new(),
variables: vec![
LexicalVariable {
name: "t0".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t1".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t2".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
LexicalVariable {
name: "t3".to_string(),
kind: VariableType::Anonymous,
implicit_precedence: 0,
start_state: 0,
},
],
let pool_map = extract_default_aliases(&g, &meta, &mut out);
// t0 -> a1, t2 -> a3 (v1 wins the tie from appearing first), t3 -> a6 (used twice)
// t1 -> none (appears unaliased in v2).
let default = |s| {
pool_map
.get(&s)
.map(|a| (g.pool.resolve(a.value), a.is_named))
};
assert_eq!(pool_map.len(), 3);
assert_eq!(default(Symbol::terminal(0)), Some(("a1", true)));
assert_eq!(default(Symbol::terminal(2)), Some(("a3", true)));
assert_eq!(default(Symbol::terminal(3)), Some(("a6", true)));
assert_eq!(default(Symbol::terminal(1)), None);
let default_aliases = extract_default_aliases(&mut syntax_grammar, &lexical_grammar);
assert_eq!(default_aliases.len(), 3);
// Steps carrying their symbol's default are cleared. The rest keep their alias.
let step_alias = |i: usize| {
out.steps[i]
.alias()
.map(|a| (g.pool.resolve(a.value), a.is_named))
};
assert_eq!(step_alias(0), None); // v1 t0(a1) = default
assert_eq!(step_alias(1), Some(("a2", true))); // v1 t1(a2), no default
assert_eq!(step_alias(2), None); // v1 t2(a3) = default
assert_eq!(step_alias(3), Some(("a4", true))); // v1 t3(a4) != a6
assert_eq!(step_alias(4), None); // v2 t0(a1) = default
assert_eq!(step_alias(5), None); // v2 t1 unaliased
assert_eq!(step_alias(6), Some(("a5", true))); // v2 t2(a5) != a3
assert_eq!(step_alias(7), None); // v2 t3(a6) = default
assert_eq!(step_alias(8), None); // v2 t3(a6) = default
}
assert_eq!(
default_aliases.get(&Symbol::terminal(0)),
Some(&Alias {
value: "a1".to_string(),
fn aliased(pool: &mut RulePool, symbol: Symbol, name: &str) -> ProductionStep {
let value = pool.intern(name);
ProductionStep::pack(
symbol,
Precedence::None,
None,
Some(Alias {
value,
is_named: true,
})
);
assert_eq!(
default_aliases.get(&Symbol::terminal(2)),
Some(&Alias {
value: "a3".to_string(),
is_named: true,
})
);
assert_eq!(
default_aliases.get(&Symbol::terminal(3)),
Some(&Alias {
value: "a6".to_string(),
is_named: true,
})
);
assert_eq!(default_aliases.get(&Symbol::terminal(1)), None);
assert_eq!(
syntax_grammar.variables,
vec![
SyntaxVariable {
name: "v1".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)),
ProductionStep::new(Symbol::terminal(1)).with_alias("a2", true),
ProductionStep::new(Symbol::terminal(2)),
ProductionStep::new(Symbol::terminal(3)).with_alias("a4", true),
],
},],
},
SyntaxVariable {
name: "v2".to_owned(),
kind: VariableType::Named,
productions: vec![Production {
dynamic_precedence: 0,
steps: vec![
ProductionStep::new(Symbol::terminal(0)),
ProductionStep::new(Symbol::terminal(1)),
ProductionStep::new(Symbol::terminal(2)).with_alias("a5", true),
ProductionStep::new(Symbol::terminal(3)),
ProductionStep::new(Symbol::terminal(3)),
],
},],
},
]
);
}),
None,
0,
)
}
fn plain(symbol: Symbol) -> ProductionStep {
ProductionStep::pack(symbol, Precedence::None, None, None, None, 0)
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,17 @@
use rustc_hash::{FxBuildHasher, FxHashMap};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::InternedGrammar;
use crate::{
Diagnostic,
grammars::{InputGrammar, ReservedWordContext, Variable, VariableType},
rules::{Rule, Symbol},
grammars::{InputGrammar, VariableType},
rules::{Rule, RuleId, RulePool, Symbol},
strpool::StrId,
};
pub type InternSymbolsResult<T> = Result<T, InternSymbolsError>;
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)]
pub enum InternSymbolsError {
#[error("A grammar's start rule must be visible.")]
HiddenStartRule,
@ -24,193 +25,189 @@ pub enum InternSymbolsError {
UndefinedWordToken(String),
}
pub(super) fn intern_symbols(
grammar: &InputGrammar,
diagnostics: &mut Vec<Diagnostic>,
) -> InternSymbolsResult<InternedGrammar> {
let interner = Interner { grammar };
/// The non-pool outputs of the intern pass. The rule bodies are rewritten in place
/// (`NamedSymbol`->`Sym`). This carries the derived metadata that travels alongside
/// them.
#[derive(Debug)]
pub(super) struct InternedGrammarMeta {
pub kinds: Vec<VariableType>,
/// Per external token: its name (if a named symbol) and kind
pub external_tokens: Vec<(Option<StrId>, VariableType)>,
pub supertypes: Vec<Symbol>,
pub conflicts: Vec<Vec<Symbol>>,
pub inline: Vec<Symbol>,
pub word: Option<Symbol>,
}
if variable_type_for_name(&grammar.variables[0].name) == VariableType::Hidden {
pub(super) fn intern_symbols(
grammar: &mut InputGrammar,
diagnostics: &mut Vec<Diagnostic>,
) -> InternSymbolsResult<InternedGrammarMeta> {
let InputGrammar {
pool,
variables,
external_roots,
extra_roots,
reserved_sets,
..
} = grammar;
// StrId -> the symbol for a variable or external token name. An external whose name
// shadows a variable resolves to the variable (variables are inserted first).
let mut name_of_symbol =
FxHashMap::with_capacity_and_hasher(variables.len() + external_roots.len(), FxBuildHasher);
for (i, v) in variables.iter().enumerate() {
name_of_symbol.insert(v.name, Symbol::non_terminal(i));
}
for (i, &root) in external_roots.iter().enumerate() {
if let Rule::NamedSymbol(sid) = pool.node(root) {
name_of_symbol
.entry(sid)
.or_insert_with(|| Symbol::external(i));
}
}
if variable_type_for_name(pool.resolve(variables[0].name)) == VariableType::Hidden {
Err(InternSymbolsError::HiddenStartRule)?;
}
let mut variables = Vec::with_capacity(grammar.variables.len());
for variable in &grammar.variables {
variables.push(Variable {
name: variable.name.clone(),
kind: variable_type_for_name(&variable.name),
rule: interner.intern_rule(&variable.rule, Some(&variable.name), diagnostics)?,
});
}
// External names/kinds read before the in-place rewrite consumes them.
let external_tokens = external_roots
.iter()
.map(|&root| match pool.node(root) {
Rule::NamedSymbol(sid) => (Some(sid), variable_type_for_name(pool.resolve(sid))),
_ => (None, VariableType::Anonymous),
})
.collect::<Vec<(Option<StrId>, VariableType)>>();
let mut external_tokens = Vec::with_capacity(grammar.external_tokens.len());
for external_token in &grammar.external_tokens {
let rule = interner.intern_rule(external_token, None, diagnostics)?;
let (name, kind) = if let Rule::NamedSymbol(name) = external_token {
(name.clone(), variable_type_for_name(name))
} else {
(String::new(), VariableType::Anonymous)
};
external_tokens.push(Variable { name, kind, rule });
}
let mut kinds = variables
.iter()
.map(|v| variable_type_for_name(pool.resolve(v.name)))
.collect::<Vec<VariableType>>();
let mut extra_symbols = Vec::with_capacity(grammar.extra_symbols.len());
for extra_token in &grammar.extra_symbols {
extra_symbols.push(interner.intern_rule(extra_token, None, diagnostics)?);
let mut stack = Vec::new();
for v in variables.iter() {
intern_root(
pool,
v.root,
Some(v.name),
&name_of_symbol,
diagnostics,
&mut stack,
)?;
}
let mut supertype_symbols = Vec::with_capacity(grammar.supertype_symbols.len());
for supertype_symbol_name in &grammar.supertype_symbols {
supertype_symbols.push(interner.intern_name(supertype_symbol_name).ok_or_else(|| {
InternSymbolsError::UndefinedSupertype(supertype_symbol_name.clone())
})?);
for &root in external_roots.iter().chain(extra_roots.iter()) {
intern_root(pool, root, None, &name_of_symbol, diagnostics, &mut stack)?;
}
let mut reserved_words = Vec::with_capacity(grammar.reserved_words.len());
for reserved_word_set in &grammar.reserved_words {
let mut interned_set = Vec::with_capacity(reserved_word_set.reserved_words.len());
for rule in &reserved_word_set.reserved_words {
interned_set.push(interner.intern_rule(rule, None, diagnostics)?);
}
reserved_words.push(ReservedWordContext {
name: reserved_word_set.name.clone(),
reserved_words: interned_set,
});
}
let mut expected_conflicts = Vec::with_capacity(grammar.expected_conflicts.len());
for conflict in &grammar.expected_conflicts {
let mut interned_conflict = Vec::with_capacity(conflict.len());
for name in conflict {
interned_conflict.push(
interner
.intern_name(name)
.ok_or_else(|| InternSymbolsError::UndefinedConflict(name.clone()))?,
);
}
expected_conflicts.push(interned_conflict);
}
let mut variables_to_inline = Vec::new();
for name in &grammar.variables_to_inline {
if let Some(symbol) = interner.intern_name(name) {
variables_to_inline.push(symbol);
for set in reserved_sets.iter() {
for &root in &set.roots {
intern_root(pool, root, None, &name_of_symbol, diagnostics, &mut stack)?;
}
}
let word_token = if let Some(name) = grammar.word_token.as_ref() {
Some(
interner
.intern_name(name)
.ok_or_else(|| InternSymbolsError::UndefinedWordToken(name.clone()))?,
)
} else {
None
};
let lookup = |sid: StrId| name_of_symbol.get(&sid).copied();
let supertypes = grammar
.supertype_names
.iter()
.map(|&s| {
lookup(s)
.ok_or_else(|| InternSymbolsError::UndefinedSupertype(pool.resolve(s).to_string()))
})
.collect::<InternSymbolsResult<Vec<_>>>()?;
let conflicts = grammar
.conflict_names
.iter()
.map(|c| {
c.iter()
.map(|&s| {
lookup(s).ok_or_else(|| {
InternSymbolsError::UndefinedConflict(pool.resolve(s).to_string())
})
})
.collect::<InternSymbolsResult<Vec<_>>>()
})
.collect::<InternSymbolsResult<Vec<_>>>()?;
for (i, variable) in variables.iter_mut().enumerate() {
if supertype_symbols.contains(&Symbol::non_terminal(i)) {
variable.kind = VariableType::Hidden;
// Unkown inline names are silently skipped.
let inline = grammar
.inline_names
.iter()
.filter_map(|&s| lookup(s))
.collect();
let word = grammar
.word_name
.map(|s| {
lookup(s)
.ok_or_else(|| InternSymbolsError::UndefinedWordToken(pool.resolve(s).to_string()))
})
.transpose()?;
for s in &supertypes {
if s.is_non_terminal() {
kinds[s.index] = VariableType::Hidden;
}
}
Ok(InternedGrammar {
variables,
Ok(InternedGrammarMeta {
kinds,
external_tokens,
extra_symbols,
expected_conflicts,
variables_to_inline,
supertype_symbols,
word_token,
precedence_orderings: grammar.precedence_orderings.clone(),
reserved_word_sets: reserved_words,
supertypes,
conflicts,
inline,
word,
})
}
struct Interner<'a> {
grammar: &'a InputGrammar,
}
// Iterative pre-order walk rewriting `NamedSymbol`->`Sym` in place.
fn intern_root(
pool: &mut RulePool,
root: RuleId,
var_name: Option<StrId>,
name_of_symbol: &FxHashMap<StrId, Symbol>,
diagnostics: &mut Vec<Diagnostic>,
stack: &mut Vec<RuleId>,
) -> InternSymbolsResult<()> {
stack.clear();
stack.push(root);
impl Interner<'_> {
fn intern_rule(
&self,
rule: &Rule,
name: Option<&str>,
diagnostics: &mut Vec<Diagnostic>,
) -> InternSymbolsResult<Rule> {
match rule {
Rule::Choice(elements) => {
Self::check_single(elements, name, true, diagnostics);
let mut result = Vec::with_capacity(elements.len());
for element in elements {
result.push(self.intern_rule(element, name, diagnostics)?);
while let Some(id) = stack.pop() {
match pool.node(id) {
Rule::NamedSymbol(sid) => match name_of_symbol.get(&sid).copied() {
Some(s) => pool.set_node(
id,
Rule::Sym {
kind: s.kind,
index: s.index as u32,
},
),
None => Err(InternSymbolsError::Undefined(pool.resolve(sid).to_string()))?,
},
Rule::Seq(range) | Rule::Choice(range) => {
let children = pool.child_slice(range);
// In the case of a seq or choice rule of 1 element in a hidden rule, weird
// inconsistent behavior with queries can occur. So we should warn the user about it.
if children.len() == 1
&& matches!(pool.node(children[0]), Rule::String(_) | Rule::Pattern(..))
{
let name = var_name.map(|s| pool.resolve(s).to_string());
diagnostics.push(if matches!(pool.node(id), Rule::Choice(_)) {
Diagnostic::UnaryChoice { name }
} else {
Diagnostic::UnarySeq { name }
});
}
Ok(Rule::Choice(result))
let base = stack.len();
stack.extend_from_slice(pool.child_slice(range));
stack[base..].reverse();
}
Rule::Seq(elements) => {
Self::check_single(elements, name, false, diagnostics);
let mut result = Vec::with_capacity(elements.len());
for element in elements {
result.push(self.intern_rule(element, name, diagnostics)?);
}
Ok(Rule::Seq(result))
}
Rule::Repeat(content) => Ok(Rule::Repeat(Box::new(self.intern_rule(
content,
name,
diagnostics,
)?))),
Rule::Metadata { rule, params } => Ok(Rule::Metadata {
rule: Box::new(self.intern_rule(rule, name, diagnostics)?),
params: params.clone(),
}),
Rule::Reserved { rule, context_name } => Ok(Rule::Reserved {
rule: Box::new(self.intern_rule(rule, name, diagnostics)?),
context_name: context_name.clone(),
}),
Rule::NamedSymbol(name) => self.intern_name(name).map_or_else(
|| Err(InternSymbolsError::Undefined(name.clone())),
|symbol| Ok(Rule::Symbol(symbol)),
),
_ => Ok(rule.clone()),
Rule::Repeat(inner)
| Rule::Metadata { rule: inner, .. }
| Rule::Reserved { rule: inner, .. } => stack.push(inner),
_ => {}
}
}
fn intern_name(&self, symbol: &str) -> Option<Symbol> {
for (i, variable) in self.grammar.variables.iter().enumerate() {
if variable.name == symbol {
return Some(Symbol::non_terminal(i));
}
}
for (i, external_token) in self.grammar.external_tokens.iter().enumerate() {
if let Rule::NamedSymbol(name) = external_token
&& name == symbol
{
return Some(Symbol::external(i));
}
}
None
}
// In the case of a seq or choice rule of 1 element in a hidden rule, weird
// inconsistent behavior with queries can occur. So we should warn the user about it.
fn check_single(
elements: &[Rule],
name: Option<&str>,
is_choice: bool,
diagnostics: &mut Vec<Diagnostic>,
) {
if elements.len() == 1 && matches!(elements[0], Rule::String(_) | Rule::Pattern(_, _)) {
let name = name.map(str::to_string);
diagnostics.push(if is_choice {
Diagnostic::UnaryChoice { name }
} else {
Diagnostic::UnarySeq { name }
});
}
}
Ok(())
}
fn variable_type_for_name(name: &str) -> VariableType {
@ -223,96 +220,244 @@ fn variable_type_for_name(name: &str) -> VariableType {
#[cfg(test)]
mod tests {
use crate::{grammars::Variable, rules::SymbolType};
use super::*;
#[test]
fn test_basic_repeat_expansion() {
let grammar = intern_symbols(
&build_grammar(vec![
Variable::named("x", Rule::choice(vec![Rule::named("y"), Rule::named("_z")])),
Variable::named("y", Rule::named("_z")),
Variable::named("_z", Rule::string("a")),
]),
&mut Vec::new(),
)
.unwrap();
fn test_basic_interning() {
let mut grammar = {
// x: choice(y, _z)
// y: _z
// _z: "a"
let mut pool = RulePool::default();
let (y_str, z_str) = (pool.intern("y"), pool.intern("_z"));
let y = pool.named_symbol(y_str);
let z = pool.named_symbol(z_str);
let a = pool.intern("a");
let x_root = pool.choice(&[y, z]);
let y_root = pool.named_symbol(z_str);
let z_root = pool.string(a);
let variables = vec![
Variable {
name: pool.intern("x"),
root: x_root,
},
Variable {
name: pool.intern("y"),
root: y_root,
},
Variable {
name: pool.intern("_z"),
root: z_root,
},
];
pool_grammar(pool, variables)
};
let meta = intern_symbols(&mut grammar, &mut Vec::new()).unwrap();
// x's body was `choice(y, _z)` -> `choice(nt1, nt2)`
let x_root = grammar.pool.node(grammar.variables[0].root);
let Rule::Choice(range) = x_root else {
panic!("Expected choice, got {x_root:#?}");
};
let x_children = grammar
.pool
.child_slice(range)
.iter()
.map(|&c| grammar.pool.node(c))
.collect::<Vec<_>>();
assert_eq!(x_children, vec![nt(1), nt(2)]);
// y's body was _z -> nt2
assert_eq!(grammar.pool.node(grammar.variables[1].root), nt(2));
// z's body (a string) is untouched, it remains hidden
assert!(matches!(
grammar.pool.node(grammar.variables[2].root),
Rule::String(_)
));
assert_eq!(
grammar.variables,
meta.kinds,
vec![
Variable::named(
"x",
Rule::choice(vec![Rule::non_terminal(1), Rule::non_terminal(2),])
),
Variable::named("y", Rule::non_terminal(2)),
Variable::hidden("_z", Rule::string("a")),
VariableType::Named,
VariableType::Named,
VariableType::Hidden
]
);
}
#[test]
fn test_interning_external_token_names() {
// Variable `y` is both an internal and an external token.
// Variable `z` is just an external token.
let mut input_grammar = build_grammar(vec![
Variable::named(
"w",
Rule::choice(vec![Rule::named("x"), Rule::named("y"), Rule::named("z")]),
),
Variable::named("x", Rule::string("a")),
Variable::named("y", Rule::string("b")),
]);
input_grammar
// w: choice(x, y, z)
// x: "a"
// y: "b"
// externals: [y, z]
let mut grammar = {
let mut pool = RulePool::default();
let (x_str, y_str, z_str) = (pool.intern("x"), pool.intern("y"), pool.intern("z"));
let x_ns = pool.named_symbol(x_str);
let y_ns = pool.named_symbol(y_str);
let z_ns = pool.named_symbol(z_str);
let w_root = pool.choice(&[x_ns, y_ns, z_ns]);
let (a, b) = (pool.intern("a"), pool.intern("b"));
let x_root = pool.string(a);
let y_root = pool.string(b);
let variables = vec![
Variable {
name: pool.intern("w"),
root: w_root,
},
Variable {
name: pool.intern("x"),
root: x_root,
},
Variable {
name: pool.intern("y"),
root: y_root,
},
];
let mut grammar = pool_grammar(pool, variables);
let ext_y = grammar.pool.named_symbol(y_str);
let ext_z = grammar.pool.named_symbol(z_str);
grammar.external_roots = vec![ext_y, ext_z];
grammar
};
let meta = intern_symbols(&mut grammar, &mut Vec::new()).unwrap();
// w: x -> nt1, y -> nt2 (var shadows the external), z -> ext1
let w_root = grammar.pool.node(grammar.variables[0].root);
let Rule::Choice(range) = w_root else {
panic!("Expected choice, got {w_root:#?}");
};
let w_children = grammar
.pool
.child_slice(range)
.iter()
.map(|&c| grammar.pool.node(c))
.collect::<Vec<_>>();
assert_eq!(w_children, vec![nt(1), nt(2), ext(1)]);
// x and y bodies (bare strings) are left alone
assert!(matches!(
grammar.pool.node(grammar.variables[1].root),
Rule::String(_)
));
assert!(matches!(
grammar.pool.node(grammar.variables[2].root),
Rule::String(_)
));
// The external roots resolve the same way: y -> nt2 (shadow), z -> ext1
assert_eq!(grammar.pool.node(grammar.external_roots[0]), nt(2));
assert_eq!(grammar.pool.node(grammar.external_roots[1]), ext(1));
// external token metadata carries the correct names and kinds
let externals = meta
.external_tokens
.extend(vec![Rule::named("y"), Rule::named("z")]);
let grammar = intern_symbols(&input_grammar, &mut Vec::new()).unwrap();
// Variable `y` is referred to by its internal index.
// Variable `z` is referred to by its external index.
.iter()
.map(|&(sid, kind)| (sid.map(|s| grammar.pool.resolve(s)), kind))
.collect::<Vec<_>>();
assert_eq!(
grammar.variables,
externals,
vec![
Variable::named(
"w",
Rule::choice(vec![
Rule::non_terminal(1),
Rule::non_terminal(2),
Rule::external(1),
])
),
Variable::named("x", Rule::string("a")),
Variable::named("y", Rule::string("b")),
]
);
// The external token for `y` refers back to its internal index.
assert_eq!(
grammar.external_tokens,
vec![
Variable::named("y", Rule::non_terminal(2)),
Variable::named("z", Rule::external(1)),
(Some("y"), VariableType::Named),
(Some("z"), VariableType::Named)
]
);
}
#[test]
fn test_grammar_with_undefined_symbols() {
let result = intern_symbols(
&build_grammar(vec![Variable::named("x", Rule::named("y"))]),
&mut Vec::new(),
);
// x references y, which is undefined
let mut pool = RulePool::default();
let y_str = pool.intern("y");
let x_root = pool.named_symbol(y_str);
let variables = vec![Variable {
name: pool.intern("x"),
root: x_root,
}];
let mut grammar = pool_grammar(pool, variables);
let result = intern_symbols(&mut grammar, &mut Vec::new());
assert!(result.is_err(), "Expected an error but got none");
let e = result.err().unwrap();
assert_eq!(e.to_string(), "Undefined symbol `y`");
assert_eq!(
result.unwrap_err(),
InternSymbolsError::Undefined("y".to_string())
);
}
fn build_grammar(variables: Vec<Variable>) -> InputGrammar {
#[test]
fn test_meta_paths() {
// x: "a"
// y: "b"
// supertypes: [y]
// word: y
// conflicts: [x, y]
// inline: [x, nonexistent]
let mut grammar = {
let mut pool = RulePool::default();
let (a, b) = (pool.intern("a"), pool.intern("b"));
let (x, y) = (pool.intern("x"), pool.intern("y"));
let x_root = pool.string(a);
let y_root = pool.string(b);
let variables = vec![
Variable {
name: x,
root: x_root,
},
Variable {
name: y,
root: y_root,
},
];
let mut grammar = pool_grammar(pool, variables);
let nonexistent = grammar.pool.intern("nonexistent");
grammar.supertype_names = vec![y];
grammar.word_name = Some(y);
grammar.conflict_names = vec![vec![x, y]];
grammar.inline_names = vec![x, nonexistent];
grammar
};
let meta = intern_symbols(&mut grammar, &mut Vec::new()).unwrap();
// y is a supertype, so it's reclassified as hidden
assert_eq!(meta.kinds, vec![VariableType::Named, VariableType::Hidden]);
assert_eq!(meta.supertypes, vec![Symbol::non_terminal(1)]);
assert_eq!(meta.word, Some(Symbol::non_terminal(1)));
assert_eq!(
meta.conflicts,
vec![vec![Symbol::non_terminal(0), Symbol::non_terminal(1)]]
);
// unknown inline names are silently skipped
assert_eq!(meta.inline, vec![Symbol::non_terminal(0)]);
}
fn pool_grammar(pool: RulePool, variables: Vec<Variable>) -> InputGrammar {
InputGrammar {
pool,
variables,
name: "the_language".to_string(),
..Default::default()
}
}
fn nt(index: u32) -> Rule {
Rule::Sym {
kind: SymbolType::NonTerminal,
index,
}
}
fn ext(index: u32) -> Rule {
Rule::Sym {
kind: SymbolType::External,
index,
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,16 +7,18 @@ use std::{
use rustc_hash::{FxHashMap, FxHashSet};
use crate::LANGUAGE_VERSION;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::{
LANGUAGE_VERSION,
build_tables::Tables,
grammars::{ExternalToken, LexicalGrammar, SyntaxGrammar, VariableType},
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
nfa::CharacterSet,
node_types::ChildType,
rules::{Alias, AliasMap, Symbol, SymbolType, TokenSet},
rules::Alias,
rules::{AliasMap, Symbol, SymbolType, TokenSet},
strpool::{StrId, StrPool},
tables::{
AdvanceAction, FieldLocation, GotoAction, LexState, LexTable, ParseAction, ParseTable,
ParseTableEntry,
@ -98,11 +100,12 @@ struct Generator {
symbol_map: FxHashMap<Symbol, Symbol>,
reserved_word_sets: Vec<TokenSet>,
reserved_word_set_ids_by_parse_state: Vec<usize>,
field_names: Vec<String>,
field_names: Vec<StrId>,
supertype_symbol_map: BTreeMap<Symbol, Vec<ChildType>>,
supertype_map: BTreeMap<String, Vec<ChildType>>,
abi_version: usize,
metadata: Option<Metadata>,
str_pool: StrPool,
}
struct LargeCharacterSetInfo {
@ -215,7 +218,7 @@ impl Generator {
if other_symbol < mapping && other_alias == alias {
mapping = other_symbol;
}
} else if self.metadata_for_symbol(*other_symbol) == (&alias.value, kind) {
} else if self.metadata_for_symbol(*other_symbol) == (alias.value, kind) {
mapping = other_symbol;
break;
}
@ -246,37 +249,43 @@ impl Generator {
for production_info in &self.parse_table.production_infos {
// Build a list of all field names
for field_name in production_info.field_map.keys() {
if let Err(i) = self.field_names.binary_search(field_name) {
self.field_names.insert(i, field_name.clone());
for &field_name in production_info.field_map.keys() {
if let Err(i) = self.field_names.binary_search_by(|&sid| {
self.str_pool
.resolve(sid)
.cmp(self.str_pool.resolve(field_name))
}) {
self.field_names.insert(i, field_name);
}
}
for alias in &production_info.alias_sequence {
// Generate a mapping from aliases to C identifiers.
if let Some(alias) = &alias {
// Some aliases match an existing symbol in the grammar.
let alias_id = if let Some(existing_symbol) =
self.symbols_for_alias(alias).first()
{
self.symbol_ids[&self.symbol_map[existing_symbol]].clone()
}
// Other aliases don't match any existing symbol, and need their own
// identifiers.
else {
if let Err(i) = self.unique_aliases.binary_search(alias) {
self.unique_aliases.insert(i, alias.clone());
}
if alias.is_named {
format!("alias_sym_{}", Self::sanitize_identifier(&alias.value))
} else {
format!("anon_alias_sym_{}", Self::sanitize_identifier(&alias.value))
}
};
self.alias_ids.entry(alias.clone()).or_insert(alias_id);
// Generate a mapping from aliases to C identifiers.
for &alias in production_info.alias_sequence.iter().flatten() {
// Some aliases match an existing symbol in the grammar.
let alias_id = if let Some(existing_symbol) = self.symbols_for_alias(alias).first()
{
self.symbol_ids[&self.symbol_map[existing_symbol]].clone()
}
// Other aliases don't match any existing symbol, and need their own
// identifiers.
else {
if let Err(i) = self.unique_aliases.binary_search_by(|candidate| {
self.str_pool
.resolve(candidate.value)
.cmp(self.str_pool.resolve(alias.value))
.then_with(|| candidate.is_named.cmp(&alias.is_named))
}) {
self.unique_aliases.insert(i, alias);
}
if alias.is_named {
format!("alias_sym_{}", self.sanitize_identifier(alias.value))
} else {
format!("anon_alias_sym_{}", self.sanitize_identifier(alias.value))
}
};
self.alias_ids.entry(alias).or_insert(alias_id);
}
}
@ -461,10 +470,11 @@ impl Generator {
add_line!(self, "static const char * const ts_symbol_names[] = {{");
indent!(self);
for symbol in &self.parse_table.symbols {
let name = Self::sanitize_string(self.default_aliases.get(symbol).map_or_else(
|| self.metadata_for_symbol(*symbol).0,
|alias| alias.value.as_str(),
));
let name = self.sanitize_string(
self.default_aliases
.get(symbol)
.map_or_else(|| self.metadata_for_symbol(*symbol).0, |alias| alias.value),
);
add_line!(self, "[{}] = \"{name}\",", self.symbol_ids[symbol]);
}
for alias in &self.unique_aliases {
@ -472,7 +482,7 @@ impl Generator {
self,
"[{}] = \"{}\",",
self.alias_ids[alias],
Self::sanitize_string(&alias.value)
self.sanitize_string(alias.value)
);
}
dedent!(self);
@ -509,8 +519,13 @@ impl Generator {
fn add_field_name_enum(&mut self) {
add_line!(self, "enum ts_field_identifiers {{");
indent!(self);
for (i, field_name) in self.field_names.iter().enumerate() {
add_line!(self, "{} = {},", Self::field_id(field_name), i + 1);
for (i, &field_name) in self.field_names.iter().enumerate() {
add_line!(
self,
"{} = {},",
Self::field_id(self.str_pool.resolve(field_name)),
i + 1
);
}
dedent!(self);
add_line!(self, "}};");
@ -521,7 +536,8 @@ impl Generator {
add_line!(self, "static const char * const ts_field_names[] = {{");
indent!(self);
add_line!(self, "[0] = NULL,");
for field_name in &self.field_names {
for &field_name in &self.field_names {
let field_name = self.str_pool.resolve(field_name);
add_line!(self, "[{}] = \"{field_name}\",", Self::field_id(field_name));
}
dedent!(self);
@ -613,17 +629,18 @@ impl Generator {
fn add_non_terminal_alias_map(&mut self) {
let mut alias_ids_by_symbol = FxHashMap::default();
for variable in &self.syntax_grammar.variables {
for production in &variable.productions {
for step in &production.steps {
if let Some(alias) = &step.alias
&& step.symbol.is_non_terminal()
&& Some(alias) != self.default_aliases.get(&step.symbol)
&& self.symbol_ids.contains_key(&step.symbol)
&& let Some(alias_id) = self.alias_ids.get(alias)
for i in 0..self.syntax_grammar.variables.len() {
for prod_id in self.syntax_grammar.variable_prod_ids(i) {
for step in self.syntax_grammar.production(prod_id).steps {
if let Some(alias) = step.alias()
&& step.symbol().is_non_terminal()
&& Some(alias) != self.default_aliases.get(&step.symbol()).copied()
&& self.symbol_ids.contains_key(&step.symbol())
&& let Some(alias_id) = self.alias_ids.get(&alias)
{
let alias_ids =
alias_ids_by_symbol.entry(step.symbol).or_insert(Vec::new());
let alias_ids = alias_ids_by_symbol
.entry(step.symbol())
.or_insert(Vec::new());
if let Err(i) = alias_ids.binary_search(&alias_id) {
alias_ids.insert(i, alias_id);
}
@ -697,9 +714,12 @@ impl Generator {
let mut flat_field_map = Vec::with_capacity(production_info.field_map.len());
for (field_name, locations) in &production_info.field_map {
for location in locations {
flat_field_map.push((field_name.clone(), *location));
flat_field_map.push((*field_name, *location));
}
}
flat_field_map.sort_by(|(a, _), (b, _)| {
self.str_pool.resolve(*a).cmp(self.str_pool.resolve(*b))
});
let field_map_len = flat_field_map.len();
field_map_ids.push((
Self::get_field_map_id(
@ -742,7 +762,7 @@ impl Generator {
add!(
self,
"{{{}, {}",
Self::field_id(&field_name),
Self::field_id(self.str_pool.resolve(field_name)),
location.index
);
if location.inherited {
@ -788,7 +808,7 @@ impl Generator {
ChildType::Aliased(alias) => {
self.alias_ids.get(alias).cloned().map_or_else(
|| {
self.symbols_for_alias(alias)
self.symbols_for_alias(*alias)
.into_iter()
.map(|s| self.symbol_ids.get(&s).cloned())
.collect()
@ -1227,11 +1247,7 @@ impl Generator {
add_line!(self, "enum ts_external_scanner_symbol_identifiers {{");
indent!(self);
for i in 0..self.syntax_grammar.external_tokens.len() {
add_line!(
self,
"{} = {i},",
Self::external_token_id(&self.syntax_grammar.external_tokens[i]),
);
add_line!(self, "{} = {i},", self.external_token_id(i));
}
dedent!(self);
add_line!(self, "}};");
@ -1252,7 +1268,7 @@ impl Generator {
add_line!(
self,
"[{}] = {},",
Self::external_token_id(token),
self.external_token_id(i),
self.symbol_ids[&id_token],
);
}
@ -1273,11 +1289,7 @@ impl Generator {
add_line!(self, "[{i}] = {{");
indent!(self);
for token in self.parse_table.external_lex_states[i].iter() {
add_line!(
self,
"[{}] = true,",
Self::external_token_id(&self.syntax_grammar.external_tokens[token.index])
);
add_line!(self, "[{}] = true,", self.external_token_id(token.index));
}
dedent!(self);
add_line!(self, "}},");
@ -1697,8 +1709,8 @@ impl Generator {
}
fn get_field_map_id(
flat_field_map: Vec<(String, FieldLocation)>,
flat_field_maps: &mut Vec<(usize, Vec<(String, FieldLocation)>)>,
flat_field_map: Vec<(StrId, FieldLocation)>,
flat_field_maps: &mut Vec<(usize, Vec<(StrId, FieldLocation)>)>,
next_flat_field_map_index: &mut usize,
) -> usize {
if let Some((index, _)) = flat_field_maps.iter().find(|(_, e)| *e == *flat_field_map) {
@ -1711,11 +1723,9 @@ impl Generator {
result
}
fn external_token_id(token: &ExternalToken) -> String {
format!(
"ts_external_token_{}",
Self::sanitize_identifier(&token.name)
)
fn external_token_id(&self, token_idx: usize) -> String {
let token = &self.syntax_grammar.external_tokens[token_idx];
format!("ts_external_token_{}", self.sanitize_identifier(token.name))
}
fn assign_symbol_id(&mut self, symbol: Symbol, used_identifiers: &mut FxHashSet<String>) {
@ -1725,10 +1735,10 @@ impl Generator {
} else {
let (name, kind) = self.metadata_for_symbol(symbol);
id = match kind {
VariableType::Auxiliary => format!("aux_sym_{}", Self::sanitize_identifier(name)),
VariableType::Anonymous => format!("anon_sym_{}", Self::sanitize_identifier(name)),
VariableType::Auxiliary => format!("aux_sym_{}", self.sanitize_identifier(name)),
VariableType::Anonymous => format!("anon_sym_{}", self.sanitize_identifier(name)),
VariableType::Hidden | VariableType::Named => {
format!("sym_{}", Self::sanitize_identifier(name))
format!("sym_{}", self.sanitize_identifier(name))
}
};
@ -1750,25 +1760,27 @@ impl Generator {
format!("field_{field_name}")
}
fn metadata_for_symbol(&self, symbol: Symbol) -> (&str, VariableType) {
fn metadata_for_symbol(&self, symbol: Symbol) -> (StrId, VariableType) {
match symbol.kind {
SymbolType::End | SymbolType::EndOfNonTerminalExtra => ("end", VariableType::Hidden),
SymbolType::End | SymbolType::EndOfNonTerminalExtra => {
(StrPool::END_NAME_ID, VariableType::Hidden)
}
SymbolType::NonTerminal => {
let variable = &self.syntax_grammar.variables[symbol.index];
(&variable.name, variable.kind)
(variable.name, variable.kind)
}
SymbolType::Terminal => {
let variable = &self.lexical_grammar.variables[symbol.index];
(&variable.name, variable.kind)
(variable.name, variable.kind)
}
SymbolType::External => {
let token = &self.syntax_grammar.external_tokens[symbol.index];
(&token.name, token.kind)
(token.name, token.kind)
}
}
}
fn symbols_for_alias(&self, alias: &Alias) -> Vec<Symbol> {
fn symbols_for_alias(&self, alias: Alias) -> Vec<Symbol> {
self.parse_table
.symbols
.iter()
@ -1779,13 +1791,14 @@ impl Generator {
let (name, kind) = self.metadata_for_symbol(*symbol);
name == alias.value && kind == alias.kind()
},
|default_alias| default_alias == alias,
|&default_alias| default_alias == alias,
)
})
.collect()
}
fn sanitize_identifier(name: &str) -> String {
fn sanitize_identifier(&self, name: StrId) -> String {
let name = self.str_pool.resolve(name);
let mut result = String::with_capacity(name.len());
for c in name.chars() {
if c.is_ascii_alphanumeric() || c == '_' {
@ -1880,7 +1893,8 @@ impl Generator {
result
}
fn sanitize_string(name: &str) -> String {
fn sanitize_string(&self, name: StrId) -> String {
let name = self.str_pool.resolve(name);
let mut result = String::with_capacity(name.len());
for c in name.chars() {
match c {
@ -1939,6 +1953,8 @@ impl Generator {
/// * `default_aliases` - A map describing the global rename rules that should apply. the keys are
/// symbols that are *always* aliased in the same way, and the values are the aliases that are
/// applied to those symbols.
/// * `str_pool` - A backing pool for `StrId`-identified strings within `syntax_grammar`,
/// `lexical_grammar`, and `default_aliases`.
/// * `abi_version` - The language ABI version that should be generated. Usually you want
/// Tree-sitter's current version, but right after making an ABI change, it may be useful to
/// generate code with the previous ABI.
@ -1947,11 +1963,12 @@ impl Generator {
reason = "all parameters are required for code generation"
)]
pub fn render_c_code(
name: &str,
name: StrId,
tables: Tables,
syntax_grammar: SyntaxGrammar,
lexical_grammar: LexicalGrammar,
default_aliases: AliasMap,
str_pool: StrPool,
abi_version: usize,
semantic_version: Option<(u8, u8, u8)>,
supertype_symbol_map: BTreeMap<Symbol, Vec<ChildType>>,
@ -1961,7 +1978,7 @@ pub fn render_c_code(
}
Generator {
language_name: name.to_string(),
language_name: str_pool.resolve(name).to_string(),
parse_table: tables.parse_table,
main_lex_table: tables.main_lex_table,
keyword_lex_table: tables.keyword_lex_table,
@ -1977,6 +1994,7 @@ pub fn render_c_code(
patch,
}),
supertype_symbol_map,
str_pool,
..Default::default()
}
.generate()

View file

@ -1,9 +1,15 @@
use std::{collections::BTreeMap, fmt, hash::Hash};
//! Flat, pool-indexed rule IR for the grammar backend.
//!
//! Nodes live in one append-only arena and reference children by index, so passes
//! are iterative pool walks with in-place rewrites.
use serde::{Deserialize, Serialize};
use super::bitvec::{BitVec, SetBitsIter};
use super::grammars::VariableType;
use crate::{
bitvec::{BitVec, SetBitsIter},
grammars::VariableType,
strpool::{StrId, StrPool},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum SymbolType {
@ -20,191 +26,15 @@ pub enum Associativity {
Right,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
pub struct Alias {
pub value: String,
pub value: StrId,
pub is_named: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum Precedence {
#[default]
None,
Integer(i32),
Name(String),
}
pub type AliasMap = BTreeMap<Symbol, Alias>;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MetadataParams {
pub precedence: Precedence,
pub dynamic_precedence: i32,
pub associativity: Option<Associativity>,
pub is_token: bool,
pub is_main_token: bool,
pub alias: Option<Alias>,
pub field_name: Option<String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Symbol {
pub kind: SymbolType,
pub index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Rule {
Blank,
String(String),
Pattern(String, String),
NamedSymbol(String),
Symbol(Symbol),
Choice(Vec<Self>),
Metadata {
params: MetadataParams,
rule: Box<Self>,
},
Repeat(Box<Self>),
Seq(Vec<Self>),
Reserved {
rule: Box<Self>,
context_name: String,
},
}
// Because tokens are represented as small (~400 max) unsigned integers,
// sets of tokens can be efficiently represented as bit vectors with each
// index corresponding to a token, and each value representing whether or not
// the token is present in the set.
#[derive(Default, Clone, PartialEq, Eq, Hash)]
pub struct TokenSet {
terminal_bits: BitVec,
external_bits: BitVec,
eof: bool,
end_of_nonterminal_extra: bool,
}
impl fmt::Debug for TokenSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl PartialOrd for TokenSet {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TokenSet {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.terminal_bits
.cmp(&other.terminal_bits)
.then_with(|| self.external_bits.cmp(&other.external_bits))
.then_with(|| self.eof.cmp(&other.eof))
.then_with(|| {
self.end_of_nonterminal_extra
.cmp(&other.end_of_nonterminal_extra)
})
}
}
impl Rule {
#[must_use]
pub fn field(name: String, content: Self) -> Self {
add_metadata(content, move |params| {
params.field_name = Some(name);
})
}
#[must_use]
pub fn alias(content: Self, value: String, is_named: bool) -> Self {
add_metadata(content, move |params| {
params.alias = Some(Alias { value, is_named });
})
}
#[must_use]
pub fn token(content: Self) -> Self {
add_metadata(content, |params| {
params.is_token = true;
})
}
#[must_use]
pub fn immediate_token(content: Self) -> Self {
add_metadata(content, |params| {
params.is_token = true;
params.is_main_token = true;
})
}
#[must_use]
pub fn prec(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.precedence = value;
})
}
#[must_use]
pub fn prec_left(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.associativity = Some(Associativity::Left);
params.precedence = value;
})
}
#[must_use]
pub fn prec_right(value: Precedence, content: Self) -> Self {
add_metadata(content, |params| {
params.associativity = Some(Associativity::Right);
params.precedence = value;
})
}
#[must_use]
pub fn prec_dynamic(value: i32, content: Self) -> Self {
add_metadata(content, |params| {
params.dynamic_precedence = value;
})
}
#[must_use]
pub fn repeat(rule: Self) -> Self {
Self::Repeat(Box::new(rule))
}
#[must_use]
pub fn choice(rules: Vec<Self>) -> Self {
let mut elements = Vec::with_capacity(rules.len());
for rule in rules {
choice_helper(&mut elements, rule);
}
Self::Choice(elements)
}
#[must_use]
pub const fn seq(rules: Vec<Self>) -> Self {
Self::Seq(rules)
}
pub fn is_empty(&self) -> bool {
match self {
Self::Blank | Self::Pattern(..) | Self::NamedSymbol(_) | Self::Symbol(_) => false,
Self::String(string) => string.is_empty(),
Self::Metadata { rule, .. } | Self::Repeat(rule) | Self::Reserved { rule, .. } => {
rule.is_empty()
}
Self::Choice(rules) => rules.iter().any(Self::is_empty),
Self::Seq(rules) => rules.iter().all(Self::is_empty),
}
}
}
impl Alias {
#[must_use]
pub const fn kind(&self) -> VariableType {
pub const fn kind(self) -> VariableType {
if self.is_named {
VariableType::Named
} else {
@ -213,46 +43,30 @@ impl Alias {
}
}
impl Precedence {
#[must_use]
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub enum Precedence {
#[default]
None,
Integer(i32),
Name(StrId),
}
#[cfg(test)]
impl Rule {
#[must_use]
pub const fn terminal(index: usize) -> Self {
Self::Symbol(Symbol::terminal(index))
}
#[must_use]
pub const fn non_terminal(index: usize) -> Self {
Self::Symbol(Symbol::non_terminal(index))
}
#[must_use]
pub const fn external(index: usize) -> Self {
Self::Symbol(Symbol::external(index))
}
#[must_use]
pub fn named(name: &'static str) -> Self {
Self::NamedSymbol(name.to_string())
}
#[must_use]
pub fn string(value: &'static str) -> Self {
Self::String(value.to_string())
}
#[must_use]
pub fn pattern(value: &'static str, flags: &'static str) -> Self {
Self::Pattern(value.to_string(), flags.to_string())
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct MetadataParams {
pub precedence: Precedence,
pub associativity: Option<Associativity>,
pub dynamic_precedence: i32,
pub alias: Option<Alias>,
pub field: Option<StrId>,
pub is_token: bool,
pub is_main_token: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Symbol {
pub kind: SymbolType,
pub index: usize,
}
impl Symbol {
#[must_use]
pub fn is_terminal(&self) -> bool {
@ -315,9 +129,445 @@ impl Symbol {
}
}
/// A pooled rule node.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Rule {
Blank,
String(StrId),
Pattern(StrId, StrId),
NamedSymbol(StrId),
Sym { kind: SymbolType, index: u32 },
Seq(RuleIdRange),
Choice(RuleIdRange),
Repeat(RuleId),
Metadata { params: ParamsId, rule: RuleId },
Reserved { rule: RuleId, ctx: StrId },
}
const _: () = assert!(std::mem::size_of::<Rule>() <= 12);
impl From<Symbol> for Rule {
fn from(symbol: Symbol) -> Self {
Self::Symbol(symbol)
fn from(value: Symbol) -> Self {
Self::Sym {
kind: value.kind,
index: value.index as u32,
}
}
}
impl Rule {
pub const fn symbol(self) -> Option<Symbol> {
match self {
Self::Sym { kind, index } => Some(Symbol {
kind,
index: index as usize,
}),
_ => None,
}
}
}
/// The arena: nodes, their pooled children/params, and the string interner.
/// Storage is append-only, passes rewrite nodes in place and may orphan subtrees.
#[derive(Clone, Default)]
pub struct RulePool {
nodes: Vec<Rule>,
children: Vec<RuleId>,
params: Vec<MetadataParams>,
str_pool: StrPool,
}
impl RulePool {
#[must_use]
pub fn node(&self, id: RuleId) -> Rule {
self.nodes[id.index()]
}
pub fn set_node(&mut self, id: RuleId, node: Rule) {
self.nodes[id.index()] = node;
}
pub fn push_node(&mut self, node: Rule) -> RuleId {
let id = RuleId(self.nodes.len() as u32);
self.nodes.push(node);
id
}
#[must_use]
pub fn child_slice(&self, range: RuleIdRange) -> &[RuleId] {
&self.children[range.as_range()]
}
pub fn push_children(&mut self, ids: &[RuleId]) -> RuleIdRange {
let start = self.children.len() as u32;
self.children.extend_from_slice(ids);
RuleIdRange {
start,
len: ids.len() as u32,
}
}
#[must_use]
pub fn params(&self, id: ParamsId) -> MetadataParams {
self.params[id.0 as usize]
}
pub fn push_params(&mut self, params: MetadataParams) -> ParamsId {
let id = ParamsId(self.params.len() as u32);
self.params.push(params);
id
}
pub fn set_params(&mut self, id: ParamsId, params: MetadataParams) {
self.params[id.0 as usize] = params;
}
fn metadata_with(&mut self, content: RuleId, f: impl FnOnce(&mut MetadataParams)) -> RuleId {
if let Rule::Metadata { params, .. } = self.node(content) {
let mut p = self.params(params);
if !p.is_token {
f(&mut p);
self.set_params(params, p);
return content;
}
}
let mut p = MetadataParams::default();
f(&mut p);
let params = self.push_params(p);
self.push_node(Rule::Metadata {
params,
rule: content,
})
}
pub fn blank(&mut self) -> RuleId {
self.push_node(Rule::Blank)
}
pub fn string(&mut self, value: StrId) -> RuleId {
self.push_node(Rule::String(value))
}
pub fn pattern(&mut self, value: StrId, flags: StrId) -> RuleId {
self.push_node(Rule::Pattern(value, flags))
}
pub fn named_symbol(&mut self, name: StrId) -> RuleId {
self.push_node(Rule::NamedSymbol(name))
}
pub fn field(&mut self, name: StrId, content: RuleId) -> RuleId {
self.metadata_with(content, |p| p.field = Some(name))
}
pub fn alias(&mut self, content: RuleId, value: StrId, is_named: bool) -> RuleId {
self.metadata_with(content, |p| p.alias = Some(Alias { value, is_named }))
}
pub fn token(&mut self, content: RuleId) -> RuleId {
self.metadata_with(content, |p| p.is_token = true)
}
pub fn immediate_token(&mut self, content: RuleId) -> RuleId {
self.metadata_with(content, |p| {
p.is_token = true;
p.is_main_token = true;
})
}
pub fn prec(&mut self, value: Precedence, content: RuleId) -> RuleId {
self.metadata_with(content, |p| p.precedence = value)
}
pub fn prec_left(&mut self, value: Precedence, content: RuleId) -> RuleId {
self.metadata_with(content, |p| {
p.associativity = Some(Associativity::Left);
p.precedence = value;
})
}
pub fn prec_right(&mut self, value: Precedence, content: RuleId) -> RuleId {
self.metadata_with(content, |p| {
p.associativity = Some(Associativity::Right);
p.precedence = value;
})
}
pub fn prec_dynamic(&mut self, value: i32, content: RuleId) -> RuleId {
self.metadata_with(content, |p| p.dynamic_precedence = value)
}
pub fn repeat(&mut self, content: RuleId) -> RuleId {
self.push_node(Rule::Repeat(content))
}
pub fn seq(&mut self, ids: &[RuleId]) -> RuleId {
let range = self.push_children(ids);
self.push_node(Rule::Seq(range))
}
/// Flatten nested choices and de-dup structurally, keeping a `Choice` node
/// event for a single element
pub fn choice(&mut self, ids: &[RuleId]) -> RuleId {
let mut elements: Vec<RuleId> = Vec::with_capacity(ids.len());
let mut stack: Vec<RuleId> = Vec::with_capacity(ids.len());
stack.extend(ids.iter().rev());
while let Some(id) = stack.pop() {
if let Rule::Choice(range) = self.node(id) {
let base = stack.len();
stack.extend_from_slice(self.child_slice(range));
stack[base..].reverse();
} else if !elements.iter().any(|&e| self.subtree_eq(e, id)) {
elements.push(id);
}
}
let range = self.push_children(&elements);
self.push_node(Rule::Choice(range))
}
pub fn reserved(&mut self, content: RuleId, ctx: StrId) -> RuleId {
self.push_node(Rule::Reserved { rule: content, ctx })
}
#[inline]
#[must_use]
pub fn intern(&mut self, s: &str) -> StrId {
self.str_pool.intern(s)
}
#[inline]
#[must_use]
pub fn resolve(&self, id: StrId) -> &str {
self.str_pool.resolve(id)
}
#[must_use]
pub fn into_interner(self) -> StrPool {
self.str_pool
}
#[must_use]
pub fn subtree_hash(&self, root: RuleId) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = rustc_hash::FxHasher::default();
let mut stack = vec![root];
while let Some(id) = stack.pop() {
let node = self.node(id);
std::mem::discriminant(&node).hash(&mut hasher);
match node {
Rule::Blank => {}
Rule::String(s) | Rule::NamedSymbol(s) => s.hash(&mut hasher),
Rule::Pattern(p, f) => {
p.hash(&mut hasher);
f.hash(&mut hasher);
}
Rule::Sym { kind, index } => {
(kind as u8).hash(&mut hasher);
index.hash(&mut hasher);
}
Rule::Seq(range) | Rule::Choice(range) => {
hasher.write_u32(range.len);
let base = stack.len();
stack.extend_from_slice(self.child_slice(range));
stack[base..].reverse();
}
Rule::Repeat(inner) => stack.push(inner),
Rule::Metadata { params, rule } => {
let p = self.params(params);
(p.precedence, p.associativity, p.dynamic_precedence).hash(&mut hasher);
p.alias.map(|a| (a.value, a.is_named)).hash(&mut hasher);
(p.field, p.is_token, p.is_main_token).hash(&mut hasher);
stack.push(rule);
}
Rule::Reserved { rule, ctx } => {
ctx.hash(&mut hasher);
stack.push(rule);
}
}
}
hasher.finish()
}
pub fn subtree_eq(&self, a: RuleId, b: RuleId) -> bool {
let mut stack = vec![(a, b)];
while let Some((a, b)) = stack.pop() {
match (self.node(a), self.node(b)) {
(Rule::Blank, Rule::Blank) => {}
(Rule::String(x), Rule::String(y))
| (Rule::NamedSymbol(x), Rule::NamedSymbol(y)) => {
if x != y {
return false;
}
}
(Rule::Pattern(p1, f1), Rule::Pattern(p2, f2)) => {
if p1 != p2 || f1 != f2 {
return false;
}
}
(n1 @ Rule::Sym { .. }, n2 @ Rule::Sym { .. }) => {
if n1 != n2 {
return false;
}
}
(Rule::Seq(r1), Rule::Seq(r2)) | (Rule::Choice(r1), Rule::Choice(r2)) => {
if r1.len != r2.len {
return false;
}
stack.extend(
self.child_slice(r1)
.iter()
.copied()
.zip(self.child_slice(r2).iter().copied()),
);
}
(Rule::Repeat(i1), Rule::Repeat(i2)) => stack.push((i1, i2)),
#[rustfmt::skip]
(Rule::Metadata { params: p1, rule: r1 }, Rule::Metadata { params: p2, rule: r2 }) => {
if self.params(p1) != self.params(p2) {
return false;
}
stack.push((r1, r2));
}
(Rule::Reserved { rule: r1, ctx: c1 }, Rule::Reserved { rule: r2, ctx: c2 }) => {
if c1 != c2 {
return false;
}
stack.push((r1, r2));
}
_ => return false,
}
}
true
}
/// Whether the subtree at `id` can only match the empty string.
pub fn subtree_matches_empty_str(&self, id: RuleId) -> bool {
match self.node(id) {
Rule::String(sid) => self.resolve(sid).is_empty(),
Rule::Metadata { rule, .. } | Rule::Repeat(rule) | Rule::Reserved { rule, .. } => {
self.subtree_matches_empty_str(rule)
}
Rule::Choice(range) => self
.child_slice(range)
.iter()
.any(|&c| self.subtree_matches_empty_str(c)),
Rule::Seq(range) => self
.child_slice(range)
.iter()
.all(|&c| self.subtree_matches_empty_str(c)),
_ => false,
}
}
/// Check if a rule is referenced by another rule.
///
/// This function is used to determine if a variable is used in a given rule,
/// and `is_external` indicates if the rule is an external, and if it is,
/// to not assume that a named symbol that is equal to itself means it's being referenced.
///
/// For example, if we have an external rule **and** a normal rule both called `foo`,
/// `foo` should not be thought of as directly used unless it's used within another rule.
pub fn rule_is_referenced(&self, id: RuleId, target: StrId, is_external: bool) -> bool {
match self.node(id) {
Rule::NamedSymbol(name) => name == target && !is_external,
Rule::Choice(range) | Rule::Seq(range) => self
.child_slice(range)
.iter()
.any(|&c| self.rule_is_referenced(c, target, false)),
Rule::Metadata { rule, .. } | Rule::Reserved { rule, .. } => {
self.rule_is_referenced(rule, target, is_external)
}
Rule::Repeat(inner) => self.rule_is_referenced(inner, target, false),
Rule::Blank | Rule::String(_) | Rule::Pattern(..) | Rule::Sym { .. } => false,
}
}
/// Append every `NamedSymbol` name reachable in `id` to `out`. If
/// `skip_top_level` is true, a `NamedSymbol` at the root of `id` is
/// ignored (used for externals entries, which name themselves).
pub fn collect_referenced_ids(&self, id: RuleId, skip_top_level: bool, out: &mut Vec<StrId>) {
match self.node(id) {
Rule::NamedSymbol(name) => {
if !skip_top_level {
out.push(name);
}
}
Rule::Choice(range) | Rule::Seq(range) => {
for &c in self.child_slice(range) {
self.collect_referenced_ids(c, false, out);
}
}
Rule::Metadata { rule, .. } | Rule::Reserved { rule, .. } => {
self.collect_referenced_ids(rule, skip_top_level, out);
}
Rule::Repeat(inner) => self.collect_referenced_ids(inner, false, out),
Rule::Blank | Rule::String(_) | Rule::Pattern(..) | Rule::Sym { .. } => {}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct RuleId(u32);
impl RuleId {
#[must_use]
pub const fn index(self) -> usize {
self.0 as usize
}
}
/// Index into [`RulePool::params`].
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ParamsId(u32);
/// A `[start, start + len)` slice of [`RulePool::children`].
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct RuleIdRange {
pub start: u32,
pub len: u32,
}
impl RuleIdRange {
#[must_use]
pub const fn as_range(self) -> std::ops::Range<usize> {
self.start as usize..(self.start + self.len) as usize
}
}
// Because tokens are represented as small (~400 max) unsigned integers,
// sets of tokens can be efficiently represented as bit vectors with each
// index corresponding to a token, and each value representing whether or not
// the token is present in the set.
#[derive(Default, Clone, PartialEq, Eq, Hash)]
pub struct TokenSet {
terminal_bits: BitVec,
external_bits: BitVec,
eof: bool,
end_of_nonterminal_extra: bool,
}
impl std::fmt::Debug for TokenSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl PartialOrd for TokenSet {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TokenSet {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.terminal_bits
.cmp(&other.terminal_bits)
.then_with(|| self.external_bits.cmp(&other.external_bits))
.then_with(|| self.eof.cmp(&other.eof))
.then_with(|| {
self.end_of_nonterminal_extra
.cmp(&other.end_of_nonterminal_extra)
})
}
}
@ -495,44 +745,4 @@ impl FromIterator<Symbol> for TokenSet {
}
}
fn add_metadata<T: FnOnce(&mut MetadataParams)>(input: Rule, f: T) -> Rule {
match input {
Rule::Metadata { rule, mut params } if !params.is_token => {
f(&mut params);
Rule::Metadata { rule, params }
}
_ => {
let mut params = MetadataParams::default();
f(&mut params);
Rule::Metadata {
rule: Box::new(input),
params,
}
}
}
}
fn choice_helper(result: &mut Vec<Rule>, rule: Rule) {
match rule {
Rule::Choice(elements) => {
for element in elements {
choice_helper(result, element);
}
}
_ => {
if !result.contains(&rule) {
result.push(rule);
}
}
}
}
impl fmt::Display for Precedence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Integer(i) => write!(f, "{i}"),
Self::Name(s) => write!(f, "'{s}'"),
Self::None => write!(f, "none"),
}
}
}
pub type AliasMap = std::collections::BTreeMap<Symbol, Alias>;

View file

@ -0,0 +1,74 @@
use std::{num::NonZeroU32, rc::Rc};
use rustc_hash::FxHashMap;
/// Interned string id, a 1-based index into the pool's string table
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Hash, Debug, Eq)]
pub struct StrId(NonZeroU32);
impl StrId {
/// Dense 0-based index (ids are 1-based).
#[must_use]
pub const fn index(self) -> usize {
self.0.get() as usize - 1
}
/// The raw 1-based id, for packed encodings where 0 means "none".
#[must_use]
pub const fn raw(self) -> u32 {
self.0.get()
}
/// Inverse of [`Self::raw`]. Caller must pass a value produced by `raw`.
#[must_use]
pub const fn from_raw(raw: u32) -> Self {
Self(NonZeroU32::new(raw).unwrap())
}
}
impl Default for StrId {
fn default() -> Self {
StrPool::EMPTY_STR_ID
}
}
#[derive(Clone, Debug)]
pub struct StrPool {
strs: Vec<Rc<str>>,
str_ids: FxHashMap<Rc<str>, StrId>,
}
impl Default for StrPool {
fn default() -> Self {
let mut pool = Self {
strs: Vec::default(),
str_ids: FxHashMap::default(),
};
let empty_id = pool.intern("");
debug_assert_eq!(empty_id, Self::EMPTY_STR_ID);
let end_id = pool.intern("end");
debug_assert_eq!(end_id, Self::END_NAME_ID);
pool
}
}
impl StrPool {
pub const EMPTY_STR_ID: StrId = StrId::from_raw(1);
pub const END_NAME_ID: StrId = StrId::from_raw(2);
pub fn intern(&mut self, s: &str) -> StrId {
if let Some(&id) = self.str_ids.get(s) {
return id;
}
let owned: Rc<str> = Rc::from(s);
let id = StrId(NonZeroU32::new(self.strs.len() as u32 + 1).unwrap());
self.strs.push(Rc::clone(&owned));
self.str_ids.insert(owned, id);
id
}
#[must_use]
pub fn resolve(&self, id: StrId) -> &str {
&self.strs[id.index()]
}
}

View file

@ -2,7 +2,9 @@ use std::collections::BTreeMap;
use super::{
nfa::CharacterSet,
rules::{Alias, Symbol, TokenSet},
rules::Alias,
rules::{Symbol, TokenSet},
strpool::StrId,
};
pub type ProductionInfoId = usize;
pub type ParseStateId = usize;
@ -62,7 +64,7 @@ pub struct FieldLocation {
#[derive(Debug, Default, PartialEq, Eq)]
pub struct ProductionInfo {
pub alias_sequence: Vec<Option<Alias>>,
pub field_map: BTreeMap<String, Vec<FieldLocation>>,
pub field_map: BTreeMap<StrId, Vec<FieldLocation>>,
}
#[derive(Debug, Default, PartialEq, Eq)]

View file

@ -1 +1 @@
Grammar contains an indirectly recursive rule: type_expression -> _expression -> identifier_expression -> type_expression
Grammar contains an indirectly recursive rule: type_expression -> _expression -> type_expression