diff --git a/Cargo.toml b/Cargo.toml index 388622fa7..dd768bac2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/generate/src/build_tables.rs b/crates/generate/src/build_tables.rs index f477d4868..f86cc27a2 100644 --- a/crates/generate/src/build_tables.rs +++ b/crates/generate/src/build_tables.rs @@ -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, ) -> BuildTableResult { - 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 { 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::(); - 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, 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::>() @@ -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), ); } } diff --git a/crates/generate/src/build_tables/build_parse_table.rs b/crates/generate/src/build_tables/build_parse_table.rs index 74c6c67ba..1a15246dc 100644 --- a/crates/generate/src/build_tables/build_parse_table.rs +++ b/crates/generate/src/build_tables/build_parse_table.rs @@ -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>, parse_table: ParseTable, + str_pool: &'a StrPool, } pub type BuildTableResult = Result; @@ -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::>(); @@ -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::>(); - 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::>(); - 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, ) -> BuildTableResult<(ParseTable, Vec>)> { ParseTableBuilder::new( @@ -1171,6 +1198,7 @@ pub fn build_parse_table<'a>( lexical_grammar, item_set_builder, variable_info, + str_pool, ) .build(diagnostics) } diff --git a/crates/generate/src/build_tables/coincident_tokens.rs b/crates/generate/src/build_tables/coincident_tokens.rs index 7d7666e61..20eb998b2 100644 --- a/crates/generate/src/build_tables/coincident_tokens.rs +++ b/crates/generate/src/build_tables/coincident_tokens.rs @@ -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>, /// 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, - 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, "}}")?; diff --git a/crates/generate/src/build_tables/item.rs b/crates/generate/src/build_tables/item.rs index bc49e5231..2c605ceb8 100644 --- a/crates/generate/src/build_tables/item.rs +++ b/crates/generate/src/build_tables/item.rs @@ -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 = 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>, 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> = prods - .iter() - .map(|p| vec![DotKeys::default(); p.steps.len() + 1].into_boxed_slice()) - .collect(); + let mut slot_keys: Vec> = (0..slot_count) + .map(|pi| vec![DotKeys::default(); prod(pi).steps.len() + 1].into_boxed_slice()) + .collect::>(); // 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), u32> = FxHashMap::default(); + let mut sym_classes = FxHashMap::default(); for &(pi, dot) in &contents { - let syms: Vec = 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::>(); 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::(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)] + 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 { 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 { - self.step().map(|step| step.symbol) + pub fn step(&self, grammar: &SyntaxGrammar) -> Option { + self.production(grammar) + .steps + .get(self.step_index as usize) + .copied() } #[must_use] - pub fn associativity(&self) -> Option { - self.prev_step().and_then(|step| step.associativity) + pub fn symbol(&self, grammar: &SyntaxGrammar) -> Option { + 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 { + 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 { 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!( diff --git a/crates/generate/src/build_tables/item_set_builder.rs b/crates/generate/src/build_tables/item_set_builder.rs index f0d5d22dc..84ae27600 100644 --- a/crates/generate/src/build_tables/item_set_builder.rs +++ b/crates/generate/src/build_tables/item_set_builder.rs @@ -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, reserved_first_sets: FxHashMap, last_sets: FxHashMap, @@ -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, " }},")?; diff --git a/crates/generate/src/build_tables/minimize_parse_table.rs b/crates/generate/src/build_tables/minimize_parse_table.rs index 395159dec..95fd680b7 100644 --- a/crates/generate/src/build_tables/minimize_parse_table.rs +++ b/crates/generate/src/build_tables/minimize_parse_table.rs @@ -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) } } diff --git a/crates/generate/src/build_tables/token_conflicts.rs b/crates/generate/src/build_tables/token_conflicts.rs index 072b64286..1f2b1a755 100644 --- a/crates/generate/src/build_tables/token_conflicts.rs +++ b/crates/generate/src/build_tables/token_conflicts.rs @@ -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, + #[expect(dead_code, reason = "Debugging aid")] following_tokens: Vec, + #[allow(dead_code, reason = "Debugging/test aid")] starting_chars_by_index: Vec, + #[expect(dead_code, reason = "Debugging aid")] following_chars_by_index: Vec, - 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) -> Self { + pub fn new(grammar: &LexicalGrammar, following_tokens: Vec) -> 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() } } diff --git a/crates/generate/src/generate.rs b/crates/generate/src/generate.rs index 8f770e988..dc65f0bf1 100644 --- a/crates/generate/src/generate.rs +++ b/crates/generate/src/generate.rs @@ -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, variable_info: Vec, + 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, ) -> 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, ) -> GenerateResult { - 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, ) -> GenerateResult { + 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, diff --git a/crates/generate/src/grammars.rs b/crates/generate/src/grammars.rs index e47a91a3c..a2603b8db 100644 --- a/crates/generate/src/grammars.rs +++ b/crates/generate/src/grammars.rs @@ -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, - pub extra_symbols: Vec, - pub expected_conflicts: Vec>, + pub external_roots: Vec, + pub extra_roots: Vec, + pub reserved_sets: Vec, + pub supertype_names: Vec, + pub conflict_names: Vec>, + pub inline_names: Vec, + pub word_name: Option, pub precedence_orderings: Vec>, - pub external_tokens: Vec, - pub variables_to_inline: Vec, - pub supertype_symbols: Vec, - pub word_token: Option, - pub reserved_words: Vec>, } -#[derive(Debug, Default, PartialEq, Eq)] -pub struct ReservedWordContext { - pub name: String, - pub reserved_words: Vec, +#[derive(Clone)] +pub struct ReservedWordContext { + pub name: StrId, + pub roots: Vec, } // 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, } -// 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, - pub alias: Option, - pub field_name: Option, - 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::() == 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) -> 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, + alias: Option, + field: Option, + 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 { + 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) { + 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 { + (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) { + 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 { + (self.field != 0).then(|| StrId::from_raw(self.field)) + } + + pub fn set_field(&mut self, field: Option) { + 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, + 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 { + 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, pub productions: Vec, - pub production_map: HashMap<(*const Production, u32), Vec>, + pub var_prods: Vec<(u32, u32)>, +} + +#[derive(Debug, Default)] +pub struct InlinedProductionMap { + pub map: FxHashMap<(u32, u32), Vec>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SyntaxVariable { - pub name: String, + pub name: StrId, pub kind: VariableType, - pub productions: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ExternalToken { - pub name: String, + pub name: StrId, pub kind: VariableType, pub corresponding_internal_token: Option, } @@ -141,92 +278,41 @@ pub struct SyntaxGrammar { pub word_token: Option, pub precedence_orderings: Vec>, pub reserved_word_sets: Vec, + + pub steps: Vec, + pub productions: Vec, + 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, - ) -> 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 { + 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 { - 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 + 'a> { - self.production_map - .get(&(std::ptr::from_ref::(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) } } diff --git a/crates/generate/src/node_types.rs b/crates/generate/src/node_types.rs index bec5bef9b..48cb46258 100644 --- a/crates/generate/src/node_types.rs +++ b/crates/generate/src/node_types.rs @@ -7,9 +7,12 @@ use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::strpool::StrPool; + use super::{ grammars::{LexicalGrammar, SyntaxGrammar, VariableType}, rules::{Alias, AliasMap, Symbol, SymbolType}, + strpool::StrId, }; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -26,7 +29,7 @@ pub struct FieldInfo { #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct VariableInfo { - pub fields: FxHashMap, + pub fields: FxHashMap, pub children: FieldInfo, pub children_without_fields: FieldInfo, pub has_multi_step_production: bool, @@ -197,6 +200,7 @@ pub fn get_variable_info( syntax_grammar: &SyntaxGrammar, lexical_grammar: &LexicalGrammar, default_aliases: &AliasMap, + str_pool: &StrPool, ) -> VariableInfoResult> { let child_type_is_visible = |t: &ChildType| { variable_type_for_child_type(t, syntax_grammar, lexical_grammar) >= VariableType::Anonymous @@ -215,13 +219,14 @@ pub fn get_variable_info( while did_change { did_change = false; - for (i, variable) in syntax_grammar.variables.iter().enumerate() { + for i in 0..syntax_grammar.variables.len() { let mut variable_info = result[i].clone(); // Examine each of the variable's productions. The variable's child types can be // immediately combined across all productions, but the child quantities must be // recorded separately for each production. - for production in &variable.productions { + for prod_id in syntax_grammar.variable_prod_ids(i) { + let production = syntax_grammar.production(prod_id); let mut production_field_quantities = FxHashMap::default(); let mut production_children_quantity = ChildQuantity::zero(); let mut production_children_without_fields_quantity = ChildQuantity::zero(); @@ -231,8 +236,8 @@ pub fn get_variable_info( variable_info.has_multi_step_production = true; } - for step in &production.steps { - let child_symbol = step.symbol; + for step in production.steps { + let child_symbol = step.symbol(); let child_type = step.child_type(default_aliases); let child_is_hidden = !child_type_is_visible(&child_type) @@ -248,10 +253,10 @@ pub fn get_variable_info( // Maintain the set of child types associated with each field, and the quantity // of children associated with each field in this production. - if let Some(field_name) = &step.field_name { + if let Some(field_name) = step.field() { let field_info = variable_info .fields - .entry(field_name.clone()) + .entry(field_name) .or_insert_with(FieldInfo::default); did_change |= extend_sorted(&mut field_info.types, Some(&child_type)); @@ -293,7 +298,7 @@ pub fn get_variable_info( // If a hidden child has fields, then the parent node can appear to have // those same fields. - for (field_name, child_field_info) in &child_variable_info.fields { + for (&field_name, child_field_info) in &child_variable_info.fields { production_field_quantities .entry(field_name) .or_insert_with(ChildQuantity::zero) @@ -301,7 +306,7 @@ pub fn get_variable_info( did_change |= extend_sorted( &mut variable_info .fields - .entry(field_name.clone()) + .entry(field_name) .or_insert_with(FieldInfo::default) .types, &child_field_info.types, @@ -318,7 +323,7 @@ pub fn get_variable_info( // If a hidden child can have named children without fields, then the parent // node can appear to have those same children. - if step.field_name.is_none() { + if step.field().is_none() { let grandchildren_info = &child_variable_info.children_without_fields; if !grandchildren_info.types.is_empty() { production_children_without_fields_quantity @@ -374,24 +379,28 @@ pub fn get_variable_info( let variable = &syntax_grammar.variables[supertype_symbol.index]; // A symbol can have a multi-step production either directly or via an inlined // anonymous child. In the latter case, we can report a more specific error. - let hidden_child_name = variable - .productions - .iter() - .filter(|production| production.steps.len() == 1) - .find_map(|production| { - let step = &production.steps[0]; - let child_symbol = step.symbol; + + let hidden_child_name = syntax_grammar + .variable_prod_ids(supertype_symbol.index) + .filter(|&prod_id| syntax_grammar.production(prod_id).steps.len() == 1) + .find_map(|prod_id| { + let step = syntax_grammar.production(prod_id).steps[0]; + let child_symbol = step.symbol(); let child_type = step.child_type(default_aliases); let child_is_hidden = !child_type_is_visible(&child_type) && !syntax_grammar.supertype_symbols.contains(&child_symbol); (child_is_hidden && child_symbol.is_non_terminal() && result[child_symbol.index].has_multi_step_production) - .then(|| syntax_grammar.variables[child_symbol.index].name.clone()) + .then(|| { + str_pool + .resolve(syntax_grammar.variables[child_symbol.index].name) + .to_string() + }) }); Err(VariableInfoError::InvalidSupertype(InvalidSupertypeError { - supertype: variable.name.clone(), + supertype: str_pool.resolve(variable.name).to_string(), child: hidden_child_name, }))?; } @@ -423,10 +432,10 @@ fn get_aliases_by_symbol( default_aliases: &AliasMap, ) -> FxHashMap>> { let mut aliases_by_symbol = FxHashMap::default(); - for (symbol, alias) in default_aliases { + for (symbol, &alias) in default_aliases { aliases_by_symbol.insert(*symbol, { let mut aliases = BTreeSet::new(); - aliases.insert(Some(alias.clone())); + aliases.insert(Some(alias)); aliases }); } @@ -438,24 +447,24 @@ fn get_aliases_by_symbol( .insert(None); } } - for variable in &syntax_grammar.variables { - for production in &variable.productions { - for step in &production.steps { + for i in 0..syntax_grammar.variables.len() { + for prod_id in syntax_grammar.variable_prod_ids(i) { + for step in syntax_grammar.production(prod_id).steps { aliases_by_symbol - .entry(step.symbol) + .entry(step.symbol()) .or_insert_with(BTreeSet::new) .insert( - step.alias + step.alias() .as_ref() - .or_else(|| default_aliases.get(&step.symbol)) - .cloned(), + .or_else(|| default_aliases.get(&step.symbol())) + .copied(), ); } } } aliases_by_symbol.insert( Symbol::non_terminal(0), - std::iter::once(&None).cloned().collect(), + std::iter::once(&None).copied().collect(), ); aliases_by_symbol } @@ -516,18 +525,19 @@ pub fn generate_node_types_json( lexical_grammar: &LexicalGrammar, default_aliases: &AliasMap, variable_info: &[VariableInfo], + str_pool: &StrPool, ) -> SuperTypeCycleResult> { let mut node_types_json = BTreeMap::new(); let child_type_to_node_type = |child_type: &ChildType| match child_type { ChildType::Aliased(alias) => NodeTypeJSON { - kind: alias.value.clone(), + kind: str_pool.resolve(alias.value).to_string(), named: alias.is_named, }, ChildType::Normal(symbol) => { if let Some(alias) = default_aliases.get(symbol) { NodeTypeJSON { - kind: alias.value.clone(), + kind: str_pool.resolve(alias.value).to_string(), named: alias.is_named, } } else { @@ -535,21 +545,21 @@ pub fn generate_node_types_json( SymbolType::NonTerminal => { let variable = &syntax_grammar.variables[symbol.index]; NodeTypeJSON { - kind: variable.name.clone(), + kind: str_pool.resolve(variable.name).to_string(), named: variable.kind != VariableType::Anonymous, } } SymbolType::Terminal => { let variable = &lexical_grammar.variables[symbol.index]; NodeTypeJSON { - kind: variable.name.clone(), + kind: str_pool.resolve(variable.name).to_string(), named: variable.kind != VariableType::Anonymous, } } SymbolType::External => { let variable = &syntax_grammar.external_tokens[symbol.index]; NodeTypeJSON { - kind: variable.name.clone(), + kind: str_pool.resolve(variable.name).to_string(), named: variable.kind != VariableType::Anonymous, } } @@ -606,9 +616,9 @@ pub fn generate_node_types_json( if syntax_grammar.supertype_symbols.contains(&symbol) { let node_type_json = node_types_json - .entry(variable.name.clone()) + .entry(variable.name) .or_insert_with(|| NodeInfoJSON { - kind: variable.name.clone(), + kind: str_pool.resolve(variable.name).to_string(), named: true, root: false, extra: extra_names.contains(&variable.name), @@ -655,10 +665,10 @@ pub fn generate_node_types_json( // There may already be an entry with this name, because multiple // rules may be aliased with the same name. let mut node_type_existed = true; - let node_type_json = node_types_json.entry(kind.clone()).or_insert_with(|| { + let node_type_json = node_types_json.entry(*kind).or_insert_with(|| { node_type_existed = false; NodeInfoJSON { - kind: kind.clone(), + kind: str_pool.resolve(*kind).to_string(), named: is_named, root: i == 0, extra: extra_names.contains(&kind), @@ -670,22 +680,28 @@ pub fn generate_node_types_json( let fields_json = node_type_json.fields.as_mut().unwrap(); for (new_field, field_info) in &info.fields { - let field_json = fields_json.entry(new_field.clone()).or_insert_with(|| { - // If another rule is aliased with the same name, and does *not* have this - // field, then this field cannot be required. - let mut field_json = FieldInfoJSON::default(); - if node_type_existed { - field_json.required = false; - } - field_json - }); + let field_json = fields_json + .entry(str_pool.resolve(*new_field).to_string()) + .or_insert_with(|| { + // If another rule is aliased with the same name, and does *not* have this + // field, then this field cannot be required. + let mut field_json = FieldInfoJSON::default(); + if node_type_existed { + field_json.required = false; + } + field_json + }); populate_field_info_json(field_json, field_info); } // If another rule is aliased with the same name, any fields that aren't present in // this cannot be required. for (existing_field, field_json) in fields_json.iter_mut() { - if !info.fields.contains_key(existing_field) { + if !info + .fields + .keys() + .any(|&f| str_pool.resolve(f).eq(existing_field)) + { field_json.required = false; } } @@ -784,21 +800,18 @@ pub fn generate_node_types_json( }) }); - for (name, kind) in regular_tokens.chain(external_tokens) { + for (&name, kind) in regular_tokens.chain(external_tokens) { match kind { VariableType::Named => { - let node_type_json = - node_types_json - .entry(name.clone()) - .or_insert_with(|| NodeInfoJSON { - kind: name.clone(), - named: true, - root: false, - extra: extra_names.contains(&name), - fields: None, - children: None, - subtypes: None, - }); + let node_type_json = node_types_json.entry(name).or_insert_with(|| NodeInfoJSON { + kind: str_pool.resolve(name).to_string(), + named: true, + root: false, + extra: extra_names.contains(&name), + fields: None, + children: None, + subtypes: None, + }); if let Some(children) = &mut node_type_json.children { children.required = false; } @@ -809,7 +822,7 @@ pub fn generate_node_types_json( } } VariableType::Anonymous => anonymous_node_types.push(NodeInfoJSON { - kind: name.clone(), + kind: str_pool.resolve(name).to_string(), named: false, root: false, extra: extra_names.contains(&name), @@ -895,35 +908,45 @@ mod tests { grammars::{ InputGrammar, LexicalVariable, Production, ProductionStep, SyntaxVariable, Variable, }, - prepare_grammar::prepare_grammar, - rules::Rule, + prepare_grammar::{PreparedGrammar, prepare_grammar}, + rules::{Alias, Precedence, Rule, RuleId, RulePool}, + strpool::StrPool, }; #[test] fn test_node_types_simple() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let v1 = { + let f1 = { + let v2 = named(&mut pool, "v2"); + field(&mut pool, "f1", v2) + }; + let f2 = { + let semi = string(&mut pool, ";"); + field(&mut pool, "f2", semi) + }; + pool.seq(&[f1, f2]) + }; + let v2 = string(&mut pool, "x"); + let v3 = string(&mut pool, "y"); + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("f1".to_string(), Rule::named("v2")), - Rule::field("f2".to_string(), Rule::string(";")), - ]), + name: pool.intern("v1"), + root: v1, }, Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v2"), + root: v2, }, // This rule is not reachable from the start symbol // so it won't be present in the node_types Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("y"), + name: pool.intern("v3"), + root: v3, }, ], + pool, ..Default::default() }) .unwrap(); @@ -997,21 +1020,31 @@ mod tests { #[test] fn test_node_types_simple_extras() { - let node_types = get_node_types(&InputGrammar { - extra_symbols: vec![Rule::named("v3")], + let mut pool = RulePool::default(); + let v1 = { + let f1 = { + let v2 = named(&mut pool, "v2"); + field(&mut pool, "f1", v2) + }; + let f2 = { + let semi = string(&mut pool, ";"); + field(&mut pool, "f2", semi) + }; + pool.seq(&[f1, f2]) + }; + let v2 = string(&mut pool, "x"); + let v3 = string(&mut pool, "y"); + let extra = named(&mut pool, "v3"); + let node_types = get_node_types(InputGrammar { + extra_roots: vec![extra], variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("f1".to_string(), Rule::named("v2")), - Rule::field("f2".to_string(), Rule::string(";")), - ]), + name: pool.intern("v1"), + root: v1, }, Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v2"), + root: v2, }, // This rule is not reachable from the start symbol, but // it is reachable from the 'extra_symbols' so it @@ -1019,11 +1052,11 @@ mod tests { // But because it's only a literal, it will get replaced by // a lexical variable. Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("y"), + name: pool.intern("v3"), + root: v3, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1109,21 +1142,38 @@ mod tests { #[test] fn test_node_types_deeper_extras() { - let node_types = get_node_types(&InputGrammar { - extra_symbols: vec![Rule::named("v3")], + let mut pool = RulePool::default(); + let v1 = { + let f1 = { + let v2 = named(&mut pool, "v2"); + field(&mut pool, "f1", v2) + }; + let f2 = { + let semi = string(&mut pool, ";"); + field(&mut pool, "f2", semi) + }; + pool.seq(&[f1, f2]) + }; + let v2 = string(&mut pool, "x"); + let v3 = { + let y = string(&mut pool, "y"); + let z = { + let z = string(&mut pool, "z"); + pool.repeat(z) + }; + pool.seq(&[y, z]) + }; + let extra = named(&mut pool, "v3"); + let node_types = get_node_types(InputGrammar { + extra_roots: vec![extra], variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("f1".to_string(), Rule::named("v2")), - Rule::field("f2".to_string(), Rule::string(";")), - ]), + name: pool.intern("v1"), + root: v1, }, Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v2"), + root: v2, }, // This rule is not reachable from the start symbol, but // it is reachable from the 'extra_symbols' so it @@ -1131,11 +1181,11 @@ mod tests { // Because it is not just a literal, it won't get replaced // by a lexical variable. Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![Rule::string("y"), Rule::repeat(Rule::string("z"))]), + name: pool.intern("v3"), + root: v3, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1221,34 +1271,42 @@ mod tests { #[test] fn test_node_types_with_supertypes() { - let node_types = get_node_types(&InputGrammar { - supertype_symbols: vec!["_v2".to_string()], + let mut pool = RulePool::default(); + let v1 = { + let inner = named(&mut pool, "_v2"); + field(&mut pool, "f1", inner) + }; + let v2 = { + let (a, b, c) = ( + named(&mut pool, "v3"), + named(&mut pool, "v4"), + string(&mut pool, "*"), + ); + pool.choice(&[a, b, c]) + }; + let v3 = string(&mut pool, "x"); + let v4 = string(&mut pool, "y"); + let node_types = get_node_types(InputGrammar { + supertype_names: vec![pool.intern("_v2")], variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::field("f1".to_string(), Rule::named("_v2")), + name: pool.intern("v1"), + root: v1, }, Variable { - name: "_v2".to_string(), - kind: VariableType::Hidden, - rule: Rule::choice(vec![ - Rule::named("v3"), - Rule::named("v4"), - Rule::string("*"), - ]), + name: pool.intern("_v2"), + root: v2, }, Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v3"), + root: v3, }, Variable { - name: "v4".to_string(), - kind: VariableType::Named, - rule: Rule::string("y"), + name: pool.intern("v4"), + root: v4, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1312,38 +1370,47 @@ mod tests { /// in the topological sort. #[test] fn test_node_types_supertype_with_only_hidden_child() { - let node_types = get_node_types(&InputGrammar { - supertype_symbols: vec!["_type_a".to_string(), "_type_b".to_string()], + let mut pool = RulePool::default(); + let v1 = { + let (a, b) = (named(&mut pool, "_type_a"), named(&mut pool, "_type_b")); + pool.seq(&[a, b]) + }; + let type_a = { + let (a, b) = (named(&mut pool, "v2"), named(&mut pool, "v3")); + pool.choice(&[a, b]) + }; + let v2 = string(&mut pool, "x"); + let v3 = string(&mut pool, "y"); + let type_b = external(&mut pool, 0); + let hidden_ext = named(&mut pool, "_hidden_ext"); + let node_types = get_node_types(InputGrammar { + supertype_names: vec![pool.intern("_type_a"), pool.intern("_type_b")], + external_roots: vec![hidden_ext], variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![Rule::named("_type_a"), Rule::named("_type_b")]), + name: pool.intern("v1"), + root: v1, }, // Supertype A: a normal choice of named subtypes Variable { - name: "_type_a".to_string(), - kind: VariableType::Hidden, - rule: Rule::choice(vec![Rule::named("v2"), Rule::named("v3")]), + name: pool.intern("_type_a"), + root: type_a, }, Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v2"), + root: v2, }, Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("y"), + name: pool.intern("v3"), + root: v3, }, // Supertype B: a hidden external token with no subtypes Variable { - name: "_type_b".to_string(), - kind: VariableType::Hidden, - rule: Rule::external(0), + name: pool.intern("_type_b"), + root: type_b, }, ], - external_tokens: vec![Rule::named("_hidden_ext")], + pool, ..Default::default() }); assert!(node_types.is_ok()); @@ -1351,37 +1418,48 @@ mod tests { #[test] fn test_node_types_for_children_without_fields() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let v1 = { + let a = named(&mut pool, "v2"); + let f1 = { + let v3 = named(&mut pool, "v3"); + field(&mut pool, "f1", v3) + }; + let c = named(&mut pool, "v4"); + pool.seq(&[a, f1, c]) + }; + let v2 = { + let open = string(&mut pool, "{"); + let mid = { + let v3 = named(&mut pool, "v3"); + let blank = pool.blank(); + pool.choice(&[v3, blank]) + }; + let close = string(&mut pool, "}"); + pool.seq(&[open, mid, close]) + }; + let v3 = string(&mut pool, "x"); + let v4 = string(&mut pool, "y"); + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::named("v2"), - Rule::field("f1".to_string(), Rule::named("v3")), - Rule::named("v4"), - ]), + name: pool.intern("v1"), + root: v1, }, Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::string("{"), - Rule::choice(vec![Rule::named("v3"), Rule::Blank]), - Rule::string("}"), - ]), + name: pool.intern("v2"), + root: v2, }, Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("x"), + name: pool.intern("v3"), + root: v3, }, Variable { - name: "v4".to_string(), - kind: VariableType::Named, - rule: Rule::string("y"), + name: pool.intern("v4"), + root: v4, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1448,26 +1526,34 @@ mod tests { #[test] fn test_node_types_with_inlined_rules() { - let node_types = get_node_types(&InputGrammar { - variables_to_inline: vec!["v2".to_string()], + let mut pool = RulePool::default(); + let v1 = { + let (a, b) = (named(&mut pool, "v2"), named(&mut pool, "v3")); + pool.seq(&[a, b]) + }; + let v2 = { + let a = string(&mut pool, "a"); + alias(&mut pool, a, "x", true) + }; + let v3 = string(&mut pool, "b"); + let node_types = get_node_types(InputGrammar { + inline_names: vec![pool.intern("v2")], variables: vec![ Variable { - name: "v1".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![Rule::named("v2"), Rule::named("v3")]), + name: pool.intern("v1"), + root: v1, }, // v2 should not appear in the node types, since it is inlined Variable { - name: "v2".to_string(), - kind: VariableType::Named, - rule: Rule::alias(Rule::string("a"), "x".to_string(), true), + name: pool.intern("v2"), + root: v2, }, Variable { - name: "v3".to_string(), - kind: VariableType::Named, - rule: Rule::string("b"), + name: pool.intern("v3"), + root: v3, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1501,48 +1587,53 @@ mod tests { #[test] fn test_node_types_for_aliased_nodes() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let thing = { + let (a, b) = (named(&mut pool, "type"), named(&mut pool, "expression")); + pool.choice(&[a, b]) + }; + let ty = { + let id = { + let inner = named(&mut pool, "identifier"); + alias(&mut pool, inner, "type_identifier", true) + }; + let void = string(&mut pool, "void"); + pool.choice(&[id, void]) + }; + let expression = { + let id = named(&mut pool, "identifier"); + let foo = { + let inner = named(&mut pool, "foo_identifier"); + alias(&mut pool, inner, "identifier", true) + }; + pool.choice(&[id, foo]) + }; + let identifier = pattern(&mut pool, "\\w+"); + let foo_identifier = pattern(&mut pool, "[\\w-]+"); + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "thing".to_string(), - kind: VariableType::Named, - rule: Rule::choice(vec![Rule::named("type"), Rule::named("expression")]), + name: pool.intern("thing"), + root: thing, }, Variable { - name: "type".to_string(), - kind: VariableType::Named, - rule: Rule::choice(vec![ - Rule::alias( - Rule::named("identifier"), - "type_identifier".to_string(), - true, - ), - Rule::string("void"), - ]), + name: pool.intern("type"), + root: ty, }, Variable { - name: "expression".to_string(), - kind: VariableType::Named, - rule: Rule::choice(vec![ - Rule::named("identifier"), - Rule::alias( - Rule::named("foo_identifier"), - "identifier".to_string(), - true, - ), - ]), + name: pool.intern("expression"), + root: expression, }, Variable { - name: "identifier".to_string(), - kind: VariableType::Named, - rule: Rule::pattern("\\w+", ""), + name: pool.intern("identifier"), + root: identifier, }, Variable { - name: "foo_identifier".to_string(), - kind: VariableType::Named, - rule: Rule::pattern("[\\w-]+", ""), + name: pool.intern("foo_identifier"), + root: foo_identifier, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1576,30 +1667,43 @@ mod tests { #[test] fn test_node_types_with_multiple_valued_fields() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let a = { + let first = { + let blank = pool.blank(); + let rep = { + let f1 = { + let b = named(&mut pool, "b"); + field(&mut pool, "f1", b) + }; + pool.repeat(f1) + }; + pool.choice(&[blank, rep]) + }; + let second = { + let c = named(&mut pool, "c"); + pool.repeat(c) + }; + pool.seq(&[first, second]) + }; + let b = string(&mut pool, "b"); + let c = string(&mut pool, "c"); + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "a".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::choice(vec![ - Rule::Blank, - Rule::repeat(Rule::field("f1".to_string(), Rule::named("b"))), - ]), - Rule::repeat(Rule::named("c")), - ]), + name: pool.intern("a"), + root: a, }, Variable { - name: "b".to_string(), - kind: VariableType::Named, - rule: Rule::string("b"), + name: pool.intern("b"), + root: b, }, Variable { - name: "c".to_string(), - kind: VariableType::Named, - rule: Rule::string("c"), + name: pool.intern("c"), + root: c, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1641,15 +1745,24 @@ mod tests { #[test] fn test_node_types_with_fields_on_hidden_tokens() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let script = { + let a = { + let pat = pattern(&mut pool, "hi"); + field(&mut pool, "a", pat) + }; + let b = { + let pat = pattern(&mut pool, "bye"); + field(&mut pool, "b", pat) + }; + pool.seq(&[a, b]) + }; + let node_types = get_node_types(InputGrammar { variables: vec![Variable { - name: "script".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("a".to_string(), Rule::pattern("hi", "")), - Rule::field("b".to_string(), Rule::pattern("bye", "")), - ]), + name: pool.intern("script"), + root: script, }], + pool, ..Default::default() }) .unwrap(); @@ -1670,35 +1783,57 @@ mod tests { #[test] fn test_node_types_with_multiple_rules_same_alias_name() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let script = { + let a = named(&mut pool, "a"); + let b = { + let inner = named(&mut pool, "b"); + alias(&mut pool, inner, "a", true) + }; + pool.choice(&[a, b]) + }; + let a = { + let f1 = { + let s = string(&mut pool, "1"); + field(&mut pool, "f1", s) + }; + let f2 = { + let s = string(&mut pool, "2"); + field(&mut pool, "f2", s) + }; + pool.seq(&[f1, f2]) + }; + let b = { + let f2a = { + let s = string(&mut pool, "22"); + field(&mut pool, "f2", s) + }; + let f2b = { + let s = string(&mut pool, "222"); + field(&mut pool, "f2", s) + }; + let f3 = { + let s = string(&mut pool, "3"); + field(&mut pool, "f3", s) + }; + pool.seq(&[f2a, f2b, f3]) + }; + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "script".to_string(), - kind: VariableType::Named, - rule: Rule::choice(vec![ - Rule::named("a"), - // Rule `b` is aliased as rule `a` - Rule::alias(Rule::named("b"), "a".to_string(), true), - ]), + name: pool.intern("script"), + root: script, }, Variable { - name: "a".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("f1".to_string(), Rule::string("1")), - Rule::field("f2".to_string(), Rule::string("2")), - ]), + name: pool.intern("a"), + root: a, }, Variable { - name: "b".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("f2".to_string(), Rule::string("22")), - Rule::field("f2".to_string(), Rule::string("222")), - Rule::field("f3".to_string(), Rule::string("3")), - ]), + name: pool.intern("b"), + root: b, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1795,30 +1930,48 @@ mod tests { #[test] fn test_node_types_with_tokens_aliased_to_match_rules() { - let node_types = get_node_types(&InputGrammar { + let mut pool = RulePool::default(); + let a = { + let (b, c) = (named(&mut pool, "b"), named(&mut pool, "c")); + pool.seq(&[b, c]) + }; + let b = { + let (c1, mid, c2) = ( + named(&mut pool, "c"), + string(&mut pool, "B"), + named(&mut pool, "c"), + ); + pool.seq(&[c1, mid, c2]) + }; + let c = { + let cc = string(&mut pool, "C"); + let d = { + // This token is aliased as a `b`, which will produce a `b` node + // with no children. + let inner = string(&mut pool, "D"); + alias(&mut pool, inner, "b", true) + }; + pool.choice(&[cc, d]) + }; + + // above Alias D + let node_types = get_node_types(InputGrammar { variables: vec![ Variable { - name: "a".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![Rule::named("b"), Rule::named("c")]), + name: pool.intern("a"), + root: a, }, // Ordinarily, `b` nodes have two named `c` children. Variable { - name: "b".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![Rule::named("c"), Rule::string("B"), Rule::named("c")]), + name: pool.intern("b"), + root: b, }, Variable { - name: "c".to_string(), - kind: VariableType::Named, - rule: Rule::choice(vec![ - Rule::string("C"), - // This token is aliased as a `b`, which will produce a `b` node - // with no children. - Rule::alias(Rule::string("D"), "b".to_string(), true), - ]), + name: pool.intern("c"), + root: c, }, ], + pool, ..Default::default() }) .unwrap(); @@ -1850,335 +2003,354 @@ mod tests { #[test] fn test_get_variable_info() { - let variable_info = get_variable_info( - &build_syntax_grammar( - vec![ - // Required field `field1` has only one node type. - SyntaxVariable { - name: "rule0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(0)), - ProductionStep::new(Symbol::non_terminal(1)) - .with_field_name("field1"), - ], - }], - }, - // Hidden node - SyntaxVariable { - name: "_rule1".to_string(), - kind: VariableType::Hidden, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(1))], - }], - }, - // Optional field `field2` can have two possible node types. - SyntaxVariable { - name: "rule2".to_string(), - kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(0))], - }, - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(0)), - ProductionStep::new(Symbol::terminal(2)) - .with_field_name("field2"), - ], - }, - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(0)), - ProductionStep::new(Symbol::terminal(3)) - .with_field_name("field2"), - ], - }, + let mut interner = StrPool::default(); + let field1 = interner.intern("field1"); + let field2 = interner.intern("field2"); + let lexical_grammar = build_lexical_grammar(&mut interner); + let grammar = build_syntax_grammar( + &mut interner, + vec![ + // Required field `field1` has only one node type. + ( + "rule0", + VariableType::Named, + vec![vec![ + step(Symbol::terminal(0), None), + step(Symbol::non_terminal(1), Some(field1)), + ]], + ), + // Hidden node + ( + "_rule1", + VariableType::Hidden, + vec![vec![step(Symbol::terminal(1), None)]], + ), + // Optional field `field2` can have two possible node types. + ( + "rule2", + VariableType::Named, + vec![ + vec![step(Symbol::terminal(0), None)], + vec![ + step(Symbol::terminal(0), None), + step(Symbol::terminal(2), Some(field2)), ], - }, - ], - vec![], - ), - &build_lexical_grammar(), - &AliasMap::new(), - ) - .unwrap(); + vec![ + step(Symbol::terminal(0), None), + step(Symbol::terminal(3), Some(field2)), + ], + ], + ), + ], + vec![], + ); + let variable_info = + get_variable_info(&grammar, &lexical_grammar, &AliasMap::new(), &interner).unwrap(); assert_eq!( variable_info[0].fields, vec![( - "field1".to_string(), + field1, FieldInfo { quantity: ChildQuantity { exists: true, required: true, - multiple: false, + multiple: false }, - types: vec![ChildType::Normal(Symbol::terminal(1))], + types: vec![ChildType::Normal(Symbol::terminal(1))] } )] .into_iter() - .collect::>() + .collect() ); - assert_eq!( variable_info[2].fields, vec![( - "field2".to_string(), + field2, FieldInfo { quantity: ChildQuantity { exists: true, required: false, - multiple: false, + multiple: false }, types: vec![ ChildType::Normal(Symbol::terminal(2)), - ChildType::Normal(Symbol::terminal(3)), - ], + ChildType::Normal(Symbol::terminal(3)) + ] } )] .into_iter() - .collect::>() + .collect() ); } #[test] fn test_get_variable_info_with_repetitions_inside_fields() { - let variable_info = get_variable_info( - &build_syntax_grammar( - vec![ - // Field associated with a repetition. - SyntaxVariable { - name: "rule0".to_string(), - kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)) - .with_field_name("field1"), - ], - }, - Production { - dynamic_precedence: 0, - steps: vec![], - }, + let mut interner = StrPool::default(); + let field1 = interner.intern("field1"); + let lexical_grammar = build_lexical_grammar(&mut interner); + let grammar = build_syntax_grammar( + &mut interner, + vec![ + // Field associated with a repetiation. + ( + "rule0", + VariableType::Named, + vec![vec![step(Symbol::non_terminal(1), Some(field1))], vec![]], + ), + ( + "_rule0_repeat", + VariableType::Hidden, + vec![ + vec![step(Symbol::terminal(1), None)], + vec![ + step(Symbol::non_terminal(1), None), + step(Symbol::non_terminal(1), None), ], - }, - // Repetition node - SyntaxVariable { - name: "_rule0_repeat".to_string(), - kind: VariableType::Hidden, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(1))], - }, - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::non_terminal(1)), - ], - }, - ], - }, - ], - vec![], - ), - &build_lexical_grammar(), - &AliasMap::new(), - ) - .unwrap(); + ], + ), + ], + vec![], + ); + let variable_info = + get_variable_info(&grammar, &lexical_grammar, &AliasMap::new(), &interner).unwrap(); assert_eq!( variable_info[0].fields, vec![( - "field1".to_string(), + field1, FieldInfo { quantity: ChildQuantity { exists: true, required: false, - multiple: true, + multiple: true }, types: vec![ChildType::Normal(Symbol::terminal(1))], } )] .into_iter() - .collect::>() + .collect() ); } #[test] fn test_get_variable_info_with_inherited_fields() { - let variable_info = get_variable_info( - &build_syntax_grammar( - vec![ - SyntaxVariable { - name: "rule0".to_string(), - kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(0)), - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::terminal(1)), - ], - }, - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::non_terminal(1))], - }, + let mut interner = StrPool::default(); + let field1 = interner.intern("field1"); + let dot = interner.intern("."); + let lexical_grammar = build_lexical_grammar(&mut interner); + let grammar = build_syntax_grammar( + &mut interner, + vec![ + ( + "rule0", + VariableType::Named, + vec![ + vec![ + step(Symbol::terminal(0), None), + step(Symbol::non_terminal(1), None), + step(Symbol::terminal(1), None), ], - }, - // Hidden node with fields - SyntaxVariable { - name: "_rule1".to_string(), - kind: VariableType::Hidden, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(2)).with_alias(".", false), - ProductionStep::new(Symbol::terminal(3)).with_field_name("field1"), - ], - }], - }, - ], - vec![], - ), - &build_lexical_grammar(), - &AliasMap::new(), - ) - .unwrap(); + vec![step(Symbol::non_terminal(1), None)], + ], + ), + // Hidden node with fields + ( + "_rule1", + VariableType::Hidden, + vec![vec![ + ProductionStep::pack( + Symbol::terminal(2), + Precedence::None, + None, + Some(Alias { + value: dot, + is_named: false, + }), + None, + ProductionStep::NO_RESERVED_WORDS, + ), + step(Symbol::terminal(3), Some(field1)), + ]], + ), + ], + vec![], + ); + let variable_info = + get_variable_info(&grammar, &lexical_grammar, &AliasMap::new(), &interner).unwrap(); assert_eq!( variable_info[0].fields, vec![( - "field1".to_string(), + field1, FieldInfo { quantity: ChildQuantity { exists: true, required: true, - multiple: false, + multiple: false }, - types: vec![ChildType::Normal(Symbol::terminal(3))], + types: vec![ChildType::Normal(Symbol::terminal(3))] } )] .into_iter() - .collect::>() + .collect() ); - assert_eq!( variable_info[0].children_without_fields, FieldInfo { quantity: ChildQuantity { exists: true, required: false, - multiple: true, + multiple: true }, types: vec![ ChildType::Normal(Symbol::terminal(0)), - ChildType::Normal(Symbol::terminal(1)), - ], + ChildType::Normal(Symbol::terminal(1)) + ] } ); } #[test] fn test_get_variable_info_with_supertypes() { - let variable_info = get_variable_info( - &build_syntax_grammar( - vec![ - SyntaxVariable { - name: "rule0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(0)), - ProductionStep::new(Symbol::non_terminal(1)) - .with_field_name("field1"), - ProductionStep::new(Symbol::terminal(1)), - ], - }], - }, - SyntaxVariable { - name: "_rule1".to_string(), - kind: VariableType::Hidden, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(2))], - }, - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(3))], - }, - ], - }, - ], - // _rule1 is a supertype - vec![Symbol::non_terminal(1)], - ), - &build_lexical_grammar(), - &AliasMap::new(), - ) - .unwrap(); + let mut interner = StrPool::default(); + let field1 = interner.intern("field1"); + let lexical_grammar = build_lexical_grammar(&mut interner); + let grammar = build_syntax_grammar( + &mut interner, + vec![ + ( + "rule0", + VariableType::Named, + vec![vec![ + step(Symbol::terminal(0), None), + step(Symbol::non_terminal(1), Some(field1)), + step(Symbol::terminal(1), None), + ]], + ), + ( + "_rule1", + VariableType::Hidden, + vec![ + vec![step(Symbol::terminal(2), None)], + vec![step(Symbol::terminal(3), None)], + ], + ), + ], + // _rule1 is a supertype + vec![Symbol::non_terminal(1)], + ); + let variable_info = + get_variable_info(&grammar, &lexical_grammar, &AliasMap::new(), &interner).unwrap(); assert_eq!( variable_info[0].fields, vec![( - "field1".to_string(), + field1, FieldInfo { quantity: ChildQuantity { exists: true, required: true, - multiple: false, + multiple: false }, - types: vec![ChildType::Normal(Symbol::non_terminal(1))], + types: vec![ChildType::Normal(Symbol::non_terminal(1))] } )] .into_iter() - .collect::>() + .collect() ); } - fn get_node_types(grammar: &InputGrammar) -> SuperTypeCycleResult> { - let (syntax_grammar, lexical_grammar, _, default_aliases) = - prepare_grammar(grammar, &mut Vec::new()).unwrap(); - let variable_info = - get_variable_info(&syntax_grammar, &lexical_grammar, &default_aliases).unwrap(); + fn get_node_types(grammar: InputGrammar) -> SuperTypeCycleResult> { + let PreparedGrammar { + syntax_grammar, + lexical_grammar, + default_aliases, + str_pool, + .. + } = prepare_grammar(grammar, &mut Vec::new()).unwrap(); + let variable_info = get_variable_info( + &syntax_grammar, + &lexical_grammar, + &default_aliases, + &str_pool, + ) + .unwrap(); generate_node_types_json( &syntax_grammar, &lexical_grammar, &default_aliases, &variable_info, + &str_pool, ) } + fn named(p: &mut RulePool, name: &str) -> RuleId { + let name = p.intern(name); + p.named_symbol(name) + } + fn string(p: &mut RulePool, value: &str) -> RuleId { + let value = p.intern(value); + p.string(value) + } + fn pattern(p: &mut RulePool, value: &str) -> RuleId { + let (value, flags) = (p.intern(value), p.intern("")); + p.pattern(value, flags) + } + fn field(p: &mut RulePool, name: &str, content: RuleId) -> RuleId { + let name = p.intern(name); + p.field(name, content) + } + fn alias(p: &mut RulePool, content: RuleId, value: &str, is_named: bool) -> RuleId { + let value = p.intern(value); + p.alias(content, value, is_named) + } + fn external(p: &mut RulePool, index: u32) -> RuleId { + p.push_node(Rule::Sym { + kind: SymbolType::External, + index, + }) + } + fn build_syntax_grammar( - variables: Vec, + interner: &mut StrPool, + variables: Vec<(&str, VariableType, Vec>)>, supertype_symbols: Vec, ) -> SyntaxGrammar { + let (mut steps, mut productions, mut var_prods, mut vars) = + (Vec::new(), Vec::new(), Vec::new(), Vec::new()); + for (name, kind, prods) in variables { + let prod_start = productions.len() as u32; + for prod_steps in prods { + let steps_start = steps.len() as u32; + steps.extend(prod_steps); + productions.push(Production { + steps_start, + steps_len: steps.len() as u32 - steps_start, + dynamic_precedence: 0, + }); + } + var_prods.push((prod_start, productions.len() as u32)); + vars.push(SyntaxVariable { + name: interner.intern(name), + kind, + }); + } SyntaxGrammar { - variables, + variables: vars, supertype_symbols, - ..SyntaxGrammar::default() + steps, + productions, + var_prods, + ..Default::default() } } - fn build_lexical_grammar() -> LexicalGrammar { + fn build_lexical_grammar(interner: &mut StrPool) -> LexicalGrammar { let mut lexical_grammar = LexicalGrammar::default(); for i in 0..10 { lexical_grammar.variables.push(LexicalVariable { - name: format!("token_{i}"), + name: interner.intern(&format!("token_{i}")), kind: VariableType::Named, implicit_precedence: 0, start_state: 0, @@ -2186,4 +2358,15 @@ mod tests { } lexical_grammar } + + fn step(symbol: Symbol, field: Option) -> ProductionStep { + ProductionStep::pack( + symbol, + Precedence::None, + None, + None, + field, + ProductionStep::NO_RESERVED_WORDS, + ) + } } diff --git a/crates/generate/src/parse_grammar.rs b/crates/generate/src/parse_grammar.rs index ed9445ebc..e9805e76b 100644 --- a/crates/generate/src/parse_grammar.rs +++ b/crates/generate/src/parse_grammar.rs @@ -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 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 = { - 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 = { + let by_name: FxHashMap = + self.variables.iter().map(|v| (v.name, v.root)).collect(); + let mut visited: FxHashSet = FxHashSet::default(); + let mut stack: Vec = 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 = self + // Drop unused variables and clean up references to them in the config + let dropped: Vec = 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, ) -> ParseGrammarResult { let grammar_json = serde_json::from_str::(input)?; + let mut pool = RulePool::default(); - let extra_symbols = + let extra_roots = grammar_json .extras .into_iter() - .try_fold(Vec::::new(), |mut acc, item| { - let rule = parse_rule(item, false, diagnostics)?; - if let Rule::String(ref value) = rule - && value.is_empty() + .try_fold(Vec::::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::>>()?; 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::>>()?; - 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::>>()?; + 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, -) -> ParseGrammarResult { - 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, + ) -> ParseGrammarResult { + 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::>>()?; + Ok(self.choice(&members)) + } + RuleJSON::SEQ { members } => { + let members = members + .into_iter() + .map(|m| self.parse_rule(m, is_token, diagnostics)) + .collect::>>()?; + 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::>>() - .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::>>() - .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 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::>(); + 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"); } } diff --git a/crates/generate/src/prepare_grammar.rs b/crates/generate/src/prepare_grammar.rs index e19070dd2..61d22ada8 100644 --- a/crates/generate/src/prepare_grammar.rs +++ b/crates/generate/src/prepare_grammar.rs @@ -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 { - variables: Vec, - extra_symbols: Vec, - expected_conflicts: Vec>, - precedence_orderings: Vec>, - external_tokens: Vec, - variables_to_inline: Vec, - supertype_symbols: Vec, - word_token: Option, - reserved_word_sets: Vec>, -} - -pub type InternedGrammar = IntermediateGrammar; - -pub type ExtractedSyntaxGrammar = IntermediateGrammar; - -#[derive(Debug, PartialEq, Eq)] -pub struct ExtractedLexicalGrammar { - pub variables: Vec, - pub separators: Vec, -} - -impl Default for IntermediateGrammar { - 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 = Result; @@ -91,14 +59,14 @@ pub enum PrepareGrammarError { pub type ValidatePrecedenceResult = Result; -#[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); 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, -) -> PrepareGrammarResult<( - SyntaxGrammar, - LexicalGrammar, - InlinedProductionMap, - AliasMap, -)> { - validate_precedences(input_grammar)?; - validate_indirect_recursion(input_grammar)?; +) -> PrepareGrammarResult { + 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> = 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 = 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 { - 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>, - visited: &mut BTreeSet<&'a str>, - path: &mut Vec<&'a str>, +fn get_cycle( + current: StrId, + transitions: &IndexMap>, + visited: &mut BTreeSet, + path: &mut Vec, ) -> 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(¤t) { return None; } path.push(current); visited.insert(current); - if let Some(next_symbols) = transitions.get(current) { + if let Some(next_symbols) = transitions.get(¤t) { 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) = ¶ms.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::>(); + .collect::>(); + + 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) -> InputGrammar { + let mut pool = RulePool::default(); + let variables = build(&mut pool); + InputGrammar { + pool, + variables, + ..Default::default() + } + } } diff --git a/crates/generate/src/prepare_grammar/expand_repeats.rs b/crates/generate/src/prepare_grammar/expand_repeats.rs index b6bc1807d..7a8d69d79 100644 --- a/crates/generate/src/prepare_grammar/expand_repeats.rs +++ b/crates/generate/src/prepare_grammar/expand_repeats.rs @@ -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, - existing_repeats: FxHashMap, + preceding: usize, + aux: Vec, + memo: FxHashMap>, + stack: Vec, +} + +#[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::>(); + 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::>(); + 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::>(); + 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::>(); + 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) -> 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, + kinds: Vec, + ) -> (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) } } diff --git a/crates/generate/src/prepare_grammar/expand_tokens.rs b/crates/generate/src/prepare_grammar/expand_tokens.rs index c8d7d28d1..f3b0cce65 100644 --- a/crates/generate/src/prepare_grammar/expand_tokens.rs +++ b/crates/generate/src/prepare_grammar/expand_tokens.rs @@ -5,11 +5,11 @@ use regex_syntax::{ use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::ExtractedLexicalGrammar; use crate::{ grammars::{LexicalGrammar, LexicalVariable}, nfa::{CharacterSet, Nfa, NfaState}, - rules::{Precedence, Rule}, + prepare_grammar::extract_tokens::LexicalToken, + rules::{Precedence, Rule, RuleId, RulePool, Symbol}, }; struct NfaBuilder { @@ -21,7 +21,7 @@ struct NfaBuilder { pub type ExpandTokensResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ExpandTokensError { #[error( "The rule `{0}` matches the empty string. @@ -36,7 +36,7 @@ unless they are used only as the grammar's start rule. ExpandRule(ExpandRuleError), } -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub struct ExpandTokensProcessingError { rule: String, error: ExpandRuleError, @@ -44,75 +44,72 @@ pub struct ExpandTokensProcessingError { impl std::fmt::Display for ExpandTokensProcessingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!( - f, - "Error processing rule {}: Grammar error: Unexpected rule {:?}", - self.rule, self.error - )?; - Ok(()) + writeln!(f, "Error processing rule {}: {}", self.rule, self.error) } } -fn get_implicit_precedence(rule: &Rule) -> i32 { - match rule { - Rule::String(_) => 2, - Rule::Metadata { rule, params } => { - if params.is_main_token { - get_implicit_precedence(rule) + 1 - } else { - get_implicit_precedence(rule) +fn get_implicit_precedence(pool: &RulePool, root: RuleId) -> i32 { + let mut id = root; + let mut boost = 0; + loop { + match pool.node(id) { + Rule::String(_) => return 2 + boost, + Rule::Metadata { params, rule } => { + if pool.params(params).is_main_token { + boost += 1; + } + id = rule; } + _ => return boost, } - _ => 0, } } -const fn get_completion_precedence(rule: &Rule) -> i32 { - if let Rule::Metadata { params, .. } = rule - && let Precedence::Integer(p) = params.precedence +fn get_completion_precedence(pool: &RulePool, id: RuleId) -> i32 { + if let Rule::Metadata { params, .. } = pool.node(id) + && let Precedence::Integer(p) = pool.params(params).precedence { return p; } 0 } -pub fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> ExpandTokensResult { +pub fn expand_tokens( + pool: &mut RulePool, + lexical_variables: &[LexicalToken], + separator_roots: &[RuleId], +) -> ExpandTokensResult { let mut builder = NfaBuilder { nfa: Nfa::new(), is_sep: true, case_insensitive: false, precedence_stack: vec![0], }; + let separator_root = build_separator(pool, separator_roots); - let separator_rule = if grammar.separators.is_empty() { - Rule::Blank - } else { - grammar.separators.push(Rule::Blank); - Rule::repeat(Rule::choice(grammar.separators)) - }; - - let mut variables = Vec::with_capacity(grammar.variables.len()); - for (i, variable) in grammar.variables.into_iter().enumerate() { - if variable.rule.is_empty() { - Err(ExpandTokensError::EmptyString(variable.name.clone()))?; + let mut variables = Vec::with_capacity(lexical_variables.len()); + for (i, variable) in lexical_variables.iter().enumerate() { + if pool.subtree_matches_empty_str(variable.root) { + Err(ExpandTokensError::EmptyString( + pool.resolve(variable.name).to_string(), + ))?; } - - let is_immediate_token = match &variable.rule { - Rule::Metadata { params, .. } => params.is_main_token, + let is_immediate_token = match pool.node(variable.root) { + Rule::Metadata { params, .. } => pool.params(params).is_main_token, _ => false, }; builder.is_sep = false; builder.nfa.states.push(NfaState::Accept { variable_index: i, - precedence: get_completion_precedence(&variable.rule), + precedence: get_completion_precedence(pool, variable.root), }); let last_state_id = builder.nfa.last_state_id(); builder - .expand_rule(&variable.rule, last_state_id) + .expand_rule(pool, variable.root, last_state_id) .map_err(|e| { ExpandTokensError::Processing(ExpandTokensProcessingError { - rule: variable.name.clone(), + rule: pool.resolve(variable.name).to_string(), error: e, }) })?; @@ -121,14 +118,14 @@ pub fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> ExpandTokensResult builder.is_sep = true; let last_state_id = builder.nfa.last_state_id(); builder - .expand_rule(&separator_rule, last_state_id) + .expand_rule(pool, separator_root, last_state_id) .map_err(ExpandTokensError::ExpandRule)?; } variables.push(LexicalVariable { name: variable.name, kind: variable.kind, - implicit_precedence: get_implicit_precedence(&variable.rule), + implicit_precedence: get_implicit_precedence(pool, variable.root), start_state: builder.nfa.last_state_id(), }); } @@ -139,12 +136,37 @@ pub fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> ExpandTokensResult }) } +fn build_separator(pool: &mut RulePool, separator_roots: &[RuleId]) -> RuleId { + let blank = pool.push_node(Rule::Blank); + if separator_roots.is_empty() { + return blank; + } + let mut elements = Vec::with_capacity(separator_roots.len() + 1); + let mut stack = Vec::with_capacity(separator_roots.len() + 1); + stack.push(blank); + stack.extend(separator_roots.iter().rev().copied()); + 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); + } + } + let range = pool.push_children(&elements); + let choice = pool.push_node(Rule::Choice(range)); + pool.push_node(Rule::Repeat(choice)) +} + pub type ExpandRuleResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ExpandRuleError { - #[error("Grammar error: Unexpected rule {0:?}")] - UnexpectedRule(Rule), + #[error("unexpected symbol {0:?}")] + UnexpectedSymbol(Symbol), + #[error("unexpected reserved-word context {0}")] + UnexpectedReserved(String), #[error("{0}")] Parse(String), #[error(transparent)] @@ -153,7 +175,7 @@ pub enum ExpandRuleError { pub type ExpandRegexResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ExpandRegexError { #[error("{0}")] Utf8(String), @@ -194,15 +216,21 @@ fn case_fold_ascii_safe(base: &CharacterSet) -> CharacterSet { } impl NfaBuilder { - fn expand_rule(&mut self, rule: &Rule, mut next_state_id: u32) -> ExpandRuleResult { - match rule { + fn expand_rule( + &mut self, + pool: &RulePool, + id: RuleId, + mut next_state_id: u32, + ) -> ExpandRuleResult { + match pool.node(id) { Rule::Pattern(s, f) => { // With unicode enabled, `\w`, `\s` and `\d` expand to character sets that are much // larger than intended, so we replace them with the actual // character sets they should represent. If the full unicode range // of `\w`, `\s` or `\d` are needed then `\p{L}`, `\p{Z}` and `\p{N}` should be // used. - let s = s + let s = pool + .resolve(s) .replace(r"\w", r"[0-9A-Za-z_]") .replace(r"\s", r"[\t-\r ]") .replace(r"\d", r"[0-9]") @@ -220,21 +248,25 @@ impl NfaBuilder { let hir = parser .parse(&s) .map_err(|e| ExpandRuleError::Parse(e.to_string()))?; - self.case_insensitive = f.contains('i'); + self.case_insensitive = pool.resolve(f).contains('i'); self.expand_regex(&hir, next_state_id) .map_err(ExpandRuleError::ExpandRegex) } Rule::String(s) => { + let s = pool.resolve(s); for c in s.chars().rev() { self.push_advance(CharacterSet::from_char(c), next_state_id); next_state_id = self.nfa.last_state_id(); } Ok(!s.is_empty()) } - Rule::Choice(elements) => { + Rule::Choice(range) => { + let elements = pool.child_slice(range); let mut alternative_state_ids = Vec::with_capacity(elements.len()); - for element in elements { - if self.expand_rule(element, next_state_id)? { + let mut did_expand = false; + for &element in elements { + if self.expand_rule(pool, element, next_state_id)? { + did_expand = true; alternative_state_ids.push(self.nfa.last_state_id()); } else { alternative_state_ids.push(next_state_id); @@ -246,12 +278,12 @@ impl NfaBuilder { for alternative_state_id in alternative_state_ids { self.push_split(alternative_state_id); } - Ok(true) + Ok(did_expand) } - Rule::Seq(elements) => { + Rule::Seq(range) => { let mut result = false; - for element in elements.iter().rev() { - if self.expand_rule(element, next_state_id)? { + for &element in pool.child_slice(range).iter().rev() { + if self.expand_rule(pool, element, next_state_id)? { result = true; } next_state_id = self.nfa.last_state_id(); @@ -264,29 +296,39 @@ impl NfaBuilder { precedence: 0, }); // Placeholder for split let split_state_id = self.nfa.last_state_id(); - if self.expand_rule(rule, split_state_id)? { + if self.expand_rule(pool, rule, split_state_id)? { self.nfa.states[split_state_id as usize] = NfaState::Split(self.nfa.last_state_id(), next_state_id); Ok(true) } else { + self.nfa.states.pop(); Ok(false) } } - Rule::Metadata { rule, params } => { - let has_precedence = if let Precedence::Integer(precedence) = ¶ms.precedence { - self.precedence_stack.push(*precedence); - true - } else { - false - }; - let result = self.expand_rule(rule, next_state_id); + Rule::Metadata { params, rule } => { + let has_precedence = + if let Precedence::Integer(precedence) = pool.params(params).precedence { + self.precedence_stack.push(precedence); + true + } else { + false + }; + let result = self.expand_rule(pool, rule, next_state_id); if has_precedence { self.precedence_stack.pop(); } result } Rule::Blank => Ok(false), - _ => Err(ExpandRuleError::UnexpectedRule(rule.clone()))?, + Rule::Sym { kind, index } => Err(ExpandRuleError::UnexpectedSymbol(Symbol { + kind, + index: index as usize, + }))?, + Rule::Reserved { ctx, .. } => Err(ExpandRuleError::UnexpectedReserved( + pool.resolve(ctx).to_string(), + ))?, + // `NamedSymbol` is interned to `Sym` by intern_symbols + Rule::NamedSymbol(_) => unreachable!(), } } @@ -471,7 +513,7 @@ impl NfaBuilder { mod tests { use super::*; use crate::{ - grammars::Variable, + grammars::VariableType, nfa::{NfaCursor, NfaTransition}, }; @@ -519,394 +561,519 @@ mod tests { result } - #[test] - fn test_rule_expansion() { - struct Row { - rules: Vec, - separators: Vec, - examples: Vec<(&'static str, Option<(usize, &'static str)>)>, - } - - let table = [ - // regex with sequences and alternatives - Row { - rules: vec![Rule::pattern("(a|b|c)d(e|f|g)h?", "")], - separators: vec![], - examples: vec![ - ("ade1", Some((0, "ade"))), - ("bdf1", Some((0, "bdf"))), - ("bdfh1", Some((0, "bdfh"))), - ("ad1", None), - ], - }, - // regex with repeats - Row { - rules: vec![Rule::pattern("a*", "")], - separators: vec![], - examples: vec![("aaa1", Some((0, "aaa"))), ("b", Some((0, "")))], - }, - // regex with repeats in sequences - Row { - rules: vec![Rule::pattern("a((bc)+|(de)*)f", "")], - separators: vec![], - examples: vec![ - ("af1", Some((0, "af"))), - ("adedef1", Some((0, "adedef"))), - ("abcbcbcf1", Some((0, "abcbcbcf"))), - ("a", None), - ], - }, - // regex with character ranges - Row { - rules: vec![Rule::pattern("[a-fA-F0-9]+", "")], - separators: vec![], - examples: vec![("A1ff0.", Some((0, "A1ff0")))], - }, - // regex with perl character classes - Row { - rules: vec![Rule::pattern("\\w\\d\\s", "")], - separators: vec![], - examples: vec![("_0 ", Some((0, "_0 ")))], - }, - // string - Row { - rules: vec![Rule::string("abc")], - separators: vec![], - examples: vec![("abcd", Some((0, "abc"))), ("ab", None)], - }, - // complex rule containing strings and regexes - Row { - rules: vec![Rule::repeat(Rule::seq(vec![ - Rule::string("{"), - Rule::pattern("[a-f]+", ""), - Rule::string("}"), - ]))], - separators: vec![], - examples: vec![ - ("{a}{", Some((0, "{a}"))), - ("{a}{d", Some((0, "{a}"))), - ("ab", None), - ], - }, - // longest match rule - Row { - rules: vec![ - Rule::pattern("a|bc", ""), - Rule::pattern("aa", ""), - Rule::pattern("bcd", ""), - ], - separators: vec![], - examples: vec![ - ("a.", Some((0, "a"))), - ("bc.", Some((0, "bc"))), - ("aa.", Some((1, "aa"))), - ("bcd?", Some((2, "bcd"))), - ("b.", None), - ("c.", None), - ], - }, - // regex with an alternative including the empty string - Row { - rules: vec![Rule::pattern("a(b|)+c", "")], - separators: vec![], - examples: vec![ - ("ac.", Some((0, "ac"))), - ("abc.", Some((0, "abc"))), - ("abbc.", Some((0, "abbc"))), - ], - }, - // separators - Row { - rules: vec![Rule::pattern("[a-f]+", "")], - separators: vec![Rule::string("\\\n"), Rule::pattern("\\s", "")], - examples: vec![ - (" a", Some((0, "a"))), - (" \nb", Some((0, "b"))), - (" \\a", None), - (" \\\na", Some((0, "a"))), - ], - }, - // shorter tokens with higher precedence - Row { - rules: vec![ - Rule::prec(Precedence::Integer(2), Rule::pattern("abc", "")), - Rule::prec(Precedence::Integer(1), Rule::pattern("ab[cd]e", "")), - Rule::pattern("[a-e]+", ""), - ], - separators: vec![Rule::string("\\\n"), Rule::pattern("\\s", "")], - examples: vec![ - ("abceef", Some((0, "abc"))), - ("abdeef", Some((1, "abde"))), - ("aeeeef", Some((2, "aeeee"))), - ], - }, - // immediate tokens with higher precedence - Row { - rules: vec![ - Rule::prec(Precedence::Integer(1), Rule::pattern("[^a]+", "")), - Rule::immediate_token(Rule::prec( - Precedence::Integer(2), - Rule::pattern("[^ab]+", ""), - )), - ], - separators: vec![Rule::pattern("\\s", "")], - examples: vec![("cccb", Some((1, "ccc")))], - }, - Row { - rules: vec![Rule::seq(vec![ - Rule::string("a"), - Rule::choice(vec![Rule::string("b"), Rule::string("c")]), - Rule::string("d"), - ])], - separators: vec![], - examples: vec![ - ("abd", Some((0, "abd"))), - ("acd", Some((0, "acd"))), - ("abc", None), - ("ad", None), - ("d", None), - ("a", None), - ], - }, - // nested choices within sequences - Row { - rules: vec![Rule::seq(vec![ - Rule::pattern("[0-9]+", ""), - Rule::choice(vec![ - Rule::Blank, - Rule::choice(vec![Rule::seq(vec![ - Rule::choice(vec![Rule::string("e"), Rule::string("E")]), - Rule::choice(vec![ - Rule::Blank, - Rule::choice(vec![Rule::string("+"), Rule::string("-")]), - ]), - Rule::pattern("[0-9]+", ""), - ])]), - ]), - ])], - separators: vec![], - examples: vec![ - ("12", Some((0, "12"))), - ("12e", Some((0, "12"))), - ("12g", Some((0, "12"))), - ("12e3", Some((0, "12e3"))), - ("12e+", Some((0, "12"))), - ("12E+34 +", Some((0, "12E+34"))), - ("12e34", Some((0, "12e34"))), - ], - }, - // nested groups - Row { - rules: vec![Rule::seq(vec![Rule::pattern(r"([^x\\]|\\(.|\n))+", "")])], - separators: vec![], - examples: vec![("abcx", Some((0, "abc"))), ("abc\\0x", Some((0, "abc\\0")))], - }, - // allowing unrecognized escape sequences - Row { - rules: vec![ - // Escaped forward slash (used in JS because '/' is the regex delimiter) - Rule::pattern(r"\/", ""), - // Escaped quotes - Rule::pattern(r#"\"\'"#, ""), - // Quote preceded by a literal backslash - Rule::pattern(r"[\\']+", ""), - ], - separators: vec![], - examples: vec![ - ("/", Some((0, "/"))), - ("\"\'", Some((1, "\"\'"))), - (r"'\'a", Some((2, r"'\'"))), - ], - }, - // unicode property escapes - Row { - rules: vec![ - Rule::pattern(r"\p{L}+\P{L}+", ""), - Rule::pattern(r"\p{White_Space}+\P{White_Space}+[\p{White_Space}]*", ""), - ], - separators: vec![], - examples: vec![ - (" 123 abc", Some((1, " 123 "))), - ("ბΨƁ___ƀƔ", Some((0, "ბΨƁ___"))), - ], - }, - // unicode property escapes in bracketed sets - Row { - rules: vec![Rule::pattern(r"[\p{L}\p{Nd}]+", "")], - separators: vec![], - examples: vec![("abΨ12٣٣, ok", Some((0, "abΨ12٣٣")))], - }, - // unicode character escapes - Row { - rules: vec![ - Rule::pattern(r"\u{00dc}", ""), - Rule::pattern(r"\U{000000dd}", ""), - Rule::pattern(r"\u00de", ""), - Rule::pattern(r"\U000000df", ""), - ], - separators: vec![], - examples: vec![ - ("\u{00dc}", Some((0, "\u{00dc}"))), - ("\u{00dd}", Some((1, "\u{00dd}"))), - ("\u{00de}", Some((2, "\u{00de}"))), - ("\u{00df}", Some((3, "\u{00df}"))), - ], - }, - Row { - rules: vec![ - Rule::pattern(r"u\{[0-9a-fA-F]+\}", ""), - // Already-escaped curly braces - Rule::pattern(r"\{[ab]{3}\}", ""), - // Unicode codepoints - Rule::pattern(r"\u{1000A}", ""), - // Unicode codepoints (lowercase) - Rule::pattern(r"\u{1000b}", ""), - ], - separators: vec![], - examples: vec![ - ("u{1234} ok", Some((0, "u{1234}"))), - ("{aba}}", Some((1, "{aba}"))), - ("\u{1000A}", Some((2, "\u{1000A}"))), - ("\u{1000b}", Some((3, "\u{1000b}"))), - ], - }, - // Case-insensitive patterns must not fold in the two non-ASCII code - // points that Unicode simple case folding maps onto ASCII letters: - // `ſ` (U+017F) onto `s`, and the Kelvin sign `K` (U+212A) onto `k`. - Row { - rules: vec![Rule::pattern("[sk]+", "i")], - separators: vec![], - examples: vec![ - ("sSkK.", Some((0, "sSkK"))), - ("\u{017f}", None), // long s, not matched by `s` - ("\u{212a}", None), // Kelvin sign, not matched by `k` - ("sk\u{212a}", Some((0, "sk"))), // folded code point ends the token - ], - }, - // A broad class carrying `/i` (a negated class, `\p{L}`, ...) keeps - // `ſ`/`K`: folding never *introduces* them here (the class already - // contains them), so there is nothing to drop. - Row { - rules: vec![Rule::pattern("[^\"]", "i")], - separators: vec![], - examples: vec![ - ("\u{017f}", Some((0, "\u{017f}"))), // long s kept under /i - ("\u{212a}", Some((0, "\u{212a}"))), // Kelvin sign kept under /i - ("s", Some((0, "s"))), - ], - }, - // An intentionally-written `ſ`/`K` is preserved: with no `i` flag - // nothing is folded at all. - Row { - rules: vec![Rule::pattern("[\u{017f}\u{212a}]+", "")], - separators: vec![], - examples: vec![("\u{017f}\u{212a}.", Some((0, "\u{017f}\u{212a}")))], - }, - // Without the `i` flag nothing is folded, so a broad class such as - // `[^"]` must keep `ſ`/`K` instead of stripping them as fold artifacts. - Row { - rules: vec![Rule::pattern("[^\"]", "")], - separators: vec![], - examples: vec![ - ("a", Some((0, "a"))), - ("\u{017f}", Some((0, "\u{017f}"))), // long s - ("\u{212a}", Some((0, "\u{212a}"))), // Kelvin sign - ("\"", None), // the one excluded character - ], - }, - // Emojis - Row { - rules: vec![Rule::pattern(r"\p{Emoji}+", "")], - separators: vec![], - examples: vec![ - ("🐎", Some((0, "🐎"))), - ("🐴🐴", Some((0, "🐴🐴"))), - ("#0", Some((0, "#0"))), // These chars are technically emojis! - ("⻢", None), - ("♞", None), - ("horse", None), - ], - }, - // Intersection - Row { - rules: vec![Rule::pattern(r"[[0-7]&&[4-9]]+", "")], - separators: vec![], - examples: vec![ - ("456", Some((0, "456"))), - ("64", Some((0, "64"))), - ("452", Some((0, "45"))), - ("91", None), - ("8", None), - ("3", None), - ], - }, - // Difference - Row { - rules: vec![Rule::pattern(r"[[0-9]--[4-7]]+", "")], - separators: vec![], - examples: vec![ - ("123", Some((0, "123"))), - ("83", Some((0, "83"))), - ("9", Some((0, "9"))), - ("124", Some((0, "12"))), - ("67", None), - ("4", None), - ], - }, - // Symmetric difference - Row { - rules: vec![Rule::pattern(r"[[0-7]~~[4-9]]+", "")], - separators: vec![], - examples: vec![ - ("123", Some((0, "123"))), - ("83", Some((0, "83"))), - ("9", Some((0, "9"))), - ("124", Some((0, "12"))), - ("67", None), - ("4", None), - ], - }, - // Nested set operations - Row { - // 0 1 2 3 4 5 6 7 8 9 - // [0-5]: y y y y y y - // [2-4]: y y y - // [0-5]--[2-4]: y y y - // [3-9]: y y y y y y y - // [6-7]: y y - // [3-9]--[5-7]: y y y y y - // final regex: y y y y y y - rules: vec![Rule::pattern(r"[[[0-5]--[2-4]]~~[[3-9]--[6-7]]]+", "")], - separators: vec![], - examples: vec![ - ("01", Some((0, "01"))), - ("432", Some((0, "43"))), - ("8", Some((0, "8"))), - ("9", Some((0, "9"))), - ("2", None), - ("567", None), - ], - }, - ]; - - for Row { - rules, - separators, - examples, - } in &table - { - let grammar = expand_tokens(ExtractedLexicalGrammar { - separators: separators.clone(), - variables: rules - .iter() - .map(|rule| Variable::named("", rule.clone())) - .collect(), + fn check( + // (token roots, separator roots) + build: impl FnOnce(&mut RulePool) -> (Vec, Vec), + examples: &[(&str, Option<(usize, &str)>)], + ) { + let mut pool = RulePool::default(); + let (roots, separators) = build(&mut pool); + let vars = roots + .into_iter() + .enumerate() + .map(|(i, root)| LexicalToken { + name: pool.intern(&format!("tok{i}")), + kind: VariableType::Anonymous, + root, }) - .unwrap(); - - for (haystack, needle) in examples { - assert_eq!(simulate_nfa(&grammar, haystack), *needle); - } + .collect::>(); + let grammar = expand_tokens(&mut pool, &vars, &separators).unwrap(); + for &(input, expected) in examples { + assert_eq!(simulate_nfa(&grammar, input), expected, "input {input}"); } } + + #[test] + fn test_rule_expansion() { + // regex with sequences and alternatives + check( + |p| { + let (v, f) = (p.intern("(a|b|c)d(e|f|g)h?"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("ade1", Some((0, "ade"))), + ("bdf1", Some((0, "bdf"))), + ("bdfh1", Some((0, "bdfh"))), + ("ad1", None), + ], + ); + // regex with repeats + check( + |p| { + let (v, f) = (p.intern("a*"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[("aaa1", Some((0, "aaa"))), ("b", Some((0, "")))], + ); + // regex with repeats in sequences + check( + |p| { + let (v, f) = (p.intern("a((bc)+|(de)*)f"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("af1", Some((0, "af"))), + ("adedef1", Some((0, "adedef"))), + ("abcbcbcf1", Some((0, "abcbcbcf"))), + ("a", None), + ], + ); + // regex with character ranges + check( + |p| { + let (v, f) = (p.intern("[a-fA-F0-9]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[("A1ff0.", Some((0, "A1ff0")))], + ); + // regex with perl character classes + check( + |p| { + let (v, f) = (p.intern("\\w\\d\\s"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[("_0 ", Some((0, "_0 ")))], + ); + // string + check( + |p| { + let s = p.intern("abc"); + (vec![p.string(s)], vec![]) + }, + &[("abcd", Some((0, "abc"))), ("ab", None)], + ); + // complex rule containing strings and regexes + check( + |p| { + let (reg, empty) = (p.intern("[a-f]+"), p.intern("")); + let (lb, pat, rb) = (p.intern("{"), p.pattern(reg, empty), p.intern("}")); + let (left, right) = (p.string(lb), p.string(rb)); + let seq = p.seq(&[left, pat, right]); + (vec![p.repeat(seq)], vec![]) + }, + &[ + ("{a}{", Some((0, "{a}"))), + ("{a}{d", Some((0, "{a}"))), + ("ab", None), + ], + ); + // longest match rule + check( + |p| { + let empty = p.intern(""); + let (x, y, z) = (p.intern("a|bc"), p.intern("aa"), p.intern("bcd")); + ( + vec![ + p.pattern(x, empty), + p.pattern(y, empty), + p.pattern(z, empty), + ], + vec![], + ) + }, + &[ + ("a.", Some((0, "a"))), + ("bc.", Some((0, "bc"))), + ("aa.", Some((1, "aa"))), + ("bcd?", Some((2, "bcd"))), + ("b.", None), + ("c.", None), + ], + ); + // regex with an alternative including the empty string + check( + |p| { + let (v, f) = (p.intern("a(b|)+c"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("ac.", Some((0, "ac"))), + ("abc.", Some((0, "abc"))), + ("abbc.", Some((0, "abbc"))), + ], + ); + // separators + check( + |p| { + let (v, f) = (p.intern("[a-f]+"), p.intern("")); + let token = p.pattern(v, f); + let escaped_newline = { + let value = p.intern("\\\n"); + p.string(value) + }; + let whitespace = { + let value = p.intern("\\s"); + p.pattern(value, f) + }; + (vec![token], vec![escaped_newline, whitespace]) + }, + &[ + (" a", Some((0, "a"))), + (" \nb", Some((0, "b"))), + (" \\a", None), + (" \\\na", Some((0, "a"))), + ], + ); + // shorter tokens with higher precedence + check( + |p| { + let f = p.intern(""); + let (v1, v2, v3) = (p.intern("abc"), p.intern("ab[cd]e"), p.intern("[a-e]+")); + let (pat1, pat2, pat3) = (p.pattern(v1, f), p.pattern(v2, f), p.pattern(v3, f)); + ( + vec![ + p.prec(Precedence::Integer(2), pat1), + p.prec(Precedence::Integer(1), pat2), + pat3, + ], + vec![], + ) + }, + &[ + ("abceef", Some((0, "abc"))), + ("abdeef", Some((1, "abde"))), + ("aeeeef", Some((2, "aeeee"))), + ], + ); + // immediate tokens with higher precedence + check( + |p| { + let f = p.intern(""); + let (v1, v2) = (p.intern("[^a]+"), p.intern("[^ab]+")); + let (pat1, pat2) = (p.pattern(v1, f), p.pattern(v2, f)); + let r1 = p.prec(Precedence::Integer(1), pat1); + let r2 = { + let imm = p.prec(Precedence::Integer(2), pat2); + p.immediate_token(imm) + }; + let sep = { + let s = p.intern("\\s"); + p.pattern(s, f) + }; + (vec![r1, r2], vec![sep]) + }, + &[("cccb", Some((1, "ccc")))], + ); + check( + |p| { + let (a, b, c, d) = (p.intern("a"), p.intern("b"), p.intern("c"), p.intern("d")); + let r1 = p.string(a); + let r2 = { + let (inner_1, inner_2) = (p.string(b), p.string(c)); + p.choice(&[inner_1, inner_2]) + }; + let r3 = p.string(d); + (vec![p.seq(&[r1, r2, r3])], vec![]) + }, + &[ + ("abd", Some((0, "abd"))), + ("acd", Some((0, "acd"))), + ("abc", None), + ("ad", None), + ("d", None), + ("a", None), + ], + ); + // nested choices within sequences + check( + |p| { + let r1 = { + let (v, f) = (p.intern("[0-9]+"), p.intern("")); + p.pattern(v, f) + }; + let r2 = { + let blank = p.blank(); + let inner_ch = { + let ch1 = { + let (e1, e2) = (p.intern("e"), p.intern("E")); + let (e1, e2) = (p.string(e1), p.string(e2)); + p.choice(&[e1, e2]) + }; + let ch2 = { + let blank = p.blank(); + let (plus, minus) = (p.intern("+"), p.intern("-")); + let (plus, minus) = (p.string(plus), p.string(minus)); + let inner_ch = p.choice(&[plus, minus]); + p.choice(&[blank, inner_ch]) + }; + let pat = { + let (v, f) = (p.intern("[0-9]+"), p.intern("")); + p.pattern(v, f) + }; + let sq = p.seq(&[ch1, ch2, pat]); + p.choice(&[sq]) + }; + p.choice(&[blank, inner_ch]) + }; + let seq = p.seq(&[r1, r2]); + (vec![seq], vec![]) + }, + &[ + ("12", Some((0, "12"))), + ("12e", Some((0, "12"))), + ("12g", Some((0, "12"))), + ("12e3", Some((0, "12e3"))), + ("12e+", Some((0, "12"))), + ("12E+34 +", Some((0, "12E+34"))), + ("12e34", Some((0, "12e34"))), + ], + ); + // nested groups + check( + |p| { + let (v, f) = (p.intern(r"([^x\\]|\\(.|\n))+"), p.intern("")); + let pat = p.pattern(v, f); + let sq = p.seq(&[pat]); + (vec![sq], vec![]) + }, + &[("abcx", Some((0, "abc"))), ("abc\\0x", Some((0, "abc\\0")))], + ); + // allowing unrecognized escape sequences + check( + |p| { + let f = p.intern(""); + // Escaped forward slash (used in JS because '/' is the regex delimiter) + let v1 = p.intern(r"\/"); + // Escaped quotes + let v2 = p.intern(r#"\"\'"#); + // Quote preceded by a literal backslash + let v3 = p.intern(r"[\\']+"); + ( + vec![p.pattern(v1, f), p.pattern(v2, f), p.pattern(v3, f)], + vec![], + ) + }, + &[ + ("/", Some((0, "/"))), + ("\"\'", Some((1, "\"\'"))), + (r"'\'a", Some((2, r"'\'"))), + ], + ); + // unicode property escapes + check( + |p| { + let f = p.intern(""); + let v1 = p.intern(r"\p{L}+\P{L}+"); + let v2 = p.intern(r"\p{White_Space}+\P{White_Space}+[\p{White_Space}]*"); + (vec![p.pattern(v1, f), p.pattern(v2, f)], vec![]) + }, + &[ + (" 123 abc", Some((1, " 123 "))), + ("ბΨƁ___ƀƔ", Some((0, "ბΨƁ___"))), + ], + ); + // unicode property escapes in bracketed sets + check( + |p| { + let (v, f) = (p.intern(r"[\p{L}\p{Nd}]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[("abΨ12٣٣, ok", Some((0, "abΨ12٣٣")))], + ); + // unicode character escapes + check( + |p| { + let f = p.intern(""); + let v1 = p.intern(r"\u{00dc}"); + let v2 = p.intern(r"\U{000000dd}"); + let v3 = p.intern(r"\u00de"); + let v4 = p.intern(r"\U000000df"); + ( + vec![ + p.pattern(v1, f), + p.pattern(v2, f), + p.pattern(v3, f), + p.pattern(v4, f), + ], + vec![], + ) + }, + &[ + ("\u{00dc}", Some((0, "\u{00dc}"))), + ("\u{00dd}", Some((1, "\u{00dd}"))), + ("\u{00de}", Some((2, "\u{00de}"))), + ("\u{00df}", Some((3, "\u{00df}"))), + ], + ); + check( + |p| { + let f = p.intern(""); + let v1 = p.intern(r"u\{[0-9a-fA-F]+\}"); + // Already-escaped curly braces + let v2 = p.intern(r"\{[ab]{3}\}"); + // Unicode codepoints + let v3 = p.intern(r"\u{1000A}"); + // Unicode codepoints (lowercase) + let v4 = p.intern(r"\u{1000b}"); + ( + vec![ + p.pattern(v1, f), + p.pattern(v2, f), + p.pattern(v3, f), + p.pattern(v4, f), + ], + vec![], + ) + }, + &[ + ("u{1234} ok", Some((0, "u{1234}"))), + ("{aba}}", Some((1, "{aba}"))), + ("\u{1000A}", Some((2, "\u{1000A}"))), + ("\u{1000b}", Some((3, "\u{1000b}"))), + ], + ); + // Case-insensitive patterns must not fold in the two non-ASCII code + // points that Unicode simple case folding maps onto ASCII letters: + // `ſ` (U+017F) onto `s`, and the Kelvin sign `K` (U+212A) onto `k`. + check( + |p| { + let (v, f) = (p.intern("[sk]+"), p.intern("i")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("sSkK.", Some((0, "sSkK"))), + ("\u{017f}", None), // long s, not matched by `s` + ("\u{212a}", None), // Kelvin sign, not matched by `k` + ("sk\u{212a}", Some((0, "sk"))), // folded code point ends the token + ], + ); + // A broad class carrying `/i` (a negated class, `\p{L}`, ...) keeps + // `ſ`/`K`: folding never *introduces* them here (the class already + // contains them), so there is nothing to drop. + check( + |p| { + let (v, f) = (p.intern("[^\"]"), p.intern("i")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("\u{017f}", Some((0, "\u{017f}"))), // long s kept under /i + ("\u{212a}", Some((0, "\u{212a}"))), // Kelvin sign kept under /i + ("s", Some((0, "s"))), + ], + ); + // An intentionally-written `ſ`/`K` is preserved: the stripping above + // only fires when the ASCII pair it folds with is also present. + check( + |p| { + let (v, f) = (p.intern("[\u{017f}\u{212a}]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[("\u{017f}\u{212a}.", Some((0, "\u{017f}\u{212a}")))], + ); + // Without the `i` flag nothing is folded, so a broad class such as + // `[^"]` must keep `ſ`/`K` instead of stripping them as fold artifacts. + check( + |p| { + let (v, f) = (p.intern("[^\"]"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("a", Some((0, "a"))), + ("\u{017f}", Some((0, "\u{017f}"))), // long s + ("\u{212a}", Some((0, "\u{212a}"))), // Kelvin sign + ("\"", None), // the one excluded character + ], + ); + // Emojis + check( + |p| { + let (v, f) = (p.intern(r"\p{Emoji}+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("🐎", Some((0, "🐎"))), + ("🐴🐴", Some((0, "🐴🐴"))), + ("#0", Some((0, "#0"))), // These chars are technically emojis! + ("⻢", None), + ("♞", None), + ("horse", None), + ], + ); + // Intersection + check( + |p| { + let (v, f) = (p.intern(r"[[0-7]&&[4-9]]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("456", Some((0, "456"))), + ("64", Some((0, "64"))), + ("452", Some((0, "45"))), + ("91", None), + ("8", None), + ("3", None), + ], + ); + // Difference + check( + |p| { + let (v, f) = (p.intern(r"[[0-9]--[4-7]]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("123", Some((0, "123"))), + ("83", Some((0, "83"))), + ("9", Some((0, "9"))), + ("124", Some((0, "12"))), + ("67", None), + ("4", None), + ], + ); + // Symmetric difference + check( + |p| { + let (v, f) = (p.intern(r"[[0-7]~~[4-9]]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("123", Some((0, "123"))), + ("83", Some((0, "83"))), + ("9", Some((0, "9"))), + ("124", Some((0, "12"))), + ("67", None), + ("4", None), + ], + ); + // Nested set operations + check( + // 0 1 2 3 4 5 6 7 8 9 + // [0-5]: y y y y y y + // [2-4]: y y y + // [0-5]--[2-4]: y y y + // [3-9]: y y y y y y y + // [6-7]: y y + // [3-9]--[5-7]: y y y y y + // final regex: y y y y y y + |p| { + let (v, f) = (p.intern(r"[[[0-5]--[2-4]]~~[[3-9]--[6-7]]]+"), p.intern("")); + (vec![p.pattern(v, f)], vec![]) + }, + &[ + ("01", Some((0, "01"))), + ("432", Some((0, "43"))), + ("8", Some((0, "8"))), + ("9", Some((0, "9"))), + ("2", None), + ("567", None), + ], + ); + } + + #[test] + fn test_repeat_of_empty_choice_does_not_leave_an_accept_state() { + check( + |p| { + let (a, b) = (p.intern("a"), p.intern("b")); + let token_a = p.string(a); + let (left, right) = (p.blank(), p.blank()); + let empty_choice = p.choice(&[left, right]); + let repeated_empty = p.repeat(empty_choice); + let token_b_suffix = p.string(b); + let token_b = p.seq(&[repeated_empty, token_b_suffix]); + (vec![token_a, token_b], vec![]) + }, + &[("b", Some((1, "b")))], + ); + } } diff --git a/crates/generate/src/prepare_grammar/extract_default_aliases.rs b/crates/generate/src/prepare_grammar/extract_default_aliases.rs index 8e5738cd7..345044234 100644 --- a/crates/generate/src/prepare_grammar/extract_default_aliases.rs +++ b/crates/generate/src/prepare_grammar/extract_default_aliases.rs @@ -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) } } diff --git a/crates/generate/src/prepare_grammar/extract_tokens.rs b/crates/generate/src/prepare_grammar/extract_tokens.rs index 69e144095..9ffe79ba8 100644 --- a/crates/generate/src/prepare_grammar/extract_tokens.rs +++ b/crates/generate/src/prepare_grammar/extract_tokens.rs @@ -3,15 +3,16 @@ use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::{ExtractedLexicalGrammar, ExtractedSyntaxGrammar, InternedGrammar}; use crate::{ - grammars::{ExternalToken, ReservedWordContext, Variable, VariableType}, - rules::{MetadataParams, Rule, Symbol, SymbolType}, + grammars::{ExternalToken, InputGrammar, VariableType}, + prepare_grammar::intern_symbols::InternedGrammarMeta, + rules::{MetadataParams, Rule, RuleId, RulePool, Symbol}, + strpool::StrId, }; pub type ExtractTokensResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ExtractTokensError { #[error( "The rule `{0}` contains an empty string. @@ -33,7 +34,7 @@ unless they are used only as the grammar's start rule. NonTokenReservedWord(String), } -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub struct NonTerminalWordTokenError { pub symbol_name: String, pub conflicting_symbol_name: Option, @@ -57,526 +58,662 @@ impl std::fmt::Display for NonTerminalWordTokenError { } } +/// A single extracted token. +#[derive(Clone, Debug)] +pub struct LexicalToken { + /// Generated for anon tokens, rule name for absorbed variables + pub name: StrId, + pub kind: VariableType, + /// Pool root defining this token + pub root: RuleId, +} + +/// The extra pass's outputs besides the in-place rewrites. +#[derive(Clone, Debug, Default)] +pub(super) struct ExtractedGrammarMeta { + pub kinds: Vec, + pub lexical_variables: Vec, + pub separator_roots: Vec, + pub extra_symbols: Vec, + pub external_tokens: Vec, + pub reserved_sets: Vec<(StrId, Vec)>, + pub supertypes: Vec, + pub conflicts: Vec>, + pub inline: Vec, + pub word: Option, +} + +/// Token de-duper over pool subtrees +#[derive(Default)] +struct TokenExtractor { + lexical: Vec, + usage_counts: Vec, + memo: FxHashMap>, +} + +impl TokenExtractor { + /// Find or create the lexical token for `token_root`. Returns the terminal index if found + fn extract_token( + &mut self, + pool: &mut RulePool, + token_root: RuleId, + string_name: Option, + var_name: Option, + aux_token_count: &mut u32, + is_first: bool, + ) -> ExtractTokensResult { + let hash = pool.subtree_hash(token_root); + if let Some(candidates) = self.memo.get(&hash) { + for &i in candidates { + if pool.subtree_eq(self.lexical[i as usize].root, token_root) { + self.usage_counts[i as usize] += 1; + return Ok(i); + } + } + } + let (name, kind) = if let Some(sid) = string_name { + if pool.resolve(sid).is_empty() && !is_first { + Err(ExtractTokensError::EmptyString( + var_name.map_or_else(String::new, |v| pool.resolve(v).to_string()), + ))?; + } + (sid, VariableType::Anonymous) + } else { + *aux_token_count += 1; + let name = format!( + "{}_token{aux_token_count}", + var_name.map_or("", |v| pool.resolve(v)) + ); + (pool.intern(&name), VariableType::Auxiliary) + }; + // Shallow hoist: create a new token id pointing to the same pooled subtree. + // The original `token_root` slot is overwritten by caller to be a token, but it still + // points to the subtree. This new node becomes the token's root in the lexical grammar. + let index = self.lexical.len() as u32; + let root = pool.push_node(pool.node(token_root)); + self.lexical.push(LexicalToken { name, kind, root }); + self.usage_counts.push(1); + self.memo.entry(hash).or_default().push(index); + Ok(index) + } + + /// In-place token extraction over one root. + /// - `String`/`Pattern`: always extracted + /// - `token(...)`: metadata extracts the inner child when no other metadata + /// params are set, otherwise the whole metadata node + fn extract_in_root( + &mut self, + pool: &mut RulePool, + root: RuleId, + var_name: Option, + is_first: bool, + stack: &mut Vec, + ) -> ExtractTokensResult<()> { + let mut aux_token_count = 0; + stack.clear(); + stack.push(root); + while let Some(id) = stack.pop() { + match pool.node(id) { + Rule::String(sid) => { + let i = self.extract_token( + pool, + id, + Some(sid), + var_name, + &mut aux_token_count, + is_first, + )?; + pool.set_node(id, Rule::from(Symbol::terminal(i as usize))); + } + Rule::Pattern(..) => { + let i = self.extract_token( + pool, + id, + None, + var_name, + &mut aux_token_count, + is_first, + )?; + pool.set_node(id, Rule::from(Symbol::terminal(i as usize))); + } + Rule::Metadata { params, rule } => { + let p = pool.params(params); + if p.is_token { + let cleaned = MetadataParams { + is_token: false, + ..p + }; + let string_name = match pool.node(rule) { + Rule::String(s) => Some(s), + _ => None, + }; + let token_root = if cleaned == MetadataParams::default() { + rule // Only a token, drop the wrapper + } else { + id + }; + let i = self.extract_token( + pool, + token_root, + string_name, + var_name, + &mut aux_token_count, + is_first, + )?; + pool.set_node(id, Rule::from(Symbol::terminal(i as usize))); + } else { + stack.push(rule); + } + } + Rule::Seq(range) | Rule::Choice(range) => { + let base = stack.len(); + stack.extend_from_slice(pool.child_slice(range)); + stack[base..].reverse(); + } + Rule::Repeat(inner) | Rule::Reserved { rule: inner, .. } => stack.push(inner), + _ => {} + } + } + Ok(()) + } + + /// Structural lookup + fn find(&self, pool: &RulePool, root: RuleId) -> Option { + self.memo.get(&pool.subtree_hash(root)).and_then(|cands| { + cands + .iter() + .copied() + .find(|&i| pool.subtree_eq(self.lexical[i as usize].root, root)) + }) + } +} + pub(super) fn extract_tokens( - mut grammar: InternedGrammar, -) -> ExtractTokensResult<(ExtractedSyntaxGrammar, ExtractedLexicalGrammar)> { - let mut extractor = TokenExtractor { - current_variable_name: String::new(), - current_variable_token_count: 0, - is_first_rule: false, - extracted_variables: Vec::new(), - extracted_usage_counts: Vec::new(), - }; + g: &mut InputGrammar, + interned: &InternedGrammarMeta, +) -> ExtractTokensResult { + let mut extractor = TokenExtractor::default(); + let mut stack = Vec::new(); - for (i, variable) in &mut grammar.variables.iter_mut().enumerate() { - extractor.extract_tokens_in_variable(i == 0, variable)?; + for (i, v) in g.variables.iter().enumerate() { + extractor.extract_in_root(&mut g.pool, v.root, Some(v.name), i == 0, &mut stack)?; } - - for variable in &mut grammar.external_tokens { - extractor.extract_tokens_in_variable(false, variable)?; - } - - let mut lexical_variables = Vec::with_capacity(extractor.extracted_variables.len()); - for variable in extractor.extracted_variables { - lexical_variables.push(variable); + for (&root, &(name, _)) in g.external_roots.iter().zip(&interned.external_tokens) { + extractor.extract_in_root(&mut g.pool, root, name, false, &mut stack)?; } // If a variable's entire rule was extracted as a token and that token didn't // appear within any other rule, then remove that variable from the syntax // grammar, giving its name to the token in the lexical grammar. Any symbols // that pointed to that variable will need to be updated to point to the - // variable in the lexical grammar. Symbols that pointed to later variables + // token in the lexical grammar. Symbols that pointed to later variables // will need to have their indices decremented. - let mut variables = Vec::with_capacity(grammar.variables.len()); - let mut symbol_replacer = SymbolReplacer { - replacements: FxHashMap::default(), - }; - for (i, variable) in grammar.variables.into_iter().enumerate() { - if let Rule::Symbol(Symbol { - kind: SymbolType::Terminal, - index, - }) = variable.rule - && i > 0 - && extractor.extracted_usage_counts[index] == 1 + let old_len = g.variables.len(); + let mut replacements: FxHashMap = FxHashMap::default(); + let mut retained = Vec::with_capacity(old_len); + let mut kinds = Vec::with_capacity(old_len); + + // The start variable cannot be absorbed + retained.push(g.variables[0]); + kinds.push(interned.kinds[0]); + for (i, v) in g.variables.iter().enumerate().skip(1) { + if let Some(sym) = g.pool.node(v.root).symbol() + && sym.is_terminal() + && extractor.usage_counts[sym.index] == 1 { - let lexical_variable = &mut lexical_variables[index]; - if lexical_variable.kind == VariableType::Auxiliary - || variable.kind != VariableType::Hidden + let lexical = &mut extractor.lexical[sym.index]; + if lexical.kind == VariableType::Auxiliary || interned.kinds[i] != VariableType::Hidden { - lexical_variable.kind = variable.kind; - lexical_variable.name = variable.name; - symbol_replacer.replacements.insert(i, index); + lexical.kind = interned.kinds[i]; + lexical.name = v.name; + replacements.insert(i as u32, sym.index as u32); continue; } } - variables.push(variable); + retained.push(*v); + kinds.push(interned.kinds[i]); + } + g.variables = retained; + + // Prefix-sum renumbering + let mut shift = vec![0u32; old_len]; + let mut removed = 0u32; + for (i, slot) in shift.iter_mut().enumerate() { + *slot = removed; + if replacements.contains_key(&(i as u32)) { + removed += 1; + } + } + let replace_symbol = |s: Symbol| { + if !s.is_non_terminal() { + return s; + } + replacements.get(&(s.index as u32)).map_or_else( + || Symbol::non_terminal(s.index - shift[s.index] as usize), + |&r| Symbol::terminal(r as usize), + ) + }; + + for v in &g.variables { + renumber_root(&mut g.pool, v.root, &replace_symbol, &mut stack); + } + for &root in &g.external_roots { + renumber_root(&mut g.pool, root, &replace_symbol, &mut stack); } - for variable in &mut variables { - variable.rule = symbol_replacer.replace_symbols_in_rule(&variable.rule); - } - - let expected_conflicts = grammar - .expected_conflicts - .into_iter() - .map(|conflict| { - let mut result = conflict - .iter() - .map(|symbol| symbol_replacer.replace_symbol(*symbol)) - .collect::>(); + // Renumber each conflict through absorption, then canonicalize + let conflicts = interned + .conflicts + .iter() + .map(|c| { + let mut result: Vec = c.iter().map(|&s| replace_symbol(s)).collect(); result.sort_unstable(); result.dedup(); result }) .collect(); - let supertype_symbols: Vec = grammar - .supertype_symbols - .into_iter() - .map(|symbol| symbol_replacer.replace_symbol(symbol)) - .collect(); - for supertype_symbol in &supertype_symbols { - if supertype_symbol.is_terminal() { - Err(ExtractTokensError::SupertypeTerminal( - lexical_variables[supertype_symbol.index].name.clone(), + let supertypes = interned + .supertypes + .iter() + .map(|&s| { + let sym = replace_symbol(s); + // A supertype that got absorbed into a token isn't allowed + if sym.is_terminal() { + Err(ExtractTokensError::SupertypeTerminal( + g.pool + .resolve(extractor.lexical[sym.index].name) + .to_string(), + )) + } else { + Ok(sym) + } + }) + .collect::>>()?; + + let inline = interned.inline.iter().map(|&s| replace_symbol(s)).collect(); + + let mut separator_roots = Vec::new(); + let mut extra_symbols = Vec::with_capacity(g.extra_roots.len()); + for &root in &g.extra_roots { + if let Some(s) = g.pool.node(root).symbol() { + extra_symbols.push(replace_symbol(s)); + } else if let Some(i) = extractor.find(&g.pool, root) { + extra_symbols.push(Symbol::terminal(i as usize)); + } else { + separator_roots.push(root); + } + } + + let mut external_tokens = Vec::with_capacity(g.external_roots.len()); + for (&root, &(name, kind)) in g.external_roots.iter().zip(&interned.external_tokens) { + let Some(s) = g.pool.node(root).symbol() else { + Err(ExtractTokensError::NonSymbolExternalToken)? + }; + if s.is_non_terminal() { + Err(ExtractTokensError::ExternalTokenNonTerminal( + g.pool.resolve(g.variables[s.index].name).to_string(), ))?; } - } - - let variables_to_inline = grammar - .variables_to_inline - .into_iter() - .map(|symbol| symbol_replacer.replace_symbol(symbol)) - .collect(); - - let mut separators = Vec::new(); - let mut extra_symbols = Vec::new(); - for rule in grammar.extra_symbols { - if let Rule::Symbol(symbol) = rule { - extra_symbols.push(symbol_replacer.replace_symbol(symbol)); - } else if let Some(index) = lexical_variables.iter().position(|v| v.rule == rule) { - extra_symbols.push(Symbol::terminal(index)); - } else { - separators.push(rule); - } - } - - let mut external_tokens = Vec::with_capacity(grammar.external_tokens.len()); - for external_token in grammar.external_tokens { - let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule); - if let Rule::Symbol(symbol) = rule { - if symbol.is_non_terminal() { - Err(ExtractTokensError::ExternalTokenNonTerminal( - variables[symbol.index].name.clone(), - ))?; - } - - if symbol.is_external() { - external_tokens.push(ExternalToken { - name: external_token.name, - kind: external_token.kind, - corresponding_internal_token: None, - }); - } else { - external_tokens.push(ExternalToken { - name: lexical_variables[symbol.index].name.clone(), - kind: external_token.kind, - corresponding_internal_token: Some(symbol), - }); + external_tokens.push(if s.is_external() { + let Some(name) = name else { + Err(ExtractTokensError::NonSymbolExternalToken)? + }; + ExternalToken { + name, + kind, + corresponding_internal_token: None, } } else { - Err(ExtractTokensError::NonSymbolExternalToken)?; - } + ExternalToken { + name: extractor.lexical[s.index].name, + kind, + corresponding_internal_token: Some(s), + } + }); } - let word_token = if let Some(token) = grammar.word_token { - let token = symbol_replacer.replace_symbol(token); - if token.is_non_terminal() { - let word_token_variable = &variables[token.index]; - let conflicting_symbol_name = variables + let word = match interned.word.map(replace_symbol) { + Some(token) if token.is_non_terminal() => { + let word_root = g.variables[token.index].root; + let conflicting_symbol_name = g + .variables .iter() .enumerate() - .find(|(i, v)| *i != token.index && v.rule == word_token_variable.rule) - .map(|(_, v)| v.name.clone()); - + .find(|(i, v)| *i != token.index && g.pool.subtree_eq(v.root, word_root)) + .map(|(_, v)| g.pool.resolve(v.name).to_string()); Err(ExtractTokensError::WordToken(NonTerminalWordTokenError { - symbol_name: word_token_variable.name.clone(), + symbol_name: g.pool.resolve(g.variables[token.index].name).to_string(), conflicting_symbol_name, - }))?; + }))? } - Some(token) - } else { - None + word => word, }; - let mut reserved_word_contexts = Vec::with_capacity(grammar.reserved_word_sets.len()); - for reserved_word_context in grammar.reserved_word_sets { - let mut reserved_words = Vec::with_capacity(reserved_word_contexts.len()); - for reserved_rule in reserved_word_context.reserved_words { - if let Rule::Symbol(symbol) = reserved_rule { - reserved_words.push(symbol_replacer.replace_symbol(symbol)); - } else if let Some(index) = lexical_variables - .iter() - .position(|v| v.rule == reserved_rule) - { - reserved_words.push(Symbol::terminal(index)); + let mut reserved_sets = Vec::with_capacity(g.reserved_sets.len()); + for set in &g.reserved_sets { + let mut symbols = Vec::with_capacity(set.roots.len()); + for &root in &set.roots { + if let Some(s) = g.pool.node(root).symbol() { + symbols.push(replace_symbol(s)); + } else if let Some(i) = extractor.find(&g.pool, root) { + symbols.push(Symbol::terminal(i as usize)); } else { - let rule = if let Rule::Metadata { rule, .. } = &reserved_rule { - rule.as_ref() - } else { - &reserved_rule + let inner = match g.pool.node(root) { + Rule::Metadata { rule, .. } => g.pool.node(rule), + node => node, }; - let token_name = match rule { - Rule::String(s) => s.clone(), - Rule::Pattern(p, _) => p.clone(), + let token_name = match inner { + Rule::String(s) | Rule::Pattern(s, _) => g.pool.resolve(s).to_string(), _ => "unknown".to_string(), }; Err(ExtractTokensError::NonTokenReservedWord(token_name))?; } } - reserved_word_contexts.push(ReservedWordContext { - name: reserved_word_context.name, - reserved_words, - }); + reserved_sets.push((set.name, symbols)); } - Ok(( - ExtractedSyntaxGrammar { - variables, - expected_conflicts, - extra_symbols, - variables_to_inline, - supertype_symbols, - external_tokens, - word_token, - precedence_orderings: grammar.precedence_orderings, - reserved_word_sets: reserved_word_contexts, - }, - ExtractedLexicalGrammar { - variables: lexical_variables, - separators, - }, - )) + Ok(ExtractedGrammarMeta { + kinds, + lexical_variables: extractor.lexical, + separator_roots, + extra_symbols, + external_tokens, + reserved_sets, + supertypes, + conflicts, + inline, + word, + }) } -struct TokenExtractor { - current_variable_name: String, - current_variable_token_count: usize, - is_first_rule: bool, - extracted_variables: Vec, - extracted_usage_counts: Vec, -} - -struct SymbolReplacer { - replacements: FxHashMap, -} - -impl TokenExtractor { - fn extract_tokens_in_variable( - &mut self, - is_first: bool, - variable: &mut Variable, - ) -> ExtractTokensResult<()> { - self.current_variable_name.clear(); - self.current_variable_name.push_str(&variable.name); - self.current_variable_token_count = 0; - self.is_first_rule = is_first; - variable.rule = self.extract_tokens_in_rule(&variable.rule)?; - Ok(()) - } - - fn extract_tokens_in_rule(&mut self, input: &Rule) -> ExtractTokensResult { - match input { - Rule::String(name) => Ok(self.extract_token(input, Some(name))?.into()), - Rule::Pattern(..) => Ok(self.extract_token(input, None)?.into()), - Rule::Metadata { params, rule } => { - if params.is_token { - let mut params = params.clone(); - params.is_token = false; - - let string_value = if let Rule::String(value) = rule.as_ref() { - Some(value) - } else { - None - }; - - let rule_to_extract = if params == MetadataParams::default() { - rule.as_ref() - } else { - input - }; - - Ok(self.extract_token(rule_to_extract, string_value)?.into()) - } else { - Ok(Rule::Metadata { - params: params.clone(), - rule: Box::new(self.extract_tokens_in_rule(rule)?), - }) - } +fn renumber_root( + pool: &mut RulePool, + root: RuleId, + replace: &impl Fn(Symbol) -> Symbol, + stack: &mut Vec, +) { + stack.clear(); + stack.push(root); + while let Some(id) = stack.pop() { + match pool.node(id) { + Rule::Sym { kind, index } => { + let s = Symbol { + kind, + index: index as usize, + }; + let replaced = replace(s); + pool.set_node(id, Rule::from(replaced)); } - Rule::Repeat(content) => Ok(Rule::Repeat(Box::new( - self.extract_tokens_in_rule(content)?, - ))), - Rule::Seq(elements) => Ok(Rule::Seq( - elements - .iter() - .map(|e| self.extract_tokens_in_rule(e)) - .collect::>>()?, - )), - Rule::Choice(elements) => Ok(Rule::Choice( - elements - .iter() - .map(|e| self.extract_tokens_in_rule(e)) - .collect::>>()?, - )), - Rule::Reserved { rule, context_name } => Ok(Rule::Reserved { - rule: Box::new(self.extract_tokens_in_rule(rule)?), - context_name: context_name.clone(), - }), - _ => Ok(input.clone()), - } - } - - fn extract_token( - &mut self, - rule: &Rule, - string_value: Option<&String>, - ) -> ExtractTokensResult { - for (i, variable) in self.extracted_variables.iter_mut().enumerate() { - if variable.rule == *rule { - self.extracted_usage_counts[i] += 1; - return Ok(Symbol::terminal(i)); + Rule::Seq(range) | Rule::Choice(range) => { + stack.extend_from_slice(pool.child_slice(range)); } + Rule::Repeat(inner) + | Rule::Metadata { rule: inner, .. } + | Rule::Reserved { rule: inner, .. } => stack.push(inner), + _ => {} } - - let index = self.extracted_variables.len(); - let variable = if let Some(string_value) = string_value { - if string_value.is_empty() && !self.is_first_rule { - Err(ExtractTokensError::EmptyString( - self.current_variable_name.clone(), - ))?; - } - Variable { - name: string_value.clone(), - kind: VariableType::Anonymous, - rule: rule.clone(), - } - } else { - self.current_variable_token_count += 1; - Variable { - name: format!( - "{}_token{}", - self.current_variable_name, self.current_variable_token_count - ), - kind: VariableType::Auxiliary, - rule: rule.clone(), - } - }; - - self.extracted_variables.push(variable); - self.extracted_usage_counts.push(1); - Ok(Symbol::terminal(index)) - } -} - -impl SymbolReplacer { - fn replace_symbols_in_rule(&mut self, rule: &Rule) -> Rule { - match rule { - Rule::Symbol(symbol) => self.replace_symbol(*symbol).into(), - Rule::Choice(elements) => Rule::Choice( - elements - .iter() - .map(|e| self.replace_symbols_in_rule(e)) - .collect(), - ), - Rule::Seq(elements) => Rule::Seq( - elements - .iter() - .map(|e| self.replace_symbols_in_rule(e)) - .collect(), - ), - Rule::Repeat(content) => Rule::Repeat(Box::new(self.replace_symbols_in_rule(content))), - Rule::Metadata { rule, params } => Rule::Metadata { - params: params.clone(), - rule: Box::new(self.replace_symbols_in_rule(rule)), - }, - Rule::Reserved { rule, context_name } => Rule::Reserved { - rule: Box::new(self.replace_symbols_in_rule(rule)), - context_name: context_name.clone(), - }, - _ => rule.clone(), - } - } - - fn replace_symbol(&self, symbol: Symbol) -> Symbol { - if !symbol.is_non_terminal() { - return symbol; - } - - if let Some(replacement) = self.replacements.get(&symbol.index) { - return Symbol::terminal(*replacement); - } - - let mut adjusted_index = symbol.index; - for replaced_index in self.replacements.keys() { - if *replaced_index < symbol.index { - adjusted_index -= 1; - } - } - - Symbol::non_terminal(adjusted_index) } } #[cfg(test)] mod test { + use crate::{ + grammars::Variable, + prepare_grammar::{extract_tokens, intern_symbols}, + rules::SymbolType, + }; + use super::*; #[test] fn test_extraction() { - let (syntax_grammar, lexical_grammar) = extract_tokens(build_grammar(vec![ - Variable::named( - "rule_0", - Rule::repeat(Rule::seq(vec![ - Rule::string("a"), - Rule::pattern("b", ""), - Rule::choice(vec![ - Rule::non_terminal(1), - Rule::non_terminal(2), - Rule::token(Rule::repeat(Rule::choice(vec![ - Rule::string("c"), - Rule::string("d"), - ]))), - ]), - ])), - ), - Variable::named("rule_1", Rule::pattern("e", "")), - Variable::named("rule_2", Rule::pattern("b", "")), - Variable::named( - "rule_3", - Rule::seq(vec![Rule::non_terminal(2), Rule::Blank]), - ), - ])) - .unwrap(); + let mut pool = RulePool::default(); + // rule_0: repeat(seq("a", /b/, choice(rule_1, rule_2, token(repeat(choice("c", "d")))))) + let r0 = { + let a = str(&mut pool, "a"); + let b = pat(&mut pool, "b"); + let (r1, r2) = (n_sym(&mut pool, "rule_1"), n_sym(&mut pool, "rule_2")); + let tok = { + let (c, d) = (str(&mut pool, "c"), str(&mut pool, "d")); + let cd = pool.choice(&[c, d]); + let rep = pool.repeat(cd); + pool.token(rep) + }; + let ch = pool.choice(&[r1, r2, tok]); + let sq = pool.seq(&[a, b, ch]); + pool.repeat(sq) + }; + let r1 = pat(&mut pool, "e"); + let r2 = pat(&mut pool, "b"); + let r3 = { + let (r, bl) = (n_sym(&mut pool, "rule_2"), pool.blank()); + pool.seq(&[r, bl]) + }; + #[rustfmt::skip] + let variables = vec![ + Variable { name: pool.intern("rule_0"), root: r0 }, + Variable { name: pool.intern("rule_1"), root: r1 }, + Variable { name: pool.intern("rule_2"), root: r2 }, + Variable { name: pool.intern("rule_3"), root: r3 }, + ]; + let mut grammar = pool_grammar(pool, variables); + let ext = extract(&mut grammar).unwrap(); - assert_eq!( - syntax_grammar.variables, - vec![ - Variable::named( - "rule_0", - Rule::repeat(Rule::seq(vec![ - // The string "a" was replaced by a symbol referencing the lexical grammar - Rule::terminal(0), - // The pattern "b" was replaced by a symbol referencing the lexical grammar - Rule::terminal(1), - Rule::choice(vec![ - // The symbol referencing `rule_1` was replaced by a symbol referencing - // the lexical grammar. - Rule::terminal(3), - // The symbol referencing `rule_2` had its index decremented because - // `rule_1` was moved to the lexical grammar. - Rule::non_terminal(1), - // The rule wrapped in `token` was replaced by a symbol referencing - // the lexical grammar. - Rule::terminal(2), - ]) - ])) - ), - // The pattern "e" was only used in one place: as the definition of `rule_1`, - // so that rule was moved to the lexical grammar. The pattern "b" appeared in - // two places, so it was not moved into the lexical grammar. - Variable::named("rule_2", Rule::terminal(1)), - Variable::named( - "rule_3", - Rule::seq(vec![Rule::non_terminal(1), Rule::Blank,]) - ), - ] + // rule_1 was absorbed into the lexical grammar, rule_0, rule_2, and rule_3 remain + let names = grammar + .variables + .iter() + .map(|v| grammar.pool.resolve(v.name)) + .collect::>(); + assert_eq!(names, ["rule_0", "rule_2", "rule_3"]); + assert_eq!(ext.kinds, [VariableType::Named; 3]); + + // rule_0: repeat(seq(terminal(0), terminal(1), choice(terminal(3), non_terminal(2), terminal(2)))) + // - Its leaves became terminals: "a"->t0, "b"->t1, token(...)->t2. + // - Its symbol refs changed: rule_1 was absorbed into the lexer (now t3), + // rule_2's index dropped to 1 because rule_1 was removed. + let expected_r0 = { + let p = &mut grammar.pool; + let ch = { + let (a, b, c) = (term(p, 3), non_term(p, 1), term(p, 2)); + p.choice(&[a, b, c]) + }; + let sq = { + let (a, b) = (term(p, 0), term(p, 1)); + p.seq(&[a, b, ch]) + }; + p.repeat(sq) + }; + assert!( + grammar + .pool + .subtree_eq(grammar.variables[0].root, expected_r0) ); + // rule_2 is `/b/` -> terminal(1). It is *not* absorbed: `/b/` appears in + // two places (rule_0 and rule_2), so its terminal is used more than once. assert_eq!( - lexical_grammar.variables, - vec![ - Variable::anonymous("a", Rule::string("a")), - Variable::auxiliary("rule_0_token1", Rule::pattern("b", "")), - Variable::auxiliary( - "rule_0_token2", - Rule::repeat(Rule::choice(vec![Rule::string("c"), Rule::string("d"),])) - ), - Variable::named("rule_1", Rule::pattern("e", "")), + grammar.pool.node(grammar.variables[1].root), + Rule::Sym { + kind: SymbolType::Terminal, + index: 1 + } + ); + + // rule_3: seq(non_terminal(1), blank) -> rule_2 decremented after rule_1's removal + let expected_r3 = { + let p = &mut grammar.pool; + let (nt, bl) = (non_term(p, 1), p.blank()); + p.seq(&[nt, bl]) + }; + assert!( + grammar + .pool + .subtree_eq(grammar.variables[2].root, expected_r3) + ); + + // `/e/` is used in exactly one place (as rule_1's whole body), so rule_1 + // was absorbed into the lexical grammar (and donated its name to the token). + let lex = ext + .lexical_variables + .iter() + .map(|v| (grammar.pool.resolve(v.name), v.kind)) + .collect::>(); + assert_eq!( + lex, + [ + ("a", VariableType::Anonymous), + ("rule_0_token1", VariableType::Auxiliary), + ("rule_0_token2", VariableType::Auxiliary), + ("rule_1", VariableType::Named), ] ); + let roots = ext + .lexical_variables + .iter() + .map(|v| v.root) + .collect::>(); + let e0 = str(&mut grammar.pool, "a"); + assert!(grammar.pool.subtree_eq(roots[0], e0)); + let e1 = pat(&mut grammar.pool, "b"); + assert!(grammar.pool.subtree_eq(roots[1], e1)); + let e2 = { + let p = &mut grammar.pool; + let (c, d) = (str(p, "c"), str(p, "d")); + let cd = p.choice(&[c, d]); + p.repeat(cd) + }; + assert!(grammar.pool.subtree_eq(roots[2], e2)); + let e3 = pat(&mut grammar.pool, "e"); + assert!(grammar.pool.subtree_eq(roots[3], e3)); } #[test] fn test_start_rule_is_token() { - let (syntax_grammar, lexical_grammar) = - extract_tokens(build_grammar(vec![Variable::named( - "rule_0", - Rule::string("hello"), - )])) - .unwrap(); + // The start rule is a bare token. The token is extracted, but the start + // rule is never absorbed. It stays as a reference to terminal 0. + let mut pool = RulePool::default(); + let r0 = str(&mut pool, "hello"); + let variables = vec![Variable { + name: pool.intern("rule_0"), + root: r0, + }]; + let mut grammar = pool_grammar(pool, variables); + let ext = extract(&mut grammar).unwrap(); + let names = grammar + .variables + .iter() + .map(|v| grammar.pool.resolve(v.name)) + .collect::>(); + assert_eq!(names, ["rule_0"]); assert_eq!( - syntax_grammar.variables, - vec![Variable::named("rule_0", Rule::terminal(0)),] - ); - assert_eq!( - lexical_grammar.variables, - vec![Variable::anonymous("hello", Rule::string("hello")),] + grammar.pool.node(grammar.variables[0].root), + Rule::Sym { + kind: SymbolType::Terminal, + index: 0 + } ); + + let lex = ext + .lexical_variables + .iter() + .map(|v| (grammar.pool.resolve(v.name), v.kind)) + .collect::>(); + assert_eq!(lex, [("hello", VariableType::Anonymous)]); + let e = str(&mut grammar.pool, "hello"); + assert!(grammar.pool.subtree_eq(ext.lexical_variables[0].root, e)); } #[test] fn test_extracting_extra_symbols() { - let mut grammar = build_grammar(vec![ - Variable::named("rule_0", Rule::string("x")), - Variable::named("comment", Rule::pattern("//.*", "")), - ]); - grammar.extra_symbols = vec![Rule::string(" "), Rule::non_terminal(1)]; + // extras split two ways: + // - the `comment` ref (absorbed into a token) becomes an extra terminal + // - the bare " " string becomes a separator + let mut pool = RulePool::default(); + let r0 = str(&mut pool, "x"); + let comment = pat(&mut pool, "//.*"); + let sep = str(&mut pool, " "); + let extra_ref = n_sym(&mut pool, "comment"); + let variables = vec![ + Variable { + name: pool.intern("rule_0"), + root: r0, + }, + Variable { + name: pool.intern("comment"), + root: comment, + }, + ]; + let mut grammar = pool_grammar(pool, variables); + grammar.extra_roots = vec![sep, extra_ref]; + let ext = extract(&mut grammar).unwrap(); - let (syntax_grammar, lexical_grammar) = extract_tokens(grammar).unwrap(); - assert_eq!(syntax_grammar.extra_symbols, vec![Symbol::terminal(1),]); - assert_eq!(lexical_grammar.separators, vec![Rule::string(" "),]); + // comment's `//.*` was single use-> absorbed by terminal(1), so the extra ref + // resolves to it + assert_eq!(ext.extra_symbols, [Symbol::terminal(1)]); + // the " " string routes to separators, not a token symbol + assert_eq!(ext.separator_roots.len(), 1); + let e = str(&mut grammar.pool, " "); + assert!(grammar.pool.subtree_eq(ext.separator_roots[0], e)); } #[test] fn test_extract_externals() { - let mut grammar = build_grammar(vec![ - Variable::named( - "rule_0", - Rule::seq(vec![ - Rule::external(0), - Rule::string("a"), - Rule::non_terminal(1), - Rule::non_terminal(2), - ]), - ), - Variable::named("rule_1", Rule::string("b")), - Variable::named("rule_2", Rule::string("c")), - ]); - grammar.external_tokens = vec![ - Variable::named("external_0", Rule::external(0)), - Variable::anonymous("a", Rule::string("a")), - Variable::named("rule_2", Rule::non_terminal(2)), + let mut pool = RulePool::default(); + // rule_0: seq(external_0, "a", rule_1, rule_2) + let r0 = { + let e = n_sym(&mut pool, "external_0"); + let a = str(&mut pool, "a"); + let (r1, r2) = (n_sym(&mut pool, "rule_1"), n_sym(&mut pool, "rule_2")); + pool.seq(&[e, a, r1, r2]) + }; + let r1 = str(&mut pool, "b"); + let r2 = str(&mut pool, "c"); + // externals: [external_0, "a", rule_2] + let e0 = n_sym(&mut pool, "external_0"); + let ea = str(&mut pool, "a"); + let er2 = n_sym(&mut pool, "rule_2"); + let variables = vec![ + Variable { + name: pool.intern("rule_0"), + root: r0, + }, + Variable { + name: pool.intern("rule_1"), + root: r1, + }, + Variable { + name: pool.intern("rule_2"), + root: r2, + }, ]; - - let (syntax_grammar, _) = extract_tokens(grammar).unwrap(); + let external_0 = pool.intern("external_0"); + let a = pool.intern("a"); + let rule_2 = pool.intern("rule_2"); + let mut grammar = pool_grammar(pool, variables); + grammar.external_roots = vec![e0, ea, er2]; + let ext = extract(&mut grammar).unwrap(); assert_eq!( - syntax_grammar.external_tokens, - vec![ + ext.external_tokens, + [ + // a genuine external, no internal counterpart ExternalToken { - name: "external_0".to_string(), + name: external_0, kind: VariableType::Named, corresponding_internal_token: None, }, + // "a" is also extracted internally (terminal 0), so it links to it ExternalToken { - name: "a".to_string(), + name: a, kind: VariableType::Anonymous, corresponding_internal_token: Some(Symbol::terminal(0)), }, + // rule_2 shadowed a variable that got absorbed to terminal 2 ExternalToken { - name: "rule_2".to_string(), + name: rule_2, kind: VariableType::Named, corresponding_internal_token: Some(Symbol::terminal(2)), }, @@ -586,69 +723,145 @@ mod test { #[test] fn test_error_on_external_with_same_name_as_non_terminal() { - let mut grammar = build_grammar(vec![ - Variable::named( - "rule_0", - Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(2)]), - ), - Variable::named( - "rule_1", - Rule::seq(vec![Rule::non_terminal(2), Rule::non_terminal(2)]), - ), - Variable::named("rule_2", Rule::string("a")), - ]); - grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))]; + let mut pool = RulePool::default(); + let r0 = { + let (a, b) = (n_sym(&mut pool, "rule_1"), n_sym(&mut pool, "rule_2")); + pool.seq(&[a, b]) + }; + let r1 = { + let (a, b) = (n_sym(&mut pool, "rule_2"), n_sym(&mut pool, "rule_2")); + pool.seq(&[a, b]) + }; + let r2 = str(&mut pool, "a"); + let ext_ref = n_sym(&mut pool, "rule_1"); + let variables = vec![ + Variable { + name: pool.intern("rule_0"), + root: r0, + }, + Variable { + name: pool.intern("rule_1"), + root: r1, + }, + Variable { + name: pool.intern("rule_2"), + root: r2, + }, + ]; + let mut grammar = pool_grammar(pool, variables); + grammar.external_roots = vec![ext_ref]; - let result = extract_tokens(grammar); - assert!(result.is_err(), "Expected an error but got no error"); - let err = result.err().unwrap(); + // rule_1 is a seq (not absorbable), so it stays a non-terminal. A non-terminal + // non-terminal can't also be an external token. + let err = extract(&mut grammar).unwrap_err(); assert_eq!( - err.to_string(), - "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule" + err, + ExtractTokensError::ExternalTokenNonTerminal("rule_1".to_string()) ); } #[test] fn test_extraction_on_hidden_terminal() { - let (syntax_grammar, lexical_grammar) = extract_tokens(build_grammar(vec![ - Variable::named("rule_0", Rule::non_terminal(1)), - Variable::hidden("_rule_1", Rule::string("a")), - ])) - .unwrap(); + // `_rule_1` is hidden and its token "a" is anonymous, so the absorption guard declines. + // Both variables stay, and the anonymous token keeps its own name. + let mut pool = RulePool::default(); + let r0 = n_sym(&mut pool, "_rule_1"); + let r1 = str(&mut pool, "a"); + let variables = vec![ + Variable { + name: pool.intern("rule_0"), + root: r0, + }, + Variable { + name: pool.intern("_rule_1"), + root: r1, + }, + ]; + let mut grammar = pool_grammar(pool, variables); + let ext = extract(&mut grammar).unwrap(); - // The rule `_rule_1` should not "absorb" the - // terminal "a", since it is hidden, - // so we expect two variables still + let names = grammar + .variables + .iter() + .map(|v| grammar.pool.resolve(v.name)) + .collect::>(); + assert_eq!(names, ["rule_0", "_rule_1"]); + assert_eq!(ext.kinds, [VariableType::Named, VariableType::Hidden]); + #[rustfmt::skip] assert_eq!( - syntax_grammar.variables, - vec![ - Variable::named("rule_0", Rule::non_terminal(1)), - Variable::hidden("_rule_1", Rule::terminal(0)), - ] + grammar.pool.node(grammar.variables[0].root), + Rule::Sym { kind: SymbolType::NonTerminal, index: 1 } + ); + #[rustfmt::skip] + assert_eq!( + grammar.pool.node(grammar.variables[1].root), + Rule::Sym { kind: SymbolType::Terminal, index: 0 } ); - // We should not have a hidden rule in our lexical grammar, only the terminal "a" - assert_eq!( - lexical_grammar.variables, - vec![Variable::anonymous("a", Rule::string("a"))] - ); + let lex = ext + .lexical_variables + .iter() + .map(|v| (grammar.pool.resolve(v.name), v.kind)) + .collect::>(); + assert_eq!(lex, [("a", VariableType::Anonymous)]); + let e = str(&mut grammar.pool, "a"); + assert!(grammar.pool.subtree_eq(ext.lexical_variables[0].root, e)); } #[test] fn test_extraction_with_empty_string() { - assert!( - extract_tokens(build_grammar(vec![ - Variable::named("rule_0", Rule::non_terminal(1)), - Variable::hidden("_rule_1", Rule::string("")), - ])) - .is_err() + // An empty string outside the start rule is an error. + let mut pool = RulePool::default(); + let r0 = n_sym(&mut pool, "_rule_1"); + let r1 = str(&mut pool, ""); + #[rustfmt::skip] + let variables = vec![ + Variable { name: pool.intern("rule_0"), root: r0 }, + Variable { name: pool.intern("_rule_1"), root: r1 }, + ]; + let mut grammar = pool_grammar(pool, variables); + assert_eq!( + extract(&mut grammar).unwrap_err(), + ExtractTokensError::EmptyString("_rule_1".to_string()) ); } - fn build_grammar(variables: Vec) -> InternedGrammar { - InternedGrammar { + fn str(p: &mut RulePool, t: &str) -> RuleId { + let id = p.intern(t); + p.string(id) + } + fn pat(p: &mut RulePool, t: &str) -> RuleId { + let (v, f) = (p.intern(t), p.intern("")); + p.pattern(v, f) + } + fn n_sym(p: &mut RulePool, n: &str) -> RuleId { + let id = p.intern(n); + p.named_symbol(id) + } + + 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 pool_grammar(pool: RulePool, variables: Vec) -> InputGrammar { + InputGrammar { + pool, variables, ..Default::default() } } + + fn extract(g: &mut InputGrammar) -> ExtractTokensResult { + let meta = intern_symbols(g, &mut Vec::new()).unwrap(); + extract_tokens(g, &meta) + } } diff --git a/crates/generate/src/prepare_grammar/flatten_grammar.rs b/crates/generate/src/prepare_grammar/flatten_grammar.rs index ab89c8e5c..ac03929c8 100644 --- a/crates/generate/src/prepare_grammar/flatten_grammar.rs +++ b/crates/generate/src/prepare_grammar/flatten_grammar.rs @@ -1,22 +1,26 @@ use rustc_hash::FxHashMap; - use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::ExtractedSyntaxGrammar; use crate::{ grammars::{ - Production, ProductionStep, ReservedWordSetId, SyntaxGrammar, SyntaxVariable, Variable, + InputGrammar, Production, ProductionStep, ProductionStore, SyntaxGrammar, SyntaxVariable, }, - rules::{Alias, Associativity, Precedence, Rule, Symbol, TokenSet}, + prepare_grammar::extract_tokens::ExtractedGrammarMeta, + rules::{ + Alias, Associativity, Precedence, Rule, RuleId, RulePool, Symbol, SymbolType, TokenSet, + }, + strpool::{StrId, StrPool}, }; pub type FlattenGrammarResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum FlattenGrammarError { #[error("No such reserved word set: {0}")] NoReservedWordSet(String), + #[error("Reserved word set count {0} exceeds the maximum of {max}", max = u16::MAX)] + TooManyReservedWordSets(usize), #[error( "The rule `{0}` matches the empty string. @@ -29,337 +33,395 @@ unless they are used only as the grammar's start rule. RecursiveInline(String), } -struct RuleFlattener { - production: Production, - reserved_word_set_ids: FxHashMap, - precedence_stack: Vec, - associativity_stack: Vec, - reserved_word_stack: Vec, - alias_stack: Vec, - field_name_stack: Vec, +#[derive(Clone, Copy, Default)] +struct FlattenCtx { + prec: Precedence, + assoc: Option, + alias: Option, + field: Option, + reserved: u16, } -impl RuleFlattener { - const fn new(reserved_word_set_ids: FxHashMap) -> Self { - Self { - production: Production { - steps: Vec::new(), - dynamic_precedence: 0, +/// Selects one branch at each choice encountered during a root-to-leaf walk. After +/// a production is emitted, `advance` increments this path like a mixed-radix counter. +/// Later decisions are discarded because choosing an earlier branch can expose a +/// different set of nested choices. +#[derive(Default)] +struct ChoiceCursor { + decisions: Vec, + arities: Vec, + depth: usize, +} + +impl ChoiceCursor { + /// Begin a new walk for a new variable. + fn reset(&mut self) { + self.decisions.clear(); + self.arities.clear(); + self.depth = 0; + } + + /// Begin a new walk for the same variable as a previous walk + fn begin_path(&mut self) { + self.arities.clear(); + self.depth = 0; + } + + /// Called at each choice. Selects which path to take, and records the decision. + fn select(&mut self, len: u32) -> u32 { + debug_assert!(len > 0); + let selected = self.decisions.get(self.depth).copied().unwrap_or(0); + debug_assert!(selected < len); + self.arities.push(len); + self.depth += 1; + selected + } + + /// Find the latest path that we can advance and discard every decision after it. + /// Returns whether a new path could be found. + fn advance(&mut self) -> bool { + for depth in (0..self.arities.len()).rev() { + let selected = self.decisions.get(depth).copied().unwrap_or(0); + if selected + 1 < self.arities[depth] { + self.decisions.resize(depth + 1, 0); + self.decisions[depth] = selected + 1; + return true; + } + } + false + } +} + +/// Reusable scratch for enumerating and flattening one production path at a time. +#[derive(Default)] +pub(super) struct FlattenState { + steps: Vec, + choices: ChoiceCursor, + dyn_prec: i32, + dead: bool, +} + +impl FlattenState { + fn reset_variable(&mut self) { + self.choices.reset(); + self.reset_path(); + } + + fn reset_path(&mut self) { + self.steps.clear(); + self.dyn_prec = 0; + self.dead = false; + self.choices.begin_path(); + } + + fn push_step(&mut self, kind: SymbolType, index: u32, ctx: FlattenCtx) { + self.steps.push(ProductionStep::pack( + Symbol { + kind, + index: index as usize, }, - reserved_word_set_ids, - precedence_stack: Vec::new(), - associativity_stack: Vec::new(), - reserved_word_stack: Vec::new(), - alias_stack: Vec::new(), - field_name_stack: Vec::new(), - } + ctx.prec, + ctx.assoc, + ctx.alias, + ctx.field, + ctx.reserved, + )); } - fn flatten_variable(&mut self, variable: Variable) -> FlattenGrammarResult { - let choices = extract_choices(variable.rule); - let mut productions = Vec::with_capacity(choices.len()); - for rule in choices { - let production = self.flatten_rule(rule)?; - if !productions.contains(&production) { - productions.push(production); - } - } - Ok(SyntaxVariable { - name: variable.name, - kind: variable.kind, - productions, - }) + fn restore_outer_prec(&mut self, outer: Precedence) { + let step = self.steps.last_mut().unwrap(); + step.set_precedence(outer); } - fn flatten_rule(&mut self, rule: Rule) -> FlattenGrammarResult { - self.production = Production::default(); - self.alias_stack.clear(); - self.reserved_word_stack.clear(); - self.precedence_stack.clear(); - self.associativity_stack.clear(); - self.field_name_stack.clear(); - self.apply(rule, true)?; - Ok(self.production.clone()) - } - - fn apply(&mut self, rule: Rule, at_end: bool) -> FlattenGrammarResult { - match rule { - Rule::Seq(members) => { - let mut result = false; - let last_index = members.len() - 1; - for (i, member) in members.into_iter().enumerate() { - result |= self.apply(member, i == last_index && at_end)?; - } - Ok(result) - } - Rule::Metadata { rule, params } => { - let mut has_precedence = false; - if !params.precedence.is_none() { - has_precedence = true; - self.precedence_stack.push(params.precedence); - } - - let mut has_associativity = false; - if let Some(associativity) = params.associativity { - has_associativity = true; - self.associativity_stack.push(associativity); - } - - let mut has_alias = false; - if let Some(alias) = params.alias { - has_alias = true; - self.alias_stack.push(alias); - } - - let mut has_field_name = false; - if let Some(field_name) = params.field_name { - has_field_name = true; - self.field_name_stack.push(field_name); - } - - if params.dynamic_precedence.abs() > self.production.dynamic_precedence.abs() { - self.production.dynamic_precedence = params.dynamic_precedence; - } - - let did_push = self.apply(*rule, at_end)?; - - if has_precedence { - self.precedence_stack.pop(); - if did_push && !at_end { - self.production.steps.last_mut().unwrap().precedence = self - .precedence_stack - .last() - .cloned() - .unwrap_or(Precedence::None); - } - } - - if has_associativity { - self.associativity_stack.pop(); - if did_push && !at_end { - self.production.steps.last_mut().unwrap().associativity = - self.associativity_stack.last().copied(); - } - } - - if has_alias { - self.alias_stack.pop(); - } - - if has_field_name { - self.field_name_stack.pop(); - } - - Ok(did_push) - } - Rule::Reserved { rule, context_name } => { - self.reserved_word_stack.push( - self.reserved_word_set_ids - .get(&context_name) - .copied() - .ok_or_else(|| { - FlattenGrammarError::NoReservedWordSet(context_name.clone()) - })?, - ); - let did_push = self.apply(*rule, at_end)?; - self.reserved_word_stack.pop(); - Ok(did_push) - } - Rule::Symbol(symbol) => { - self.production.steps.push(ProductionStep { - symbol, - precedence: self - .precedence_stack - .last() - .cloned() - .unwrap_or(Precedence::None), - associativity: self.associativity_stack.last().copied(), - reserved_word_set_id: self - .reserved_word_stack - .last() - .copied() - .unwrap_or_default(), - alias: self.alias_stack.last().cloned(), - field_name: self.field_name_stack.last().cloned(), - }); - Ok(true) - } - _ => Ok(false), - } + fn restore_outer_assoc(&mut self, outer: Option) { + let step = self.steps.last_mut().unwrap(); + step.set_associativity(outer); } } -fn extract_choices(rule: Rule) -> Vec { - match rule { - Rule::Seq(elements) => { - let mut result = vec![Rule::Blank]; - for element in elements { - let extraction = extract_choices(element); - let mut next_result = Vec::with_capacity(result.len()); - for entry in result { - for extraction_entry in &extraction { - next_result.push(Rule::Seq(vec![entry.clone(), extraction_entry.clone()])); - } - } - result = next_result; - } - result +/// Flatten one deterministic path. Choices only select a child, the outer loop in +/// the caller enumerates subsequent paths from the root. +fn apply( + pool: &RulePool, + reserved_ids: &FxHashMap, + node: RuleId, + f_ctx: FlattenCtx, + at_end: bool, + st: &mut FlattenState, +) -> FlattenGrammarResult { + match pool.node(node) { + Rule::Sym { kind, index } => { + st.push_step(kind, index, f_ctx); + Ok(true) } - Rule::Choice(elements) => { - let mut result = Vec::with_capacity(elements.len()); - for element in elements { - for rule in extract_choices(element) { - result.push(rule); + Rule::Seq(range) => { + let children = pool.child_slice(range); + let mut did_push = false; + for (i, &child) in children.iter().enumerate() { + did_push |= apply( + pool, + reserved_ids, + child, + f_ctx, + at_end && i + 1 == children.len(), + st, + )?; + if st.dead { + break; } } - result + Ok(did_push) } - Rule::Metadata { rule, params } => extract_choices(*rule) - .into_iter() - .map(|rule| Rule::Metadata { - rule: Box::new(rule), - params: params.clone(), - }) - .collect(), - Rule::Reserved { rule, context_name } => extract_choices(*rule) - .into_iter() - .map(|rule| Rule::Reserved { - rule: Box::new(rule), - context_name: context_name.clone(), - }) - .collect(), - _ => vec![rule], + Rule::Choice(range) => { + // An empty choice matches nothing, so no production can contain it. + if range.len == 0 { + st.dead = true; + return Ok(false); + } + let selected = st.choices.select(range.len); + let child = pool.child_slice(range)[selected as usize]; + apply(pool, reserved_ids, child, f_ctx, at_end, st) + } + Rule::Metadata { params, rule } => { + let params = pool.params(params); + let mut inner_ctx = f_ctx; + if params.precedence != Precedence::None { + inner_ctx.prec = params.precedence; + } + if params.associativity.is_some() { + inner_ctx.assoc = params.associativity; + } + if params.alias.is_some() { + inner_ctx.alias = params.alias; + } + if params.field.is_some() { + inner_ctx.field = params.field; + } + if params.dynamic_precedence.abs() > st.dyn_prec.abs() { + st.dyn_prec = params.dynamic_precedence; + } + + let did_push = apply(pool, reserved_ids, rule, inner_ctx, at_end, st)?; + // A step's prec/assoc governs the parse position just _after_ it, so the + // regions's last step owns the gap past it. If more steps follow, that gap + // is outside the region and reverts to the outer context. At the production's + // tail the gap is the reduce and keeps the region's own value. + if did_push && !at_end { + if params.precedence != Precedence::None { + st.restore_outer_prec(f_ctx.prec); + } + if params.associativity.is_some() { + st.restore_outer_assoc(f_ctx.assoc); + } + } + Ok(did_push) + } + Rule::Reserved { rule, ctx } => { + let Some(&reserved) = reserved_ids.get(&ctx) else { + return Err(FlattenGrammarError::NoReservedWordSet( + pool.resolve(ctx).to_string(), + )); + }; + let inner = FlattenCtx { reserved, ..f_ctx }; + apply(pool, reserved_ids, rule, inner, at_end, st) + } + _ => Ok(false), } } -fn symbol_is_used(variables: &[SyntaxVariable], symbol: Symbol) -> bool { - for variable in variables { - for production in &variable.productions { - for step in &production.steps { - if step.symbol == symbol { - return true; - } - } +/// Append the completed path as a production unless this variable already has an +/// identical one. +fn emit(st: &FlattenState, out: &mut ProductionStore, prod_start: u32) { + for p in &out.productions[prod_start as usize..] { + if p.dynamic_precedence == st.dyn_prec && out.steps[p.step_range()] == st.steps[..] { + return; } } - false + let steps_start = out.steps.len() as u32; + out.steps.extend_from_slice(&st.steps); + out.productions.push(Production { + steps_start, + steps_len: st.steps.len() as u32, + dynamic_precedence: st.dyn_prec, + }); } pub(super) fn flatten_grammar( - grammar: ExtractedSyntaxGrammar, -) -> FlattenGrammarResult { - let mut reserved_word_set_ids_by_name = FxHashMap::default(); - for (ix, set) in grammar.reserved_word_sets.iter().enumerate() { - reserved_word_set_ids_by_name.insert(set.name.clone(), ReservedWordSetId(ix)); + g: &InputGrammar, + meta: &ExtractedGrammarMeta, + st: &mut FlattenState, + out: &mut ProductionStore, +) -> FlattenGrammarResult<()> { + if meta.reserved_sets.len() > usize::from(u16::MAX) { + Err(FlattenGrammarError::TooManyReservedWordSets( + meta.reserved_sets.len(), + ))?; } - - let mut flattener = RuleFlattener::new(reserved_word_set_ids_by_name); - let variables = grammar - .variables - .into_iter() - .map(|variable| flattener.flatten_variable(variable)) - .collect::>>()?; - - for (i, variable) in variables.iter().enumerate() { - let symbol = Symbol::non_terminal(i); - let used = symbol_is_used(&variables, symbol); - - for production in &variable.productions { - if used && production.steps.is_empty() { - Err(FlattenGrammarError::EmptyString(variable.name.clone()))?; + // Last wins on duplicate names. + let reserved_ids: FxHashMap = meta + .reserved_sets + .iter() + .enumerate() + .map(|(i, (name, _))| (*name, i as u16)) + .collect(); + for v in &g.variables { + let prod_start = out.productions.len() as u32; + st.reset_variable(); + loop { + apply( + &g.pool, + &reserved_ids, + v.root, + FlattenCtx::default(), + true, + st, + )?; + if !st.dead { + emit(st, out, prod_start); } + if !st.choices.advance() { + break; + } + st.reset_path(); + } + out.var_prods + .push((prod_start, out.productions.len() as u32)); + } + check(g, meta, out) +} - if grammar.variables_to_inline.contains(&symbol) - && production.steps.iter().any(|step| step.symbol == symbol) +/// Post-flatten checks. No empty productions used in variables, and no recursive inlines. +fn check( + g: &InputGrammar, + meta: &ExtractedGrammarMeta, + out: &ProductionStore, +) -> FlattenGrammarResult<()> { + for (i, &(p_start, p_end)) in out.var_prods.iter().enumerate() { + let symbol = Symbol::non_terminal(i); + let used = out.steps.iter().any(|s| s.symbol() == symbol); + let inlined = meta.inline.contains(&symbol); + for p in &out.productions[p_start as usize..p_end as usize] { + if used && p.steps_len == 0 { + Err(FlattenGrammarError::EmptyString( + g.pool.resolve(g.variables[i].name).to_string(), + ))?; + } + if inlined + && out.steps[p.step_range()] + .iter() + .any(|s| s.symbol() == symbol) { - Err(FlattenGrammarError::RecursiveInline(variable.name.clone()))?; + Err(FlattenGrammarError::RecursiveInline( + g.pool.resolve(g.variables[i].name).to_string(), + ))?; } } } - let mut reserved_word_sets = grammar - .reserved_word_sets - .into_iter() - .map(|set| set.reserved_words.into_iter().collect()) - .collect::>(); + Ok(()) +} - // If no default reserved word set is specified, there are no reserved words. +pub(super) fn assemble_syntax_grammar( + g: InputGrammar, + meta: ExtractedGrammarMeta, + out: ProductionStore, +) -> (SyntaxGrammar, StrPool) { + let mut reserved_word_sets = meta + .reserved_sets + .iter() + .map(|(_, symbols)| symbols.iter().copied().collect()) + .collect::>(); if reserved_word_sets.is_empty() { reserved_word_sets.push(TokenSet::default()); } + let variables = g + .variables + .iter() + .zip(&meta.kinds) + .map(|(v, &kind)| SyntaxVariable { name: v.name, kind }) + .collect::>(); - Ok(SyntaxGrammar { - extra_symbols: grammar.extra_symbols, - expected_conflicts: grammar.expected_conflicts, - variables_to_inline: grammar.variables_to_inline, - precedence_orderings: grammar.precedence_orderings, - external_tokens: grammar.external_tokens, - supertype_symbols: grammar.supertype_symbols, - word_token: grammar.word_token, - reserved_word_sets, - variables, - }) + let interner = g.pool.into_interner(); + + ( + SyntaxGrammar { + variables, + extra_symbols: meta.extra_symbols, + expected_conflicts: meta.conflicts, + external_tokens: meta.external_tokens, + supertype_symbols: meta.supertypes, + variables_to_inline: meta.inline, + word_token: meta.word, + precedence_orderings: g.precedence_orderings, + reserved_word_sets, + + steps: out.steps, + productions: out.productions, + var_prods: out.var_prods, + }, + interner, + ) } #[cfg(test)] mod tests { use super::*; - use crate::grammars::VariableType; + use crate::grammars::{Variable, VariableType}; #[test] fn test_flatten_grammar() { - let mut flattener = RuleFlattener::new(FxHashMap::default()); - let result = flattener - .flatten_variable(Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::non_terminal(1), - Rule::prec_left( - Precedence::Integer(101), - Rule::seq(vec![ - Rule::non_terminal(2), - Rule::choice(vec![ - Rule::prec_right( - Precedence::Integer(102), - Rule::seq(vec![Rule::non_terminal(3), Rule::non_terminal(4)]), - ), - Rule::non_terminal(5), - ]), - Rule::non_terminal(6), - ]), - ), - Rule::non_terminal(7), - ]), - }) - .unwrap(); + let (grammar, interner) = flatten_named(|p| { + let nt1 = non_term(p, 1); + let nt2 = non_term(p, 2); + let nt3 = non_term(p, 3); + let nt4 = non_term(p, 4); + let nt5 = non_term(p, 5); + let nt6 = non_term(p, 6); + let nt7 = non_term(p, 7); + let inner = { + let s = p.seq(&[nt3, nt4]); + p.prec_right(Precedence::Integer(102), s) + }; + let choice = p.choice(&[inner, nt5]); + let pl = { + let s = p.seq(&[nt2, choice, nt6]); + p.prec_left(Precedence::Integer(101), s) + }; + p.seq(&[nt1, pl, nt7]) + }) + .unwrap(); assert_eq!( - result.productions, + prods(&grammar, 0, &interner), vec![ - Production { - dynamic_precedence: 0, + ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::non_terminal(2)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), - ProductionStep::new(Symbol::non_terminal(3)) - .with_prec(Precedence::Integer(102), Some(Associativity::Right)), - ProductionStep::new(Symbol::non_terminal(4)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), - ProductionStep::new(Symbol::non_terminal(6)), - ProductionStep::new(Symbol::non_terminal(7)), + StepView::new(Symbol::non_terminal(1)), + StepView::new(Symbol::non_terminal(2)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(3)) + .prec(Precedence::Integer(102)) + .assoc(Some(Associativity::Right)), + StepView::new(Symbol::non_terminal(4)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(6)), + StepView::new(Symbol::non_terminal(7)), ] }, - Production { - dynamic_precedence: 0, + ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::non_terminal(2)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), - ProductionStep::new(Symbol::non_terminal(5)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), - ProductionStep::new(Symbol::non_terminal(6)), - ProductionStep::new(Symbol::non_terminal(7)), + StepView::new(Symbol::non_terminal(1)), + StepView::new(Symbol::non_terminal(2)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(5)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(6)), + StepView::new(Symbol::non_terminal(7)), ] }, ] @@ -368,55 +430,50 @@ mod tests { #[test] fn test_flatten_grammar_with_maximum_dynamic_precedence() { - let mut flattener = RuleFlattener::new(FxHashMap::default()); - let result = flattener - .flatten_variable(Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::non_terminal(1), - Rule::prec_dynamic( - 101, - Rule::seq(vec![ - Rule::non_terminal(2), - Rule::choice(vec![ - Rule::prec_dynamic( - 102, - Rule::seq(vec![Rule::non_terminal(3), Rule::non_terminal(4)]), - ), - Rule::non_terminal(5), - ]), - Rule::non_terminal(6), - ]), - ), - Rule::non_terminal(7), - ]), - }) - .unwrap(); + let (grammar, interner) = flatten_named(|p| { + let nt1 = non_term(p, 1); + let nt2 = non_term(p, 2); + let nt3 = non_term(p, 3); + let nt4 = non_term(p, 4); + let nt5 = non_term(p, 5); + let nt6 = non_term(p, 6); + let nt7 = non_term(p, 7); + let inner = { + let s = p.seq(&[nt3, nt4]); + p.prec_dynamic(102, s) + }; + let choice = p.choice(&[inner, nt5]); + let pd = { + let s = p.seq(&[nt2, choice, nt6]); + p.prec_dynamic(101, s) + }; + p.seq(&[nt1, pd, nt7]) + }) + .unwrap(); assert_eq!( - result.productions, + prods(&grammar, 0, &interner), vec![ - Production { - dynamic_precedence: 102, + ProdView { + dyn_prec: 102, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::non_terminal(2)), - ProductionStep::new(Symbol::non_terminal(3)), - ProductionStep::new(Symbol::non_terminal(4)), - ProductionStep::new(Symbol::non_terminal(6)), - ProductionStep::new(Symbol::non_terminal(7)), - ], + StepView::new(Symbol::non_terminal(1)), + StepView::new(Symbol::non_terminal(2)), + StepView::new(Symbol::non_terminal(3)), + StepView::new(Symbol::non_terminal(4)), + StepView::new(Symbol::non_terminal(6)), + StepView::new(Symbol::non_terminal(7)), + ] }, - Production { - dynamic_precedence: 101, + ProdView { + dyn_prec: 101, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)), - ProductionStep::new(Symbol::non_terminal(2)), - ProductionStep::new(Symbol::non_terminal(5)), - ProductionStep::new(Symbol::non_terminal(6)), - ProductionStep::new(Symbol::non_terminal(7)), - ], + StepView::new(Symbol::non_terminal(1)), + StepView::new(Symbol::non_terminal(2)), + StepView::new(Symbol::non_terminal(5)), + StepView::new(Symbol::non_terminal(6)), + StepView::new(Symbol::non_terminal(7)), + ] }, ] ); @@ -424,49 +481,42 @@ mod tests { #[test] fn test_flatten_grammar_with_final_precedence() { - let mut flattener = RuleFlattener::new(FxHashMap::default()); - let result = flattener - .flatten_variable(Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::prec_left( - Precedence::Integer(101), - Rule::seq(vec![Rule::non_terminal(1), Rule::non_terminal(2)]), - ), - }) - .unwrap(); - + let (grammar, interner) = flatten_named(|p| { + let nt1 = non_term(p, 1); + let nt2 = non_term(p, 2); + let s = p.seq(&[nt1, nt2]); + p.prec_left(Precedence::Integer(101), s) + }) + .unwrap(); assert_eq!( - result.productions, - vec![Production { - dynamic_precedence: 0, + prods(&grammar, 0, &interner), + vec![ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), - ProductionStep::new(Symbol::non_terminal(2)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(1)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(2)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), ] }] ); - let result = flattener - .flatten_variable(Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::prec_left( - Precedence::Integer(101), - Rule::seq(vec![Rule::non_terminal(1)]), - ), - }) - .unwrap(); - + let (grammar, interner) = flatten_named(|p| { + let nt1 = non_term(p, 1); + let s = p.seq(&[nt1]); + p.prec_left(Precedence::Integer(101), s) + }) + .unwrap(); assert_eq!( - result.productions, - vec![Production { - dynamic_precedence: 0, + prods(&grammar, 0, &interner), + vec![ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::non_terminal(1)) - .with_prec(Precedence::Integer(101), Some(Associativity::Left)), + StepView::new(Symbol::non_terminal(1)) + .prec(Precedence::Integer(101)) + .assoc(Some(Associativity::Left)), ] }] ); @@ -474,38 +524,40 @@ mod tests { #[test] fn test_flatten_grammar_with_field_names() { - let mut flattener = RuleFlattener::new(FxHashMap::default()); - let result = flattener - .flatten_variable(Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::field("first-thing".to_string(), Rule::terminal(1)), - Rule::terminal(2), - Rule::choice(vec![ - Rule::Blank, - Rule::field("second-thing".to_string(), Rule::terminal(3)), - ]), - ]), - }) - .unwrap(); + let (grammar, interner) = flatten_named(|p| { + let t1 = term(p, 1); + let f1 = { + let n = p.intern("first-thing"); + p.field(n, t1) + }; + let t2 = term(p, 2); + let t3 = term(p, 3); + let f2 = { + let n = p.intern("second-thing"); + p.field(n, t3) + }; + let blank = p.blank(); + let choice = p.choice(&[blank, f2]); + p.seq(&[f1, t2, choice]) + }) + .unwrap(); assert_eq!( - result.productions, + prods(&grammar, 0, &interner), vec![ - Production { - dynamic_precedence: 0, + ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::terminal(1)).with_field_name("first-thing"), - ProductionStep::new(Symbol::terminal(2)) + StepView::new(Symbol::terminal(1)).field("first-thing"), + StepView::new(Symbol::terminal(2)), ] }, - Production { - dynamic_precedence: 0, + ProdView { + dyn_prec: 0, steps: vec![ - ProductionStep::new(Symbol::terminal(1)).with_field_name("first-thing"), - ProductionStep::new(Symbol::terminal(2)), - ProductionStep::new(Symbol::terminal(3)).with_field_name("second-thing"), + StepView::new(Symbol::terminal(1)).field("first-thing"), + StepView::new(Symbol::terminal(2)), + StepView::new(Symbol::terminal(3)).field("second-thing"), ] }, ] @@ -513,30 +565,232 @@ mod tests { } #[test] - fn test_flatten_grammar_with_recursive_inline_variable() { - let result = flatten_grammar(ExtractedSyntaxGrammar { - extra_symbols: Vec::new(), - expected_conflicts: Vec::new(), - variables_to_inline: vec![Symbol::non_terminal(0)], - precedence_orderings: Vec::new(), - external_tokens: Vec::new(), - supertype_symbols: Vec::new(), - word_token: None, - reserved_word_sets: Vec::new(), - variables: vec![Variable { - name: "test".to_string(), - kind: VariableType::Named, - rule: Rule::seq(vec![ - Rule::non_terminal(0), - Rule::non_terminal(1), - Rule::non_terminal(2), - ]), - }], - }); + fn test_precedence_inherited_through_inner_metadata() { + // A prec region inherited by an inner symbol that carries _different_ metadata + // (field). + let (grammar, interner) = flatten_named(|p| { + let a = term(p, 1); + let b = term(p, 2); + let bf = { + let n = p.intern("f"); + p.field(n, b) + }; + let s = p.seq(&[a, bf]); + p.prec(Precedence::Integer(5), s) + }) + .unwrap(); assert_eq!( - result.unwrap_err().to_string(), - "Rule `test` cannot be inlined because it contains a reference to itself", + prods(&grammar, 0, &interner), + vec![ProdView { + dyn_prec: 0, + steps: vec![ + StepView::new(Symbol::terminal(1)).prec(Precedence::Integer(5)), + StepView::new(Symbol::terminal(2)) + .prec(Precedence::Integer(5)) + .field("f"), + ] + }] ); } + + #[test] + fn test_flatten_grammar_with_recursive_inline_variable() { + let mut pool = RulePool::default(); + let nt0 = non_term(&mut pool, 0); + let nt1 = non_term(&mut pool, 1); + let nt2 = non_term(&mut pool, 2); + let root = pool.seq(&[nt0, nt1, nt2]); + let name = pool.intern("test"); + let pg = InputGrammar { + pool, + variables: vec![Variable { name, root }], + ..Default::default() + }; + let meta = ExtractedGrammarMeta { + kinds: vec![VariableType::Named], + inline: vec![Symbol::non_terminal(0)], + ..Default::default() + }; + assert_eq!( + run(pg, meta).unwrap_err(), + FlattenGrammarError::RecursiveInline("test".to_string()) + ); + } + + #[test] + fn test_flatten_grammar_with_unknown_reserved() { + // Unknown reserved context + let err = flatten_named(|p| { + let t1 = term(p, 1); + let ctx = p.intern("nope"); + p.reserved(t1, ctx) + }) + .unwrap_err(); + assert_eq!( + err, + FlattenGrammarError::NoReservedWordSet("nope".to_string()) + ); + } + + #[test] + fn test_flatten_grammar_with_empty_production() { + // Empty production in used variable (`a` refs `b`, `b` is empty) + let mut pool = RulePool::default(); + let a_root = non_term(&mut pool, 1); + let b_root = pool.blank(); + let (a, b) = (pool.intern("a"), pool.intern("b")); + let pg = InputGrammar { + pool, + variables: vec![ + Variable { + name: a, + root: a_root, + }, + Variable { + name: b, + root: b_root, + }, + ], + ..Default::default() + }; + let meta = ExtractedGrammarMeta { + kinds: vec![VariableType::Named, VariableType::Named], + ..Default::default() + }; + assert_eq!( + run(pg, meta).unwrap_err(), + FlattenGrammarError::EmptyString("b".to_string()) + ); + } + + #[test] + fn test_flatten_grammar_with_empty_choice() { + let (grammar, interner) = flatten_named(|p| { + let prefix = term(p, 1); + let empty = p.choice(&[]); + let tail_a = term(p, 2); + let tail_b = term(p, 3); + let tail = p.choice(&[tail_a, tail_b]); + let dead = p.seq(&[prefix, empty, tail]); + let live = term(p, 4); + p.choice(&[dead, live]) + }) + .unwrap(); + + assert_eq!( + prods(&grammar, 0, &interner), + vec![ProdView { + dyn_prec: 0, + steps: vec![StepView::new(Symbol::terminal(4))], + }] + ); + + let (grammar, interner) = flatten_named(|p| p.choice(&[])).unwrap(); + assert!(prods(&grammar, 0, &interner).is_empty()); + } + + 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, + }) + } + + #[derive(Debug, PartialEq)] + struct StepView { + symbol: Symbol, + prec: Precedence, + assoc: Option, + alias: Option, + field: Option, + reserved: u16, + } + + impl StepView { + fn new(symbol: Symbol) -> Self { + Self { + symbol, + prec: Precedence::None, + assoc: None, + alias: None, + field: None, + reserved: 0, + } + } + fn prec(mut self, prec: Precedence) -> Self { + self.prec = prec; + self + } + fn assoc(mut self, assoc: Option) -> Self { + self.assoc = assoc; + self + } + fn field(mut self, name: &str) -> Self { + self.field = Some(name.to_string()); + self + } + } + + #[derive(Debug, PartialEq)] + struct ProdView { + dyn_prec: i32, + steps: Vec, + } + + fn run( + g: InputGrammar, + meta: ExtractedGrammarMeta, + ) -> FlattenGrammarResult<(SyntaxGrammar, StrPool)> { + let mut st = FlattenState::default(); + let mut out = ProductionStore::default(); + flatten_grammar(&g, &meta, &mut st, &mut out)?; + Ok(assemble_syntax_grammar(g, meta, out)) + } + + fn flatten_named( + build: impl FnOnce(&mut RulePool) -> RuleId, + ) -> FlattenGrammarResult<(SyntaxGrammar, StrPool)> { + let mut pool = RulePool::default(); + let root = build(&mut pool); + let name = pool.intern("test"); + let pg = InputGrammar { + pool, + variables: vec![Variable { name, root }], + ..Default::default() + }; + let meta = ExtractedGrammarMeta { + kinds: vec![VariableType::Named], + ..Default::default() + }; + run(pg, meta) + } + + /// Unpack a variable's productions from the pooled output into comparable views. + fn prods(grammar: &SyntaxGrammar, var: usize, interner: &StrPool) -> Vec { + let (p_start, p_end) = grammar.var_prods[var]; + grammar.productions[p_start as usize..p_end as usize] + .iter() + .map(|p| ProdView { + dyn_prec: p.dynamic_precedence, + steps: grammar.steps[p.step_range()] + .iter() + .map(|&s| StepView { + symbol: s.symbol(), + prec: s.precedence(), + assoc: s.associativity(), + alias: s.alias(), + field: s.field().map(|f| interner.resolve(f).to_string()), + reserved: s.reserved, + }) + .collect(), + }) + .collect() + } } diff --git a/crates/generate/src/prepare_grammar/intern_symbols.rs b/crates/generate/src/prepare_grammar/intern_symbols.rs index f3887abdd..d13a26a6e 100644 --- a/crates/generate/src/prepare_grammar/intern_symbols.rs +++ b/crates/generate/src/prepare_grammar/intern_symbols.rs @@ -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 = Result; -#[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, -) -> InternSymbolsResult { - 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, + /// Per external token: its name (if a named symbol) and kind + pub external_tokens: Vec<(Option, VariableType)>, + pub supertypes: Vec, + pub conflicts: Vec>, + pub inline: Vec, + pub word: Option, +} - if variable_type_for_name(&grammar.variables[0].name) == VariableType::Hidden { +pub(super) fn intern_symbols( + grammar: &mut InputGrammar, + diagnostics: &mut Vec, +) -> InternSymbolsResult { + 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::, 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::>(); - 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::>>()?; + 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::>>() + }) + .collect::>>()?; - 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, + name_of_symbol: &FxHashMap, + diagnostics: &mut Vec, + stack: &mut Vec, +) -> InternSymbolsResult<()> { + stack.clear(); + stack.push(root); -impl Interner<'_> { - fn intern_rule( - &self, - rule: &Rule, - name: Option<&str>, - diagnostics: &mut Vec, - ) -> InternSymbolsResult { - 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 { - 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, - ) { - 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::>(); + 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::>(); + 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::>(); 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) -> 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) -> 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, + } + } } diff --git a/crates/generate/src/prepare_grammar/process_inlines.rs b/crates/generate/src/prepare_grammar/process_inlines.rs index e001d355c..b71e7b013 100644 --- a/crates/generate/src/prepare_grammar/process_inlines.rs +++ b/crates/generate/src/prepare_grammar/process_inlines.rs @@ -1,196 +1,154 @@ -use rustc_hash::FxHashMap; +use std::hash::{Hash as _, Hasher as _}; +use rustc_hash::{FxHashMap, FxHasher}; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::{ - grammars::{InlinedProductionMap, LexicalGrammar, Production, ProductionStep, SyntaxGrammar}, - rules::SymbolType, + grammars::{InlinedProductionMap, InputGrammar, Production, ProductionStep, ProductionStore}, + prepare_grammar::extract_tokens::ExtractedGrammarMeta, + rules::{Precedence, Symbol, SymbolType}, }; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct ProductionStepId { - // A `None` value here means that the production itself was produced via inlining, - // and is stored in the builder's `productions` vector, as opposed to being - // stored in one of the grammar's variables. - variable: Option, - production: usize, - step: usize, +struct InlineBuilder<'a> { + out: &'a mut ProductionStore, + first_inlined: u32, + inline: &'a [Symbol], + map: FxHashMap<(u32, u32), Vec>, + memo: FxHashMap>, } -struct InlinedProductionMapBuilder { - production_indices_by_step_id: FxHashMap>, - productions: Vec, +#[derive(Clone, Default)] +struct ScratchProd { + steps: Vec, + dynamic_precedence: i32, } -impl InlinedProductionMapBuilder { - fn build(mut self, grammar: &SyntaxGrammar) -> InlinedProductionMap { - let mut step_ids_to_process = Vec::new(); - for (variable_index, variable) in grammar.variables.iter().enumerate() { - for production_index in 0..variable.productions.len() { - step_ids_to_process.push(ProductionStepId { - variable: Some(variable_index), - production: production_index, - step: 0, - }); - while !step_ids_to_process.is_empty() { - let mut i = 0; - while i < step_ids_to_process.len() { - let step_id = step_ids_to_process[i]; - if let Some(step) = self.production_step_for_id(step_id, grammar) { - if grammar.variables_to_inline.contains(&step.symbol) { - let inlined_step_ids = self - .inline_production_at_step(step_id, grammar) - .iter() - .copied() - .map(|production_index| ProductionStepId { - variable: None, - production: production_index, - step: step_id.step, - }); - step_ids_to_process.splice(i..=i, inlined_step_ids); - } else { - step_ids_to_process[i] = ProductionStepId { - variable: step_id.variable, - production: step_id.production, - step: step_id.step + 1, - }; - i += 1; - } +impl InlineBuilder<'_> { + fn build(mut self) -> InlinedProductionMap { + let mut worklist = Vec::new(); + for prod_id in 0..self.first_inlined { + worklist.push((prod_id, 0u32)); + while !worklist.is_empty() { + let mut i = 0; + while i < worklist.len() { + let (prod_id, step_index) = worklist[i]; + if let Some(step) = self.production_step_for_id(prod_id, step_index) { + if self.inline.contains(&step.symbol()) { + let ids = self.inline_production_at_step(prod_id, step_index); + worklist.splice(i..=i, ids.into_iter().map(|id| (id, step_index))); } else { - step_ids_to_process.remove(i); + worklist[i].1 += 1; + i += 1; } + } else { + worklist.remove(i); } } } } - let productions = self.productions; - let production_indices_by_step_id = self.production_indices_by_step_id; - let production_map = production_indices_by_step_id - .into_iter() - .map(|(step_id, production_indices)| { - let production = core::ptr::from_ref::(step_id.variable.map_or_else( - || &productions[step_id.production], - |variable_index| { - &grammar.variables[variable_index].productions[step_id.production] - }, - )); - ((production, step_id.step as u32), production_indices) - }) - .collect(); - - InlinedProductionMap { - productions, - production_map, - } + InlinedProductionMap { map: self.map } } - fn inline_production_at_step<'a>( - &'a mut self, - step_id: ProductionStepId, - grammar: &'a SyntaxGrammar, - ) -> &'a [usize] { - // Build a list of productions produced by inlining rules. + /// Expand inlining at one step of one production, dedup, and store the results + fn inline_production_at_step(&mut self, prod_id: u32, step_index: u32) -> Vec { + if let Some(ids) = self.map.get(&(prod_id, step_index)) { + return ids.clone(); + } + + let si = step_index as usize; + let src = self.out.productions[prod_id as usize]; + let mut scratch = vec![ScratchProd { + steps: self.out.steps[src.step_range()].to_vec(), + dynamic_precedence: src.dynamic_precedence, + }]; let mut i = 0; - let step_index = step_id.step; - let mut productions_to_add = vec![self.production_for_id(step_id, grammar).clone()]; - while i < productions_to_add.len() { - if let Some(step) = productions_to_add[i].steps.get(step_index) { - let symbol = step.symbol; - if grammar.variables_to_inline.contains(&symbol) { - // Remove the production from the vector, replacing it with a placeholder. - let production = productions_to_add - .splice(i..=i, std::iter::once(&Production::default()).cloned()) - .next() - .unwrap(); - - // Replace the placeholder with the inlined productions. - productions_to_add.splice( - i..=i, - grammar.variables[symbol.index].productions.iter().map(|p| { - let mut production = production.clone(); - let removed_step = production - .steps - .splice(step_index..=step_index, p.steps.iter().cloned()) - .next() - .unwrap(); - let inserted_steps = - &mut production.steps[step_index..(step_index + p.steps.len())]; - if let Some(alias) = removed_step.alias { - for inserted_step in inserted_steps.iter_mut() { - inserted_step.alias = Some(alias.clone()); - } - } - if let Some(field_name) = removed_step.field_name { - for inserted_step in inserted_steps.iter_mut() { - inserted_step.field_name = Some(field_name.clone()); - } - } - if let Some(last_inserted_step) = inserted_steps.last_mut() { - if last_inserted_step.precedence.is_none() { - last_inserted_step.precedence = removed_step.precedence; - } - if last_inserted_step.associativity.is_none() { - last_inserted_step.associativity = removed_step.associativity; - } - } - if p.dynamic_precedence.abs() > production.dynamic_precedence.abs() { - production.dynamic_precedence = p.dynamic_precedence; - } - production - }), - ); - + while i < scratch.len() { + let symbol = match scratch[i].steps.get(si) { + Some(s) if self.inline.contains(&s.symbol()) => s.symbol(), + _ => { + i += 1; continue; } - } - i += 1; + }; + + let removed_prod = std::mem::take(&mut scratch[i]); + let removed_step = removed_prod.steps[si]; + let (v_start, v_end) = self.out.var_prods[symbol.index]; + let replacements = (v_start..v_end) + .map(|p_idx| { + let p = self.out.productions[p_idx as usize]; + let mut production = removed_prod.clone(); + production + .steps + .splice(si..=si, self.out.steps[p.step_range()].iter().copied()); + let inserted = &mut production.steps[si..si + p.steps_len as usize]; + if let Some(removed_alias) = removed_step.alias() { + for step in inserted.iter_mut() { + step.set_alias(Some(removed_alias)); + } + } + if let Some(removed_field) = removed_step.field() { + for step in inserted.iter_mut() { + step.set_field(Some(removed_field)); + } + } + if let Some(last) = inserted.last_mut() { + if last.precedence() == Precedence::None { + last.set_precedence(removed_step.precedence()); + } + if last.associativity().is_none() { + last.set_associativity(removed_step.associativity()); + } + } + if p.dynamic_precedence.abs() > production.dynamic_precedence.abs() { + production.dynamic_precedence = p.dynamic_precedence; + } + production + }) + .collect::>(); + scratch.splice(i..=i, replacements); } - // Store all the computed productions. - let result = productions_to_add - .into_iter() - .map(|production| { - self.productions - .iter() - .position(|p| *p == production) - .unwrap_or_else(|| { - self.productions.push(production); - self.productions.len() - 1 - }) - }) - .collect(); + let mut result = Vec::with_capacity(scratch.len()); + for sp in scratch { + let mut hasher = FxHasher::default(); + sp.dynamic_precedence.hash(&mut hasher); + sp.steps.hash(&mut hasher); + let candidates = self.memo.entry(hasher.finish()).or_default(); + let existing = candidates.iter().copied().find(|&id| { + let p = self.out.productions[id as usize]; + p.dynamic_precedence == sp.dynamic_precedence + && self.out.steps[p.step_range()] == sp.steps + }); + result.push(existing.unwrap_or_else(|| { + let steps_start = self.out.steps.len() as u32; + self.out.steps.extend_from_slice(&sp.steps); + self.out.productions.push(Production { + steps_start, + steps_len: sp.steps.len() as u32, + dynamic_precedence: sp.dynamic_precedence, + }); + let id = (self.out.productions.len() - 1) as u32; + candidates.push(id); + id + })); + } - // Cache these productions based on the original production step. - self.production_indices_by_step_id - .entry(step_id) - .or_insert(result) + self.map.insert((prod_id, step_index), result.clone()); + result } - fn production_for_id<'a>( - &'a self, - id: ProductionStepId, - grammar: &'a SyntaxGrammar, - ) -> &'a Production { - id.variable.map_or_else( - || &self.productions[id.production], - |variable_index| &grammar.variables[variable_index].productions[id.production], - ) - } - - fn production_step_for_id<'a>( - &'a self, - id: ProductionStepId, - grammar: &'a SyntaxGrammar, - ) -> Option<&'a ProductionStep> { - self.production_for_id(id, grammar).steps.get(id.step) + fn production_step_for_id(&self, prod_id: u32, step: u32) -> Option { + let p = self.out.productions[prod_id as usize]; + (step < p.steps_len).then(|| self.out.steps[(p.steps_start + step) as usize]) } } pub type ProcessInlinesResult = Result; -#[derive(Debug, Error, Serialize, Deserialize)] +#[derive(Debug, Error, Serialize, Deserialize, PartialEq, Eq)] pub enum ProcessInlinesError { #[error("External token `{0}` cannot be inlined")] ExternalToken(String), @@ -201,358 +159,408 @@ pub enum ProcessInlinesError { } pub(super) fn process_inlines( - grammar: &SyntaxGrammar, - lexical_grammar: &LexicalGrammar, + g: &InputGrammar, + meta: &ExtractedGrammarMeta, + out: &mut ProductionStore, ) -> ProcessInlinesResult { - for symbol in &grammar.variables_to_inline { + if meta.inline.is_empty() { + return Ok(InlinedProductionMap::default()); + } + for symbol in &meta.inline { match symbol.kind { - SymbolType::External => { - Err(ProcessInlinesError::ExternalToken( - grammar.external_tokens[symbol.index].name.clone(), - ))?; - } - SymbolType::Terminal => { - Err(ProcessInlinesError::Token( - lexical_grammar.variables[symbol.index].name.clone(), - ))?; - } - SymbolType::NonTerminal if symbol.index == 0 => { - Err(ProcessInlinesError::FirstRule( - grammar.variables[symbol.index].name.clone(), - ))?; - } + SymbolType::External => Err(ProcessInlinesError::ExternalToken( + g.pool + .resolve(meta.external_tokens[symbol.index].name) + .to_string(), + ))?, + SymbolType::Terminal => Err(ProcessInlinesError::Token( + g.pool + .resolve(meta.lexical_variables[symbol.index].name) + .to_string(), + ))?, + SymbolType::NonTerminal if symbol.index == 0 => Err(ProcessInlinesError::FirstRule( + g.pool.resolve(g.variables[0].name).to_string(), + ))?, _ => {} } } - Ok(InlinedProductionMapBuilder { - productions: Vec::new(), - production_indices_by_step_id: FxHashMap::default(), + Ok(InlineBuilder { + first_inlined: out.productions.len() as u32, + out, + inline: &meta.inline, + map: FxHashMap::default(), + memo: FxHashMap::default(), } - .build(grammar)) + .build()) } #[cfg(test)] mod tests { use super::*; use crate::{ - grammars::{LexicalVariable, SyntaxVariable, VariableType}, - rules::{Associativity, Precedence, Symbol}, + grammars::VariableType, + prepare_grammar::extract_tokens::LexicalToken, + rules::{Alias, Associativity, RulePool, Symbol}, }; #[test] fn test_basic_inlining() { - let grammar = SyntaxGrammar { - variables_to_inline: vec![Symbol::non_terminal(1)], - variables: vec![ - SyntaxVariable { - name: "non-terminal-0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::non_terminal(1)), // inlined - ProductionStep::new(Symbol::terminal(11)), - ], - }], - }, - SyntaxVariable { - name: "non-terminal-1".to_string(), - kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(12)), - ProductionStep::new(Symbol::terminal(13)), - ], - }, - Production { - dynamic_precedence: -2, - steps: vec![ProductionStep::new(Symbol::terminal(14))], - }, - ], - }, + // var0: [t10, nt1, t11] (nt1 is inlined) + // var1: [t12, t13] | [t14] + let mut out = ProductionStore::default(); + add_variable( + &mut out, + &[( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::non_terminal(1)), + plain(Symbol::terminal(11)), + ], + 0, + )], + ); + add_variable( + &mut out, + &[ + ( + vec![plain(Symbol::terminal(12)), plain(Symbol::terminal(13))], + 0, + ), + (vec![plain(Symbol::terminal(14))], -2), ], - ..Default::default() - }; - - let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap(); - - // Nothing to inline at step 0. - assert!( - inline_map - .inlined_productions(&grammar.variables[0].productions[0], 0) - .is_none() ); + let g = InputGrammar::default(); + let meta = ExtractedGrammarMeta { + inline: vec![Symbol::non_terminal(1)], + ..Default::default() + }; + let map = process_inlines(&g, &meta, &mut out).unwrap(); + let prod0 = out.var_prods[0].0; + + // Nothing to inline at step 0. + assert!(inlined(&out, &map, prod0, 0).is_none()); + // Inlining variable 1 yields two productions. + let (_, prods) = inlined(&out, &map, prod0, 1).unwrap(); assert_eq!( - inline_map - .inlined_productions(&grammar.variables[0].productions[0], 1) - .unwrap() - .cloned() - .collect::>(), + prods, vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::terminal(12)), - ProductionStep::new(Symbol::terminal(13)), - ProductionStep::new(Symbol::terminal(11)), + ( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::terminal(12)), + plain(Symbol::terminal(13)), + plain(Symbol::terminal(11)), ], - }, - Production { - dynamic_precedence: -2, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::terminal(14)), - ProductionStep::new(Symbol::terminal(11)), + 0 + ), + ( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::terminal(14)), + plain(Symbol::terminal(11)), ], - }, + -2 + ), ] ); } #[test] fn test_nested_inlining() { - let grammar = SyntaxGrammar { - variables: vec![ - SyntaxVariable { - name: "non-terminal-0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::non_terminal(1)), // inlined - ProductionStep::new(Symbol::terminal(11)), - ProductionStep::new(Symbol::non_terminal(2)), // inlined - ProductionStep::new(Symbol::terminal(12)), - ], - }], - }, - SyntaxVariable { - name: "non-terminal-1".to_string(), - kind: VariableType::Named, - productions: vec![ - Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(13))], - }, - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::non_terminal(3)), // inlined - ProductionStep::new(Symbol::terminal(14)), - ], - }, - ], - }, - SyntaxVariable { - name: "non-terminal-2".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(15))], - }], - }, - SyntaxVariable { - name: "non-terminal-3".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(16))], - }], - }, + // var0: [t10, nt1, t11, nt2, t12] (nt1, nt2 inlined) + // var1: [t13] | [nt3, t14] (nt3 inlined) + // var2: [t15] + // var3: [t16] + let mut out = ProductionStore::default(); + add_variable( + &mut out, + &[( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::non_terminal(1)), + plain(Symbol::terminal(11)), + plain(Symbol::non_terminal(2)), + plain(Symbol::terminal(12)), + ], + 0, + )], + ); + add_variable( + &mut out, + &[ + (vec![plain(Symbol::terminal(13))], 0), + ( + vec![plain(Symbol::non_terminal(3)), plain(Symbol::terminal(14))], + 0, + ), ], - variables_to_inline: vec![ + ); + add_variable(&mut out, &[(vec![plain(Symbol::terminal(15))], 0)]); + add_variable(&mut out, &[(vec![plain(Symbol::terminal(16))], 0)]); + + let g = InputGrammar::default(); + let meta = ExtractedGrammarMeta { + inline: vec![ Symbol::non_terminal(1), Symbol::non_terminal(2), Symbol::non_terminal(3), ], ..Default::default() }; + let map = process_inlines(&g, &meta, &mut out).unwrap(); + let prod0 = out.var_prods[0].0; - let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap(); - - let productions = inline_map - .inlined_productions(&grammar.variables[0].productions[0], 1) - .unwrap() - .collect::>(); - + let (ids, prods) = inlined(&out, &map, prod0, 1).unwrap(); assert_eq!( - productions.iter().copied().cloned().collect::>(), + prods, vec![ - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::terminal(13)), - ProductionStep::new(Symbol::terminal(11)), - ProductionStep::new(Symbol::non_terminal(2)), - ProductionStep::new(Symbol::terminal(12)), + ( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::terminal(13)), + plain(Symbol::terminal(11)), + plain(Symbol::non_terminal(2)), + plain(Symbol::terminal(12)), ], - }, - Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::terminal(16)), - ProductionStep::new(Symbol::terminal(14)), - ProductionStep::new(Symbol::terminal(11)), - ProductionStep::new(Symbol::non_terminal(2)), - ProductionStep::new(Symbol::terminal(12)), + 0, + ), + ( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::terminal(16)), + plain(Symbol::terminal(14)), + plain(Symbol::terminal(11)), + plain(Symbol::non_terminal(2)), + plain(Symbol::terminal(12)), ], - }, + 0 + ), ] ); + // nt2, now at step 3 + let (_, prods) = inlined(&out, &map, ids[0], 3).unwrap(); assert_eq!( - inline_map - .inlined_productions(productions[0], 3) - .unwrap() - .cloned() - .collect::>(), - vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::terminal(13)), - ProductionStep::new(Symbol::terminal(11)), - ProductionStep::new(Symbol::terminal(15)), - ProductionStep::new(Symbol::terminal(12)), + prods, + vec![( + vec![ + plain(Symbol::terminal(10)), + plain(Symbol::terminal(13)), + plain(Symbol::terminal(11)), + plain(Symbol::terminal(15)), + plain(Symbol::terminal(12)), ], - },] + 0, + )] ); } #[test] fn test_inlining_with_precedence_and_alias() { - let grammar = SyntaxGrammar { - variables_to_inline: vec![Symbol::non_terminal(1), Symbol::non_terminal(2)], - variables: vec![ - SyntaxVariable { - name: "non-terminal-0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - // inlined - ProductionStep::new(Symbol::non_terminal(1)) - .with_prec(Precedence::Integer(1), Some(Associativity::Left)), - ProductionStep::new(Symbol::terminal(10)), - // inlined - ProductionStep::new(Symbol::non_terminal(2)) - .with_alias("outer_alias", true), - ], - }], - }, - SyntaxVariable { - name: "non-terminal-1".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(11)) - .with_prec(Precedence::Integer(2), None) - .with_alias("inner_alias", true), - ProductionStep::new(Symbol::terminal(12)), - ], - }], - }, - SyntaxVariable { - name: "non-terminal-2".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(13))], - }], - }, - ], + // var0: [ nt1{prec 1, left}, t10, nt2{alias outer} ] (nt1, nt2 inlined) + // var1: [ r11{prec 2, alias inner}, t12 ] + // var2: [ t13 ] + let mut pool = RulePool::default(); + let mut out = ProductionStore::default(); + add_variable( + &mut out, + &[( + vec![ + decorated( + &mut pool, + Symbol::non_terminal(1), + Precedence::Integer(1), + Some(Associativity::Left), + None, + ), + plain(Symbol::terminal(10)), + decorated( + &mut pool, + Symbol::non_terminal(2), + Precedence::None, + None, + Some("outer_alias"), + ), + ], + 0, + )], + ); + add_variable( + &mut out, + &[( + vec![ + decorated( + &mut pool, + Symbol::terminal(11), + Precedence::Integer(2), + None, + Some("inner_alias"), + ), + plain(Symbol::terminal(12)), + ], + 0, + )], + ); + add_variable(&mut out, &[(vec![plain(Symbol::terminal(13))], 0)]); + + let g = InputGrammar::default(); + let meta = ExtractedGrammarMeta { + inline: vec![Symbol::non_terminal(1), Symbol::non_terminal(2)], ..Default::default() }; + let map = process_inlines(&g, &meta, &mut out).unwrap(); + let prod0 = out.var_prods[0].0; - let inline_map = process_inlines(&grammar, &LexicalGrammar::default()).unwrap(); - - let productions = inline_map - .inlined_productions(&grammar.variables[0].productions[0], 0) - .unwrap() - .collect::>(); - + let (ids, prods) = inlined(&out, &map, prod0, 0).unwrap(); assert_eq!( - productions.iter().copied().cloned().collect::>(), - vec![Production { - dynamic_precedence: 0, - steps: vec![ - // The first step in the inlined production retains its precedence - // and alias. - ProductionStep::new(Symbol::terminal(11)) - .with_prec(Precedence::Integer(2), None) - .with_alias("inner_alias", true), - // The final step of the inlined production inherits the precedence of - // the inlined step. - ProductionStep::new(Symbol::terminal(12)) - .with_prec(Precedence::Integer(1), Some(Associativity::Left)), - ProductionStep::new(Symbol::terminal(10)), - ProductionStep::new(Symbol::non_terminal(2)).with_alias("outer_alias", true), - ] - }], + prods, + vec![( + vec![ + // The first inlined step keeps its own precedence and alias. + decorated( + &mut pool, + Symbol::terminal(11), + Precedence::Integer(2), + None, + Some("inner_alias") + ), + // The last inlined step inherits the inlined step's precedence. + decorated( + &mut pool, + Symbol::terminal(12), + Precedence::Integer(1), + Some(Associativity::Left), + None, + ), + plain(Symbol::terminal(10)), + decorated( + &mut pool, + Symbol::non_terminal(2), + Precedence::None, + None, + Some("outer_alias"), + ), + ], + 0, + )] ); + let (_, prods) = inlined(&out, &map, ids[0], 3).unwrap(); assert_eq!( - inline_map - .inlined_productions(productions[0], 3) - .unwrap() - .cloned() - .collect::>(), - vec![Production { - dynamic_precedence: 0, - steps: vec![ - ProductionStep::new(Symbol::terminal(11)) - .with_prec(Precedence::Integer(2), None) - .with_alias("inner_alias", true), - ProductionStep::new(Symbol::terminal(12)) - .with_prec(Precedence::Integer(1), Some(Associativity::Left)), - ProductionStep::new(Symbol::terminal(10)), - // All steps of the inlined production inherit their alias from the - // inlined step. - ProductionStep::new(Symbol::terminal(13)).with_alias("outer_alias", true), - ] - }], + prods, + vec![( + vec![ + decorated( + &mut pool, + Symbol::terminal(11), + Precedence::Integer(2), + None, + Some("inner_alias"), + ), + decorated( + &mut pool, + Symbol::terminal(12), + Precedence::Integer(1), + Some(Associativity::Left), + None, + ), + plain(Symbol::terminal(10)), + // Every inlined step inherits the inlined step's alias + decorated( + &mut pool, + Symbol::terminal(13), + Precedence::None, + None, + Some("outer_alias"), + ), + ], + 0, + )], ); } #[test] fn test_error_when_inlining_tokens() { - let lexical_grammar = LexicalGrammar { - variables: vec![LexicalVariable { - name: "something".to_string(), + let mut pool = RulePool::default(); + let name = pool.intern("something"); + let root = pool.blank(); + let g = InputGrammar { + pool, + ..Default::default() + }; + let meta = ExtractedGrammarMeta { + inline: vec![Symbol::terminal(0)], + lexical_variables: vec![LexicalToken { + name, kind: VariableType::Named, - implicit_precedence: 0, - start_state: 0, + root, }], ..Default::default() }; + let mut out = ProductionStore::default(); - let grammar = SyntaxGrammar { - variables_to_inline: vec![Symbol::terminal(0)], - variables: vec![SyntaxVariable { - name: "non-terminal-0".to_string(), - kind: VariableType::Named, - productions: vec![Production { - dynamic_precedence: 0, - steps: vec![ProductionStep::new(Symbol::terminal(0))], - }], - }], - ..Default::default() - }; + let result = process_inlines(&g, &meta, &mut out); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err, ProcessInlinesError::Token("something".to_string())); + } - let result = process_inlines(&grammar, &lexical_grammar); - assert!(result.is_err(), "expected an error, but got none"); - let err = result.err().unwrap(); - assert_eq!(err.to_string(), "Token `something` cannot be inlined",); + /// Append one variable's productions to `out` and record its production id range. + fn add_variable(out: &mut ProductionStore, prods: &[(Vec, i32)]) { + let start = out.productions.len() as u32; + for (steps, dynamic_prc) in prods { + 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: *dynamic_prc, + }); + } + out.var_prods.push((start, out.productions.len() as u32)); + } + + /// One inlined replacement production: its owned steps and dynamic precedence. + type InlinedProduction = (Vec, i32); + + /// Inlining results recorded at `(prod_id, step`: the replacement ids and each + /// one's steps and dynamic precedence. + fn inlined( + out: &ProductionStore, + map: &InlinedProductionMap, + prod_id: u32, + step: u32, + ) -> Option<(Vec, Vec)> { + map.map.get(&(prod_id, step)).map(|ids| { + let prods = ids + .iter() + .map(|&id| { + let p = out.productions[id as usize]; + (out.steps[p.step_range()].to_vec(), p.dynamic_precedence) + }) + .collect::>(); + (ids.clone(), prods) + }) + } + + fn plain(symbol: Symbol) -> ProductionStep { + ProductionStep::pack(symbol, Precedence::None, None, None, None, 0) + } + + fn decorated( + pool: &mut RulePool, + symbol: Symbol, + prec: Precedence, + assoc: Option, + alias: Option<&str>, + ) -> ProductionStep { + let alias = alias.map(|name| Alias { + value: pool.intern(name), + is_named: true, + }); + ProductionStep::pack(symbol, prec, assoc, alias, None, 0) } } diff --git a/crates/generate/src/render.rs b/crates/generate/src/render.rs index bc1cbd017..0c769147c 100644 --- a/crates/generate/src/render.rs +++ b/crates/generate/src/render.rs @@ -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, reserved_word_sets: Vec, reserved_word_set_ids_by_parse_state: Vec, - field_names: Vec, + field_names: Vec, supertype_symbol_map: BTreeMap>, supertype_map: BTreeMap>, abi_version: usize, metadata: Option, + 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) { @@ -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 { + fn symbols_for_alias(&self, alias: Alias) -> Vec { 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>, @@ -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() diff --git a/crates/generate/src/rules.rs b/crates/generate/src/rules.rs index 42ab9443c..c6aced596 100644 --- a/crates/generate/src/rules.rs +++ b/crates/generate/src/rules.rs @@ -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; - -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct MetadataParams { - pub precedence: Precedence, - pub dynamic_precedence: i32, - pub associativity: Option, - pub is_token: bool, - pub is_main_token: bool, - pub alias: Option, - pub field_name: Option, -} - -#[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), - Metadata { - params: MetadataParams, - rule: Box, - }, - Repeat(Box), - Seq(Vec), - Reserved { - rule: Box, - 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 { - 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 { - 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::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, + pub dynamic_precedence: i32, + pub alias: Option, + pub field: Option, + 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::() <= 12); + impl From 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 { + 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, + children: Vec, + params: Vec, + 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 = Vec::with_capacity(ids.len()); + let mut stack: Vec = 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) { + 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 { + 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 { + 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 for TokenSet { } } -fn add_metadata(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) { - 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; diff --git a/crates/generate/src/strpool.rs b/crates/generate/src/strpool.rs new file mode 100644 index 000000000..1ead9064e --- /dev/null +++ b/crates/generate/src/strpool.rs @@ -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>, + str_ids: FxHashMap, 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 = 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()] + } +} diff --git a/crates/generate/src/tables.rs b/crates/generate/src/tables.rs index a7e02e8e9..5fdfc8dfa 100644 --- a/crates/generate/src/tables.rs +++ b/crates/generate/src/tables.rs @@ -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>, - pub field_map: BTreeMap>, + pub field_map: BTreeMap>, } #[derive(Debug, Default, PartialEq, Eq)] diff --git a/test/fixtures/test_grammars/indirect_recursion_in_transitions/expected_error.txt b/test/fixtures/test_grammars/indirect_recursion_in_transitions/expected_error.txt index 4f244a6c6..f1d3bdb03 100644 --- a/test/fixtures/test_grammars/indirect_recursion_in_transitions/expected_error.txt +++ b/test/fixtures/test_grammars/indirect_recursion_in_transitions/expected_error.txt @@ -1 +1 @@ -Grammar contains an indirectly recursive rule: type_expression -> _expression -> identifier_expression -> type_expression \ No newline at end of file +Grammar contains an indirectly recursive rule: type_expression -> _expression -> type_expression \ No newline at end of file