mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
Compare commits
23 commits
master
...
generate_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a8a17cf03 | ||
|
|
72718ca566 | ||
|
|
fd90c6e974 | ||
|
|
8982547112 | ||
|
|
7b3b9da95b | ||
|
|
8f1ee0f13f | ||
|
|
cf7d9e4d68 | ||
|
|
3f353a21f9 | ||
|
|
f50c7d9e64 | ||
|
|
b9f40095d7 | ||
|
|
bbd57f95d4 | ||
|
|
45ad716e86 | ||
|
|
4fd554f6af | ||
|
|
58d153efdb | ||
|
|
e5b6b40bb9 | ||
|
|
d61d9e3715 | ||
|
|
58c7177277 | ||
|
|
62eb93fb0f | ||
|
|
afcc08c75e | ||
|
|
e661efecee | ||
|
|
f5df7c1a6f | ||
|
|
27035f6608 | ||
|
|
0b269f537b |
|
|
@ -234,6 +234,7 @@ impl BitVec {
|
|||
}
|
||||
|
||||
/// Word-level OR: self |= other. Returns true if any new bits were set.
|
||||
#[inline]
|
||||
pub fn insert_all(&mut self, other: &Self) -> bool {
|
||||
let other_words = other.words_in_use();
|
||||
if other_words == 0 {
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ mod item_set_builder;
|
|||
mod minimize_parse_table;
|
||||
mod token_conflicts;
|
||||
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
pub use build_lex_table::LARGE_CHARACTER_RANGE_COUNT;
|
||||
use build_parse_table::BuildTableResult;
|
||||
pub use build_parse_table::ParseTableBuilderError;
|
||||
use log::{debug, info};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use self::{
|
||||
build_lex_table::build_lex_table,
|
||||
|
|
@ -91,7 +92,7 @@ pub fn build_tables(
|
|||
&token_conflict_map,
|
||||
);
|
||||
populate_external_lex_states(&mut parse_table, syntax_grammar);
|
||||
mark_fragile_tokens(&mut parse_table, lexical_grammar, &token_conflict_map);
|
||||
mark_fragile_tokens(&mut parse_table, &token_conflict_map);
|
||||
|
||||
if let Some(report_symbol_name) = report_symbol_name {
|
||||
report_state_info(
|
||||
|
|
@ -284,7 +285,7 @@ fn populate_used_symbols(
|
|||
}
|
||||
|
||||
fn populate_external_lex_states(parse_table: &mut ParseTable, syntax_grammar: &SyntaxGrammar) {
|
||||
let mut external_tokens_by_corresponding_internal_token = HashMap::new();
|
||||
let mut external_tokens_by_corresponding_internal_token = FxHashMap::default();
|
||||
for (i, external_token) in syntax_grammar.external_tokens.iter().enumerate() {
|
||||
if let Some(symbol) = external_token.corresponding_internal_token {
|
||||
external_tokens_by_corresponding_internal_token.insert(symbol.index, i);
|
||||
|
|
@ -390,13 +391,12 @@ fn identify_keywords(
|
|||
// If the word token was already valid in every state containing
|
||||
// this keyword candidate, then substituting the word token won't
|
||||
// introduce any new lexical conflicts.
|
||||
if coincident_token_index
|
||||
.states_with(*token, Symbol::terminal(other_index))
|
||||
.iter()
|
||||
.all(|state_id| {
|
||||
parse_table.states[*state_id]
|
||||
.terminal_entries
|
||||
.contains_key(&word_token)
|
||||
let other = Symbol::terminal(other_index);
|
||||
if !coincident_token_index.contains(*token, other)
|
||||
|| parse_table.states.iter().all(|state| {
|
||||
!state.terminal_entries.contains_key(token)
|
||||
|| !state.terminal_entries.contains_key(&other)
|
||||
|| state.terminal_entries.contains_key(&word_token)
|
||||
})
|
||||
{
|
||||
continue;
|
||||
|
|
@ -425,25 +425,19 @@ fn identify_keywords(
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn mark_fragile_tokens(
|
||||
parse_table: &mut ParseTable,
|
||||
lexical_grammar: &LexicalGrammar,
|
||||
token_conflict_map: &TokenConflictMap,
|
||||
) {
|
||||
let n = lexical_grammar.variables.len();
|
||||
let mut valid_tokens_mask = Vec::with_capacity(n);
|
||||
fn mark_fragile_tokens(parse_table: &mut ParseTable, token_conflict_map: &TokenConflictMap) {
|
||||
let mut valid_terminal_indices = Vec::new();
|
||||
for state in &mut parse_table.states {
|
||||
valid_tokens_mask.clear();
|
||||
valid_tokens_mask.resize(n, false);
|
||||
valid_terminal_indices.clear();
|
||||
for token in state.terminal_entries.keys() {
|
||||
if token.is_terminal() {
|
||||
valid_tokens_mask[token.index] = true;
|
||||
valid_terminal_indices.push(token.index);
|
||||
}
|
||||
}
|
||||
for (token, entry) in &mut state.terminal_entries {
|
||||
if token.is_terminal() {
|
||||
for (i, is_valid) in valid_tokens_mask.iter().enumerate() {
|
||||
if *is_valid && token_conflict_map.does_overlap(i, token.index) {
|
||||
for &i in &valid_terminal_indices {
|
||||
if token_conflict_map.does_overlap(i, token.index) {
|
||||
entry.reusable = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use std::{
|
||||
collections::{HashMap, VecDeque, hash_map::Entry},
|
||||
collections::{VecDeque, hash_map::Entry},
|
||||
mem,
|
||||
};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::{coincident_tokens::CoincidentTokenIndex, token_conflicts::TokenConflictMap};
|
||||
|
|
@ -139,7 +141,7 @@ struct LexTableBuilder<'a> {
|
|||
cursor: NfaCursor<'a>,
|
||||
table: LexTable,
|
||||
state_queue: VecDeque<QueueEntry>,
|
||||
state_ids_by_nfa_state_set: HashMap<(Vec<u32>, bool), usize>,
|
||||
state_ids_by_nfa_state_set: FxHashMap<(Vec<u32>, bool), usize>,
|
||||
}
|
||||
|
||||
impl<'a> LexTableBuilder<'a> {
|
||||
|
|
@ -149,7 +151,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
cursor: NfaCursor::new(&lexical_grammar.nfa, vec![]),
|
||||
table: LexTable::default(),
|
||||
state_queue: VecDeque::new(),
|
||||
state_ids_by_nfa_state_set: HashMap::new(),
|
||||
state_ids_by_nfa_state_set: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,9 +199,13 @@ impl<'a> LexTableBuilder<'a> {
|
|||
|
||||
fn add_state(&mut self, nfa_states: Vec<u32>, eof_valid: bool) -> (usize, bool) {
|
||||
self.cursor.reset(nfa_states);
|
||||
// Move cursor.state_ids out as the key instead of cloning it.
|
||||
// The cursor is always rebuilt via reset() before its state is needed
|
||||
// again, so leaving it empty here is safe.
|
||||
let key_states = mem::take(&mut self.cursor.state_ids);
|
||||
match self
|
||||
.state_ids_by_nfa_state_set
|
||||
.entry((self.cursor.state_ids.clone(), eof_valid))
|
||||
.entry((key_states, eof_valid))
|
||||
{
|
||||
Entry::Occupied(o) => (*o.get(), false),
|
||||
Entry::Vacant(v) => {
|
||||
|
|
@ -234,8 +240,7 @@ impl<'a> LexTableBuilder<'a> {
|
|||
completion = Some((id, prec));
|
||||
}
|
||||
|
||||
let transitions = self.cursor.transitions();
|
||||
let has_sep = self.cursor.transition_chars().any(|(_, sep)| sep);
|
||||
let (transitions, has_sep) = self.cursor.transitions_and_any_sep();
|
||||
|
||||
// If EOF is a valid lookahead token, add a transition predicated on the null
|
||||
// character that leads to the empty set of NFA states.
|
||||
|
|
@ -285,20 +290,27 @@ fn check_token_conflicts(
|
|||
token_conflict_map: &TokenConflictMap,
|
||||
coincident_token_index: &CoincidentTokenIndex,
|
||||
) -> bool {
|
||||
let symbol = Symbol::terminal(i);
|
||||
for existing_token in set_without_terminal.terminals() {
|
||||
if token_conflict_map.does_conflict(i, existing_token.index)
|
||||
|| token_conflict_map.does_match_prefix(i, existing_token.index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if !coincident_token_index.contains(symbol, existing_token)
|
||||
&& (token_conflict_map.does_overlap(existing_token.index, i)
|
||||
|| token_conflict_map.does_overlap(i, existing_token.index))
|
||||
{
|
||||
let wpr = token_conflict_map.row_words;
|
||||
let row_start = i * wpr;
|
||||
let set_bits = set_without_terminal.terminal_bits_words();
|
||||
|
||||
// Does terminal i conflict with or match-prefix any terminal in the set?
|
||||
let conflict_row = &token_conflict_map.conflict_or_prefix_bits[row_start..row_start + wpr];
|
||||
for (&c, &s) in conflict_row.iter().zip(set_bits) {
|
||||
if c & s != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Does terminal i overlap (in either direction) with any non-coincident terminal in the set?
|
||||
let overlap_row = &token_conflict_map.overlap_either_bits[row_start..row_start + wpr];
|
||||
let coincident_row = &coincident_token_index.row_bits[row_start..row_start + wpr];
|
||||
for ((&o, &s), &c) in overlap_row.iter().zip(set_bits).zip(coincident_row) {
|
||||
if o & s & !c != 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +357,7 @@ fn merge_token_set(
|
|||
fn minimize_lex_table(table: &mut LexTable, parse_table: &mut ParseTable) {
|
||||
// Initially group the states by their accept action and their
|
||||
// valid lookahead characters.
|
||||
let mut state_ids_by_signature = HashMap::new();
|
||||
let mut state_ids_by_signature = FxHashMap::default();
|
||||
for (i, state) in table.states.iter().enumerate() {
|
||||
let signature = (
|
||||
i == 0,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
|
||||
collections::{BTreeMap, BTreeSet, VecDeque},
|
||||
hash::BuildHasherDefault,
|
||||
};
|
||||
|
||||
use indexmap::{IndexMap, map::Entry};
|
||||
use log::warn;
|
||||
use rustc_hash::FxHasher;
|
||||
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
@ -56,12 +56,12 @@ struct ParseTableBuilder<'a> {
|
|||
syntax_grammar: &'a SyntaxGrammar,
|
||||
lexical_grammar: &'a LexicalGrammar,
|
||||
variable_info: &'a [VariableInfo],
|
||||
core_ids_by_core: HashMap<ParseItemSetCore<'a>, usize>,
|
||||
core_ids_by_core: FxHashMap<ParseItemSetCore<'a>, usize>,
|
||||
state_ids_by_item_set: IndexMap<ParseItemSet<'a>, ParseStateId, BuildHasherDefault<FxHasher>>,
|
||||
parse_state_info_by_id: Vec<ParseStateInfo<'a>>,
|
||||
parse_state_queue: VecDeque<ParseStateQueueEntry>,
|
||||
non_terminal_extra_states: Vec<(Symbol, usize)>,
|
||||
actual_conflicts: HashSet<Vec<Symbol>>,
|
||||
actual_conflicts: FxHashSet<Vec<Symbol>>,
|
||||
parse_table: ParseTable,
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
variable_info,
|
||||
non_terminal_extra_states: Vec::new(),
|
||||
state_ids_by_item_set: IndexMap::default(),
|
||||
core_ids_by_core: HashMap::new(),
|
||||
core_ids_by_core: FxHashMap::default(),
|
||||
parse_state_info_by_id: Vec::new(),
|
||||
parse_state_queue: VecDeque::new(),
|
||||
actual_conflicts: syntax_grammar.expected_conflicts.iter().cloned().collect(),
|
||||
|
|
@ -417,7 +417,7 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
let mut terminal_successors = BTreeMap::new();
|
||||
let mut non_terminal_successors = BTreeMap::new();
|
||||
let mut lookaheads_with_conflicts = TokenSet::new();
|
||||
let mut reduction_infos = HashMap::<Symbol, ReductionInfo>::new();
|
||||
let mut reduction_infos = FxHashMap::<Symbol, ReductionInfo>::default();
|
||||
|
||||
// Each item in the item set contributes to either or a Shift action or a Reduce
|
||||
// action in this state.
|
||||
|
|
@ -481,9 +481,9 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
} else {
|
||||
ParseAction::Reduce {
|
||||
symbol,
|
||||
child_count: item.step_index as usize,
|
||||
child_count: item.step_index as u16,
|
||||
dynamic_precedence: item.production.dynamic_precedence,
|
||||
production_id: self.get_production_id(item),
|
||||
production_id: self.get_production_id(item) as u16,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -616,7 +616,7 @@ impl<'a> ParseTableBuilder<'a> {
|
|||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
.collect::<FxHashSet<_>>();
|
||||
let parent_symbol_names = parent_symbols
|
||||
.iter()
|
||||
.map(|&variable_index| {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,17 @@ use std::fmt;
|
|||
use crate::{
|
||||
grammars::LexicalGrammar,
|
||||
rules::Symbol,
|
||||
tables::{ParseStateId, ParseTable},
|
||||
tables::ParseTable,
|
||||
};
|
||||
|
||||
pub struct CoincidentTokenIndex<'a> {
|
||||
entries: Vec<Vec<ParseStateId>>,
|
||||
/// Flat bitset for fast `contains()` checks. Indexed as `a * n + b`
|
||||
/// (both `(a,b)` and `(b,a)` bits are set, so no min/max normalization needed).
|
||||
contains_bits: Vec<u64>,
|
||||
/// Word-aligned per-row bitsets for vectorized intersection checks.
|
||||
/// 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 row_bits: Vec<u64>,
|
||||
grammar: &'a LexicalGrammar,
|
||||
n: usize,
|
||||
}
|
||||
|
|
@ -15,15 +21,17 @@ pub struct CoincidentTokenIndex<'a> {
|
|||
impl<'a> CoincidentTokenIndex<'a> {
|
||||
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],
|
||||
};
|
||||
// Pre-collect terminal indices up front rather than continuously recomputing within the
|
||||
// loop below.
|
||||
let mut terminal_indices = Vec::new();
|
||||
for (i, state) in table.states.iter().enumerate() {
|
||||
for state in table.states.iter() {
|
||||
terminal_indices.clear();
|
||||
terminal_indices.extend(
|
||||
state
|
||||
|
|
@ -34,52 +42,45 @@ impl<'a> CoincidentTokenIndex<'a> {
|
|||
);
|
||||
for (j, &a) in terminal_indices.iter().enumerate() {
|
||||
for &b in &terminal_indices[j..] {
|
||||
let index = result.index(a, b);
|
||||
if result.entries[index].last().copied() != Some(i) {
|
||||
result.entries[index].push(i);
|
||||
}
|
||||
// Set both (a,b) and (b,a) bits so `contains()` needs
|
||||
// no min/max normalization.
|
||||
let ab = a * n + b;
|
||||
result.contains_bits[ab / 64] |= 1u64 << (ab % 64);
|
||||
let ba = b * n + a;
|
||||
result.contains_bits[ba / 64] |= 1u64 << (ba % 64);
|
||||
// Also populate the word-aligned row bitsets.
|
||||
result.row_bits[a * row_words + b / 64] |= 1u64 << (b % 64);
|
||||
result.row_bits[b * row_words + a / 64] |= 1u64 << (a % 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn states_with(&self, a: Symbol, b: Symbol) -> &[ParseStateId] {
|
||||
&self.entries[self.index(a.index, b.index)]
|
||||
}
|
||||
|
||||
pub fn contains(&self, a: Symbol, b: Symbol) -> bool {
|
||||
!self.entries[self.index(a.index, b.index)].is_empty()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
const fn index(&self, a: usize, b: usize) -> usize {
|
||||
if a < b {
|
||||
a * self.n + b
|
||||
} else {
|
||||
b * self.n + a
|
||||
}
|
||||
let bit_index = a.index * self.n + b.index;
|
||||
self.contains_bits[bit_index / 64] & (1u64 << (bit_index % 64)) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CoincidentTokenIndex<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "CoincidentTokenIndex {{")?;
|
||||
|
||||
writeln!(f, " entries: {{")?;
|
||||
for i in 0..self.n {
|
||||
writeln!(f, " {}: {{", self.grammar.variables[i].name)?;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if !coincident.is_empty() {
|
||||
writeln!(
|
||||
f,
|
||||
" {}: {:?},",
|
||||
self.grammar.variables[j].name,
|
||||
self.entries[self.index(i, j)].len()
|
||||
" {}: {:?},",
|
||||
self.grammar.variables[i].name, coincident
|
||||
)?;
|
||||
}
|
||||
writeln!(f, " }},")?;
|
||||
}
|
||||
write!(f, " }},")?;
|
||||
write!(f, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use super::item::{ParseItem, ParseItemDisplay, ParseItemSet, ParseItemSetEntry, TokenSetDisplay};
|
||||
use crate::{
|
||||
|
|
@ -25,9 +24,9 @@ struct FollowSetInfo {
|
|||
pub struct ParseItemSetBuilder<'a> {
|
||||
syntax_grammar: &'a SyntaxGrammar,
|
||||
lexical_grammar: &'a LexicalGrammar,
|
||||
first_sets: HashMap<Symbol, TokenSet>,
|
||||
reserved_first_sets: HashMap<Symbol, ReservedWordSetId>,
|
||||
last_sets: HashMap<Symbol, TokenSet>,
|
||||
first_sets: FxHashMap<Symbol, TokenSet>,
|
||||
reserved_first_sets: FxHashMap<Symbol, ReservedWordSetId>,
|
||||
last_sets: FxHashMap<Symbol, TokenSet>,
|
||||
inlines: &'a InlinedProductionMap,
|
||||
transitive_closure_additions: Vec<Vec<TransitiveClosureAddition<'a>>>,
|
||||
}
|
||||
|
|
@ -47,9 +46,9 @@ impl<'a> ParseItemSetBuilder<'a> {
|
|||
let mut result = Self {
|
||||
syntax_grammar,
|
||||
lexical_grammar,
|
||||
first_sets: HashMap::new(),
|
||||
reserved_first_sets: HashMap::new(),
|
||||
last_sets: HashMap::new(),
|
||||
first_sets: FxHashMap::default(),
|
||||
reserved_first_sets: FxHashMap::default(),
|
||||
last_sets: FxHashMap::default(),
|
||||
inlines,
|
||||
transitive_closure_additions: vec![Vec::new(); syntax_grammar.variables.len()],
|
||||
};
|
||||
|
|
@ -92,7 +91,7 @@ impl<'a> ParseItemSetBuilder<'a> {
|
|||
// Rather than computing these sets using recursion, we use an explicit stack
|
||||
// called `symbols_to_process`.
|
||||
let mut symbols_to_process = Vec::new();
|
||||
let mut processed_non_terminals = HashSet::new();
|
||||
let mut processed_non_terminals = FxHashSet::default();
|
||||
for i in 0..syntax_grammar.variables.len() {
|
||||
let symbol = Symbol::non_terminal(i);
|
||||
let first_set = result.first_sets.entry(symbol).or_default();
|
||||
|
|
@ -162,7 +161,7 @@ impl<'a> ParseItemSetBuilder<'a> {
|
|||
// Rather than computing these additions recursively, we use an explicit stack.
|
||||
let empty_lookaheads = TokenSet::new();
|
||||
let mut stack = Vec::new();
|
||||
let mut follow_set_info_by_non_terminal = HashMap::<usize, FollowSetInfo>::new();
|
||||
let mut follow_set_info_by_non_terminal = FxHashMap::<usize, FollowSetInfo>::default();
|
||||
for i in 0..syntax_grammar.variables.len() {
|
||||
// First, build up a map whose keys are all of the non-terminals that can
|
||||
// appear at the beginning of non-terminal `i`, and whose values store
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
mem,
|
||||
};
|
||||
use std::{cmp::Ordering, mem};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use log::debug;
|
||||
|
||||
|
|
@ -10,10 +9,70 @@ use crate::{
|
|||
OptLevel,
|
||||
dedup::split_state_id_groups,
|
||||
grammars::{LexicalGrammar, SyntaxGrammar, VariableType},
|
||||
rules::{AliasMap, Symbol, TokenSet},
|
||||
rules::{AliasMap, Symbol, SymbolType, TokenSet},
|
||||
tables::{GotoAction, ParseAction, ParseState, ParseStateId, ParseTable, ParseTableEntry},
|
||||
};
|
||||
|
||||
/// Index into `SyntaxGrammar::variables`. All nonterminal `Symbol`s share
|
||||
/// the same `kind`, so storing the index alone is sufficient for ordering.
|
||||
type NonterminalIndex = usize;
|
||||
|
||||
/// A `Symbol` packed into a `u64` for O(1) sort-key comparison.
|
||||
///
|
||||
/// Layout: high 3 bits = `kind` discriminant (5 variants fit in 3 bits), low 61 bits = `index`.
|
||||
/// This preserves `Symbol`'s derived `Ord` ordering (kind first, then index) as a single
|
||||
/// integer comparison, and halves each entry's size vs storing a full `(Symbol, _)` tuple.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct SymbolKey(u64);
|
||||
|
||||
const KEY_TAG_SHIFT: u32 = 61;
|
||||
const KEY_INDEX_MASK: u64 = (1u64 << KEY_TAG_SHIFT) - 1;
|
||||
|
||||
impl SymbolKey {
|
||||
#[inline(always)]
|
||||
fn new(sym: Symbol) -> Self {
|
||||
debug_assert!(sym.index as u64 <= KEY_INDEX_MASK, "symbol index too large");
|
||||
Self((sym.kind as u64) << KEY_TAG_SHIFT | sym.index as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn symbol(self) -> Symbol {
|
||||
let kind = match self.0 >> KEY_TAG_SHIFT {
|
||||
0 => SymbolType::External,
|
||||
1 => SymbolType::End,
|
||||
2 => SymbolType::EndOfNonTerminalExtra,
|
||||
3 => SymbolType::Terminal,
|
||||
_ => SymbolType::NonTerminal,
|
||||
};
|
||||
Symbol {
|
||||
kind,
|
||||
index: self.index(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn index(self) -> usize {
|
||||
(self.0 & KEY_INDEX_MASK) as usize
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_terminal(self) -> bool {
|
||||
(self.0 >> KEY_TAG_SHIFT) == SymbolType::Terminal as u64
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
fn is_non_terminal(self) -> bool {
|
||||
(self.0 >> KEY_TAG_SHIFT) == SymbolType::NonTerminal as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Symbol> for SymbolKey {
|
||||
fn from(sym: Symbol) -> Self {
|
||||
Self::new(sym)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn minimize_parse_table(
|
||||
parse_table: &mut ParseTable,
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
|
|
@ -50,7 +109,7 @@ struct Minimizer<'a> {
|
|||
|
||||
impl Minimizer<'_> {
|
||||
fn remove_unit_reductions(&mut self) {
|
||||
let mut aliased_symbols = HashSet::new();
|
||||
let mut aliased_symbols = FxHashSet::default();
|
||||
for variable in &self.syntax_grammar.variables {
|
||||
for production in &variable.productions {
|
||||
for step in &production.steps {
|
||||
|
|
@ -61,7 +120,7 @@ impl Minimizer<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut unit_reduction_symbols_by_state = HashMap::new();
|
||||
let mut unit_reduction_symbols_by_state = FxHashMap::default();
|
||||
for (i, state) in self.parse_table.states.iter().enumerate() {
|
||||
let mut only_unit_reductions = true;
|
||||
let mut unit_reduction_symbol = None;
|
||||
|
|
@ -135,26 +194,101 @@ impl Minimizer<'_> {
|
|||
|
||||
// Initially group the states by their parse item set core.
|
||||
let mut group_ids_by_state_id = Vec::with_capacity(self.parse_table.states.len());
|
||||
let mut state_ids_by_group_id = vec![Vec::<ParseStateId>::new(); core_count];
|
||||
// Pre-allocate for the maximum possible number of groups (one per state) to
|
||||
// avoid reallocs as split_state_id_groups pushes new groups.
|
||||
let mut state_ids_by_group_id = Vec::with_capacity(self.parse_table.states.len());
|
||||
state_ids_by_group_id.resize(core_count, Vec::new());
|
||||
for (i, state) in self.parse_table.states.iter().enumerate() {
|
||||
state_ids_by_group_id[state.core_id].push(i);
|
||||
group_ids_by_state_id.push(state.core_id);
|
||||
}
|
||||
|
||||
// Precompute sorted terminal entry pointers for merge-join in states_conflict.
|
||||
// entry_maps[state_id][i] = (symbol_key, *const ParseTableEntry)
|
||||
// Keys are packed u64s (symbol_key) for single-instruction comparison.
|
||||
// Storing a raw pointer avoids the IndexMap::get_index call in states_conflict.
|
||||
//
|
||||
// Safety invariant: parse_table.states are not mutated during the grouping phase,
|
||||
// so all pointers remain valid for the lifetime of entry_maps.
|
||||
let entry_maps: Vec<Vec<(SymbolKey, *const ParseTableEntry)>> = self
|
||||
.parse_table
|
||||
.states
|
||||
.iter()
|
||||
.map(|state| {
|
||||
let mut entries: Vec<(SymbolKey, *const ParseTableEntry)> = state
|
||||
.terminal_entries
|
||||
.iter()
|
||||
.map(|(sym, entry)| (SymbolKey::new(*sym), entry as *const ParseTableEntry))
|
||||
.collect();
|
||||
entries.sort_unstable_by_key(|&(key, _)| key);
|
||||
entries
|
||||
})
|
||||
.collect();
|
||||
|
||||
split_state_id_groups(
|
||||
&self.parse_table.states,
|
||||
&mut state_ids_by_group_id,
|
||||
&mut group_ids_by_state_id,
|
||||
0,
|
||||
|left, right, groups| self.states_conflict(left, right, groups),
|
||||
|left, right, groups| self.states_conflict(left, right, groups, &entry_maps),
|
||||
);
|
||||
|
||||
// Precompute per-state sorted shift actions and nonterminal goto actions.
|
||||
// State actions are stable across loop iterations; only group assignments change.
|
||||
// Keys are packed u64s (symbol_key) for single-instruction comparison.
|
||||
let shift_maps: Vec<Vec<(SymbolKey, ParseStateId)>> = self
|
||||
.parse_table
|
||||
.states
|
||||
.iter()
|
||||
.map(|state| {
|
||||
let mut shifts: Vec<(SymbolKey, ParseStateId)> = state
|
||||
.terminal_entries
|
||||
.iter()
|
||||
.filter_map(|(sym, entry)| {
|
||||
let action = entry.actions.last()?;
|
||||
if let ParseAction::Shift { state: s, .. } = action {
|
||||
Some((SymbolKey::new(*sym), *s))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
shifts.sort_unstable_by_key(|&(key, _)| key);
|
||||
shifts
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Store only the symbol index: all nonterminal entries share the same kind,
|
||||
// so index alone is sufficient for sorting and comparison.
|
||||
let nonterminal_maps: Vec<Vec<(NonterminalIndex, GotoAction)>> = self
|
||||
.parse_table
|
||||
.states
|
||||
.iter()
|
||||
.map(|state| {
|
||||
let mut entries: Vec<(NonterminalIndex, GotoAction)> = state
|
||||
.nonterminal_entries
|
||||
.iter()
|
||||
.map(|(sym, action)| (sym.index, *action))
|
||||
.collect();
|
||||
entries.sort_unstable_by_key(|&(idx, _)| idx);
|
||||
entries
|
||||
})
|
||||
.collect();
|
||||
|
||||
while split_state_id_groups(
|
||||
&self.parse_table.states,
|
||||
&mut state_ids_by_group_id,
|
||||
&mut group_ids_by_state_id,
|
||||
0,
|
||||
|left, right, groups| self.state_successors_differ(left, right, groups),
|
||||
|left, right, groups| {
|
||||
self.state_successors_differ(
|
||||
left,
|
||||
right,
|
||||
groups,
|
||||
&shift_maps,
|
||||
&nonterminal_maps,
|
||||
)
|
||||
},
|
||||
) {}
|
||||
|
||||
let error_group_index = state_ids_by_group_id
|
||||
|
|
@ -203,35 +337,72 @@ impl Minimizer<'_> {
|
|||
|
||||
fn states_conflict(
|
||||
&self,
|
||||
left_state: &ParseState,
|
||||
right_state: &ParseState,
|
||||
state1: &ParseState,
|
||||
state2: &ParseState,
|
||||
group_ids_by_state_id: &[ParseStateId],
|
||||
entry_maps: &[Vec<(SymbolKey, *const ParseTableEntry)>],
|
||||
) -> bool {
|
||||
for (token, left_entry) in &left_state.terminal_entries {
|
||||
if let Some(right_entry) = right_state.terminal_entries.get(token) {
|
||||
if self.entries_conflict(
|
||||
left_state.id,
|
||||
right_state.id,
|
||||
token,
|
||||
left_entry,
|
||||
right_entry,
|
||||
group_ids_by_state_id,
|
||||
) {
|
||||
return true;
|
||||
let entries1 = &entry_maps[state1.id];
|
||||
let entries2 = &entry_maps[state2.id];
|
||||
let len1 = entries1.len();
|
||||
let len2 = entries2.len();
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
while i < len1 || j < len2 {
|
||||
// SAFETY: each branch only accesses entries1[i] when i < len1
|
||||
// and entries2[j] when j < len2, both of which hold by construction.
|
||||
let ord = if i < len1 && j < len2 {
|
||||
unsafe { entries1.get_unchecked(i) }
|
||||
.0
|
||||
.cmp(&unsafe { entries2.get_unchecked(j) }.0)
|
||||
} else if i < len1 {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
};
|
||||
match ord {
|
||||
Ordering::Equal => {
|
||||
// SAFETY: Equal is only reachable when i < len1 && j < len2.
|
||||
let e1 = unsafe { entries1.get_unchecked(i) };
|
||||
let e2 = unsafe { entries2.get_unchecked(j) };
|
||||
let token = e1.0.symbol();
|
||||
// Safety: pointers were taken from the same parse_table.states that
|
||||
// is not mutated during the grouping phase (see entry_maps comment).
|
||||
let left_entry = unsafe { &*e1.1 };
|
||||
let right_entry = unsafe { &*e2.1 };
|
||||
if self.entries_conflict(
|
||||
state1.id,
|
||||
state2.id,
|
||||
&token,
|
||||
left_entry,
|
||||
right_entry,
|
||||
group_ids_by_state_id,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
Ordering::Less => {
|
||||
// SAFETY: Less is only reachable when i < len1.
|
||||
let e1 = unsafe { entries1.get_unchecked(i) };
|
||||
let token = e1.0.symbol();
|
||||
if self.token_conflicts(state1.id, state2.id, state2, entries2, token) {
|
||||
return true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// SAFETY: Greater is only reachable when j < len2.
|
||||
let e2 = unsafe { entries2.get_unchecked(j) };
|
||||
let token = e2.0.symbol();
|
||||
if self.token_conflicts(state1.id, state2.id, state1, entries1, token) {
|
||||
return true;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
} else if self.token_conflicts(left_state.id, right_state.id, right_state, *token) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for token in right_state.terminal_entries.keys() {
|
||||
if !left_state.terminal_entries.contains_key(token)
|
||||
&& self.token_conflicts(left_state.id, right_state.id, left_state, *token)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -240,44 +411,69 @@ impl Minimizer<'_> {
|
|||
state1: &ParseState,
|
||||
state2: &ParseState,
|
||||
group_ids_by_state_id: &[ParseStateId],
|
||||
shift_maps: &[Vec<(SymbolKey, ParseStateId)>],
|
||||
nonterminal_maps: &[Vec<(usize, GotoAction)>],
|
||||
) -> bool {
|
||||
for (token, entry1) in &state1.terminal_entries {
|
||||
if let ParseAction::Shift { state: s1, .. } = entry1.actions.last().unwrap()
|
||||
&& let Some(entry2) = state2.terminal_entries.get(token)
|
||||
&& let ParseAction::Shift { state: s2, .. } = entry2.actions.last().unwrap()
|
||||
{
|
||||
let group1 = group_ids_by_state_id[*s1];
|
||||
let group2 = group_ids_by_state_id[*s2];
|
||||
if group1 != group2 {
|
||||
debug!(
|
||||
"split states {} {} - successors for {} are split: {s1} {s2}",
|
||||
state1.id,
|
||||
state2.id,
|
||||
self.symbol_name(token),
|
||||
);
|
||||
return true;
|
||||
let shifts1 = &shift_maps[state1.id];
|
||||
let shifts2 = &shift_maps[state2.id];
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
while i < shifts1.len() && j < shifts2.len() {
|
||||
// SAFETY: loop condition ensures i < shifts1.len() and j < shifts2.len().
|
||||
let (k1, s1) = *unsafe { shifts1.get_unchecked(i) };
|
||||
let (k2, s2) = *unsafe { shifts2.get_unchecked(j) };
|
||||
match k1.cmp(&k2) {
|
||||
Ordering::Less => i += 1,
|
||||
Ordering::Greater => j += 1,
|
||||
Ordering::Equal => {
|
||||
let group1 = group_ids_by_state_id[s1];
|
||||
let group2 = group_ids_by_state_id[s2];
|
||||
if group1 != group2 {
|
||||
debug!(
|
||||
"split states {} {} - successors for {} are split: {s1} {s2}",
|
||||
state1.id,
|
||||
state2.id,
|
||||
self.symbol_name(&k1.symbol()),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (symbol, s1) in &state1.nonterminal_entries {
|
||||
if let Some(s2) = state2.nonterminal_entries.get(symbol) {
|
||||
match (s1, s2) {
|
||||
(GotoAction::ShiftExtra, GotoAction::ShiftExtra) => {}
|
||||
(GotoAction::Goto(s1), GotoAction::Goto(s2)) => {
|
||||
let group1 = group_ids_by_state_id[*s1];
|
||||
let group2 = group_ids_by_state_id[*s2];
|
||||
if group1 != group2 {
|
||||
debug!(
|
||||
"split states {} {} - successors for {} are split: {s1} {s2}",
|
||||
state1.id,
|
||||
state2.id,
|
||||
self.symbol_name(symbol),
|
||||
);
|
||||
return true;
|
||||
let nonterms1 = &nonterminal_maps[state1.id];
|
||||
let nonterms2 = &nonterminal_maps[state2.id];
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
while i < nonterms1.len() && j < nonterms2.len() {
|
||||
// SAFETY: loop condition ensures i < nonterms1.len() and j < nonterms2.len().
|
||||
let (idx1, s1) = *unsafe { nonterms1.get_unchecked(i) };
|
||||
let (idx2, s2) = *unsafe { nonterms2.get_unchecked(j) };
|
||||
match idx1.cmp(&idx2) {
|
||||
Ordering::Less => i += 1,
|
||||
Ordering::Greater => j += 1,
|
||||
Ordering::Equal => {
|
||||
match (s1, s2) {
|
||||
(GotoAction::ShiftExtra, GotoAction::ShiftExtra) => {}
|
||||
(GotoAction::Goto(s1), GotoAction::Goto(s2)) => {
|
||||
let group1 = group_ids_by_state_id[s1];
|
||||
let group2 = group_ids_by_state_id[s2];
|
||||
if group1 != group2 {
|
||||
debug!(
|
||||
"split states {} {} - successors for {} are split: {s1} {s2}",
|
||||
state1.id,
|
||||
state2.id,
|
||||
self.syntax_grammar.variables[idx1].name,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => return true,
|
||||
}
|
||||
_ => return true,
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -346,6 +542,7 @@ impl Minimizer<'_> {
|
|||
left_id: ParseStateId,
|
||||
right_id: ParseStateId,
|
||||
right_state: &ParseState,
|
||||
right_entry_map: &[(SymbolKey, *const ParseTableEntry)],
|
||||
new_token: Symbol,
|
||||
) -> bool {
|
||||
if new_token == Symbol::end_of_nonterminal_extra() {
|
||||
|
|
@ -382,28 +579,36 @@ impl Minimizer<'_> {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Hoist loop-invariant word-token comparisons.
|
||||
let word_token = self.syntax_grammar.word_token;
|
||||
let word_key = word_token.map(SymbolKey::new);
|
||||
let new_token_key = SymbolKey::new(new_token);
|
||||
let new_token_is_word = word_key == Some(new_token_key);
|
||||
let new_token_is_keyword = word_token.is_some() && self.keywords.contains(&new_token);
|
||||
|
||||
// Do not add a token if it conflicts with an existing token.
|
||||
for token in right_state.terminal_entries.keys().copied() {
|
||||
if !token.is_terminal() {
|
||||
// Iterate the pre-sorted Vec instead of IndexMap keys for cache-friendlier access.
|
||||
for &(key, _) in right_entry_map {
|
||||
if !key.is_terminal() {
|
||||
continue;
|
||||
}
|
||||
if self.syntax_grammar.word_token == Some(token) && self.keywords.contains(&new_token) {
|
||||
if new_token_is_keyword && word_key == Some(key) {
|
||||
continue;
|
||||
}
|
||||
if self.syntax_grammar.word_token == Some(new_token) && self.keywords.contains(&token) {
|
||||
if new_token_is_word && self.keywords.contains(&key.symbol()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self
|
||||
.token_conflict_map
|
||||
.does_conflict(new_token.index, token.index)
|
||||
.does_conflict(new_token.index, key.index())
|
||||
{
|
||||
debug!(
|
||||
"split states {} {} - token {} conflicts with {}",
|
||||
left_id,
|
||||
right_id,
|
||||
self.symbol_name(&new_token),
|
||||
self.symbol_name(&token),
|
||||
self.symbol_name(&key.symbol()),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
use std::{cmp::Ordering, collections::HashSet, fmt};
|
||||
use std::{cmp::Ordering, fmt};
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{
|
||||
build_tables::item::TokenSetDisplay,
|
||||
|
|
@ -7,14 +11,22 @@ use crate::{
|
|||
rules::TokenSet,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
struct TokenConflictStatus {
|
||||
matches_prefix: bool,
|
||||
does_match_continuation: bool,
|
||||
does_match_valid_continuation: bool,
|
||||
does_match_separators: bool,
|
||||
matches_same_string: bool,
|
||||
matches_different_string: bool,
|
||||
bitflags! {
|
||||
/// Per-(i,j) token conflict status, packed into a single byte.
|
||||
///
|
||||
/// Using a `u8` bitfield instead of a struct of six `bool`s reduces the
|
||||
/// status matrix from `n²·6` bytes to `n²` bytes (6× smaller). For a
|
||||
/// grammar with 200 tokens that's 240 KB → 40 KB, so a full row fits
|
||||
/// comfortably in L1 cache while scanning in `token_conflicts`.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
struct TokenConflictStatus: u8 {
|
||||
const MATCHES_PREFIX = 1 << 0;
|
||||
const DOES_MATCH_CONTINUATION = 1 << 1;
|
||||
const DOES_MATCH_VALID_CONT = 1 << 2;
|
||||
const DOES_MATCH_SEPARATORS = 1 << 3;
|
||||
const MATCHES_SAME_STRING = 1 << 4;
|
||||
const MATCHES_DIFFERENT_STRING = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenConflictMap<'a> {
|
||||
|
|
@ -24,6 +36,13 @@ pub struct TokenConflictMap<'a> {
|
|||
starting_chars_by_index: Vec<CharacterSet>,
|
||||
following_chars_by_index: Vec<CharacterSet>,
|
||||
grammar: &'a LexicalGrammar,
|
||||
/// Per-row bitsets for fast batch conflict checks.
|
||||
/// Row `i` spans `[i * row_words .. (i+1) * row_words]`.
|
||||
/// Bit `j` is set iff `does_conflict(i, j) || does_match_prefix(i, j)`.
|
||||
pub conflict_or_prefix_bits: Vec<u64>,
|
||||
/// Bit `j` is set iff `does_overlap(i, j) || does_overlap(j, i)`.
|
||||
pub overlap_either_bits: Vec<u64>,
|
||||
pub row_words: usize,
|
||||
}
|
||||
|
||||
impl<'a> TokenConflictMap<'a> {
|
||||
|
|
@ -38,16 +57,50 @@ impl<'a> TokenConflictMap<'a> {
|
|||
let starting_chars = get_starting_chars(&mut cursor, grammar);
|
||||
let following_chars = get_following_chars(&starting_chars, &following_tokens);
|
||||
|
||||
// Pre-compute O(1) lookup: NFA state ID → owning variable index.
|
||||
// Replaces repeated O(log n) binary searches in compute_conflict_status.
|
||||
let nfa_state_to_var: Vec<usize> = (0..grammar.nfa.states.len())
|
||||
.map(|id| grammar.variable_index_for_nfa_state(id as u32))
|
||||
.collect();
|
||||
|
||||
let n = grammar.variables.len();
|
||||
let mut status_matrix = vec![TokenConflictStatus::default(); n * n];
|
||||
let mut status_matrix = vec![TokenConflictStatus::empty(); n * n];
|
||||
for i in 0..grammar.variables.len() {
|
||||
for j in 0..i {
|
||||
let status = compute_conflict_status(&mut cursor, grammar, &following_chars, i, j);
|
||||
let status =
|
||||
compute_conflict_status(&mut cursor, grammar, &following_chars, &nfa_state_to_var, i, j);
|
||||
status_matrix[matrix_index(n, i, j)] = status.0;
|
||||
status_matrix[matrix_index(n, j, i)] = status.1;
|
||||
}
|
||||
}
|
||||
|
||||
// Precompute per-row bitsets for vectorized check_token_conflicts.
|
||||
let row_words = n.div_ceil(64);
|
||||
let conflict_mask = TokenConflictStatus::DOES_MATCH_VALID_CONT
|
||||
| TokenConflictStatus::DOES_MATCH_SEPARATORS
|
||||
| TokenConflictStatus::MATCHES_SAME_STRING
|
||||
| TokenConflictStatus::MATCHES_PREFIX;
|
||||
let overlap_mask = TokenConflictStatus::DOES_MATCH_SEPARATORS
|
||||
| TokenConflictStatus::MATCHES_PREFIX
|
||||
| TokenConflictStatus::MATCHES_SAME_STRING
|
||||
| TokenConflictStatus::DOES_MATCH_CONTINUATION;
|
||||
|
||||
let mut conflict_or_prefix_bits = vec![0u64; n * row_words];
|
||||
let mut overlap_either_bits = vec![0u64; n * row_words];
|
||||
for i in 0..n {
|
||||
let row_base = i * row_words;
|
||||
for j in 0..n {
|
||||
let entry_ij = status_matrix[matrix_index(n, i, j)];
|
||||
if entry_ij.intersects(conflict_mask) {
|
||||
conflict_or_prefix_bits[row_base + j / 64] |= 1u64 << (j % 64);
|
||||
}
|
||||
let entry_ji = status_matrix[matrix_index(n, j, i)];
|
||||
if entry_ij.intersects(overlap_mask) || entry_ji.intersects(overlap_mask) {
|
||||
overlap_either_bits[row_base + j / 64] |= 1u64 << (j % 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TokenConflictMap {
|
||||
n,
|
||||
status_matrix,
|
||||
|
|
@ -55,6 +108,9 @@ impl<'a> TokenConflictMap<'a> {
|
|||
starting_chars_by_index: starting_chars,
|
||||
following_chars_by_index: following_chars,
|
||||
grammar,
|
||||
conflict_or_prefix_bits,
|
||||
overlap_either_bits,
|
||||
row_words,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,44 +124,54 @@ impl<'a> TokenConflictMap<'a> {
|
|||
|
||||
/// Does token `i` match any strings that token `j` does *not* match?
|
||||
pub fn does_match_different_string(&self, i: usize, j: usize) -> bool {
|
||||
self.status_matrix[matrix_index(self.n, i, j)].matches_different_string
|
||||
self.status_matrix[matrix_index(self.n, i, j)]
|
||||
.contains(TokenConflictStatus::MATCHES_DIFFERENT_STRING)
|
||||
}
|
||||
|
||||
/// Does token `i` match any strings that token `j` also matches, where
|
||||
/// token `i` is preferred over token `j`?
|
||||
#[inline]
|
||||
pub fn does_match_same_string(&self, i: usize, j: usize) -> bool {
|
||||
self.status_matrix[matrix_index(self.n, i, j)].matches_same_string
|
||||
self.status_matrix[matrix_index(self.n, i, j)]
|
||||
.contains(TokenConflictStatus::MATCHES_SAME_STRING)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn does_conflict(&self, i: usize, j: usize) -> bool {
|
||||
let entry = &self.status_matrix[matrix_index(self.n, i, j)];
|
||||
entry.does_match_valid_continuation
|
||||
|| entry.does_match_separators
|
||||
|| entry.matches_same_string
|
||||
debug_assert!(i < self.n && j < self.n, "token indices out of bounds");
|
||||
// Safety: i < n and j < n imply n*i+j < n*n == status_matrix.len().
|
||||
let entry = unsafe { *self.status_matrix.get_unchecked(matrix_index(self.n, i, j)) };
|
||||
entry.intersects(
|
||||
TokenConflictStatus::DOES_MATCH_VALID_CONT
|
||||
| TokenConflictStatus::DOES_MATCH_SEPARATORS
|
||||
| TokenConflictStatus::MATCHES_SAME_STRING,
|
||||
)
|
||||
}
|
||||
|
||||
/// Does token `i` match any strings that are *prefixes* of strings matched by `j`?
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn does_match_prefix(&self, i: usize, j: usize) -> bool {
|
||||
self.status_matrix[matrix_index(self.n, i, j)].matches_prefix
|
||||
self.status_matrix[matrix_index(self.n, i, j)]
|
||||
.contains(TokenConflictStatus::MATCHES_PREFIX)
|
||||
}
|
||||
|
||||
pub fn does_match_shorter_or_longer(&self, i: usize, j: usize) -> bool {
|
||||
let entry = &self.status_matrix[matrix_index(self.n, i, j)];
|
||||
let reverse_entry = &self.status_matrix[matrix_index(self.n, j, i)];
|
||||
(entry.does_match_valid_continuation || entry.does_match_separators)
|
||||
&& !reverse_entry.does_match_separators
|
||||
let entry = self.status_matrix[matrix_index(self.n, i, j)];
|
||||
let reverse_entry = self.status_matrix[matrix_index(self.n, j, i)];
|
||||
entry.intersects(
|
||||
TokenConflictStatus::DOES_MATCH_VALID_CONT | TokenConflictStatus::DOES_MATCH_SEPARATORS,
|
||||
) && !reverse_entry.contains(TokenConflictStatus::DOES_MATCH_SEPARATORS)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn does_overlap(&self, i: usize, j: usize) -> bool {
|
||||
let status = &self.status_matrix[matrix_index(self.n, i, j)];
|
||||
status.does_match_separators
|
||||
|| status.matches_prefix
|
||||
|| status.matches_same_string
|
||||
|| status.does_match_continuation
|
||||
self.status_matrix[matrix_index(self.n, i, j)].intersects(
|
||||
TokenConflictStatus::DOES_MATCH_SEPARATORS
|
||||
| TokenConflictStatus::MATCHES_PREFIX
|
||||
| TokenConflictStatus::MATCHES_SAME_STRING
|
||||
| TokenConflictStatus::DOES_MATCH_CONTINUATION,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn prefer_token(grammar: &LexicalGrammar, left: (i32, usize), right: (i32, usize)) -> bool {
|
||||
|
|
@ -241,51 +307,70 @@ fn get_following_chars(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Hash a sorted slice of NFA state IDs to a single `u64` for use as a
|
||||
/// visited-set key. The collision probability per pair is ~1/2^64, which is
|
||||
/// negligible for any practical grammar.
|
||||
#[inline]
|
||||
fn hash_state_set(states: &[u32]) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = rustc_hash::FxHasher::default();
|
||||
states.hash(&mut h);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
fn compute_conflict_status(
|
||||
cursor: &mut NfaCursor,
|
||||
grammar: &LexicalGrammar,
|
||||
following_chars: &[CharacterSet],
|
||||
nfa_state_to_var: &[usize],
|
||||
i: usize,
|
||||
j: usize,
|
||||
) -> (TokenConflictStatus, TokenConflictStatus) {
|
||||
let mut visited_state_sets = HashSet::new();
|
||||
let mut state_set_queue = vec![vec![
|
||||
let mut visited_state_sets = FxHashSet::<u64>::default();
|
||||
let mut state_set_queue = Vec::with_capacity(4);
|
||||
state_set_queue.push(vec![
|
||||
grammar.variables[i].start_state,
|
||||
grammar.variables[j].start_state,
|
||||
]];
|
||||
]);
|
||||
let mut result = (
|
||||
TokenConflictStatus::default(),
|
||||
TokenConflictStatus::default(),
|
||||
TokenConflictStatus::empty(),
|
||||
TokenConflictStatus::empty(),
|
||||
);
|
||||
|
||||
while let Some(state_set) = state_set_queue.pop() {
|
||||
let mut live_variable_indices = grammar.variable_indices_for_nfa_states(&state_set);
|
||||
|
||||
// If only one of the two tokens could possibly match from this state, then
|
||||
// there is no reason to analyze any of its successors. Just record the fact
|
||||
// that the token matches a string that the other token does not match.
|
||||
let first_live_variable_index = live_variable_indices.next().unwrap();
|
||||
if live_variable_indices.count() == 0 {
|
||||
let first_live_variable_index = nfa_state_to_var[state_set[0] as usize];
|
||||
if state_set
|
||||
.iter()
|
||||
.all(|&s| nfa_state_to_var[s as usize] == first_live_variable_index)
|
||||
{
|
||||
if first_live_variable_index == i {
|
||||
result.0.matches_different_string = true;
|
||||
result.0.insert(TokenConflictStatus::MATCHES_DIFFERENT_STRING);
|
||||
} else {
|
||||
result.1.matches_different_string = true;
|
||||
result.1.insert(TokenConflictStatus::MATCHES_DIFFERENT_STRING);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't pursue states where there's no potential for conflict.
|
||||
cursor.reset(state_set);
|
||||
let within_separator = cursor.transition_chars().any(|(_, sep)| sep);
|
||||
|
||||
// Compute lazily: most BFS states have no completions, so
|
||||
// `within_separator` is never needed in those iterations.
|
||||
let mut within_separator = None::<bool>;
|
||||
|
||||
// Examine each possible completed token in this state.
|
||||
let mut completion = None;
|
||||
for (id, precedence) in cursor.completions() {
|
||||
if within_separator {
|
||||
let sep = *within_separator
|
||||
.get_or_insert_with(|| cursor.transition_chars().any(|(_, sep)| sep));
|
||||
if sep {
|
||||
if id == i {
|
||||
result.0.does_match_separators = true;
|
||||
result.0.insert(TokenConflictStatus::DOES_MATCH_SEPARATORS);
|
||||
} else {
|
||||
result.1.does_match_separators = true;
|
||||
result.1.insert(TokenConflictStatus::DOES_MATCH_SEPARATORS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,9 +395,9 @@ fn compute_conflict_status(
|
|||
}
|
||||
|
||||
if preferred_id == i {
|
||||
result.0.matches_same_string = true;
|
||||
result.0.insert(TokenConflictStatus::MATCHES_SAME_STRING);
|
||||
} else {
|
||||
result.1.matches_same_string = true;
|
||||
result.1.insert(TokenConflictStatus::MATCHES_SAME_STRING);
|
||||
}
|
||||
} else {
|
||||
completion = Some((id, precedence));
|
||||
|
|
@ -329,12 +414,18 @@ fn compute_conflict_status(
|
|||
if let Some((completed_id, completed_precedence)) = completion {
|
||||
let mut advanced_id = None;
|
||||
let mut successor_contains_completed_id = false;
|
||||
for variable_id in grammar.variable_indices_for_nfa_states(&transition.states) {
|
||||
if variable_id == completed_id {
|
||||
let mut prev_var = None;
|
||||
for &state_id in &transition.states {
|
||||
let var_id = nfa_state_to_var[state_id as usize];
|
||||
if prev_var == Some(var_id) {
|
||||
continue;
|
||||
}
|
||||
prev_var = Some(var_id);
|
||||
if var_id == completed_id {
|
||||
successor_contains_completed_id = true;
|
||||
break;
|
||||
}
|
||||
advanced_id = Some(variable_id);
|
||||
advanced_id = Some(var_id);
|
||||
}
|
||||
|
||||
// Determine which action is preferred: matching the already complete
|
||||
|
|
@ -345,29 +436,31 @@ fn compute_conflict_status(
|
|||
&transition,
|
||||
completed_id,
|
||||
completed_precedence,
|
||||
within_separator,
|
||||
within_separator.unwrap_or(false),
|
||||
) {
|
||||
can_advance = true;
|
||||
if advanced_id == i {
|
||||
result.0.does_match_continuation = true;
|
||||
result.0.insert(TokenConflictStatus::DOES_MATCH_CONTINUATION);
|
||||
if transition.characters.does_intersect(&following_chars[j]) {
|
||||
result.0.does_match_valid_continuation = true;
|
||||
result.0.insert(TokenConflictStatus::DOES_MATCH_VALID_CONT);
|
||||
}
|
||||
} else {
|
||||
result.1.does_match_continuation = true;
|
||||
result.1.insert(TokenConflictStatus::DOES_MATCH_CONTINUATION);
|
||||
if transition.characters.does_intersect(&following_chars[i]) {
|
||||
result.1.does_match_valid_continuation = true;
|
||||
result.1.insert(TokenConflictStatus::DOES_MATCH_VALID_CONT);
|
||||
}
|
||||
}
|
||||
} else if completed_id == i {
|
||||
result.0.matches_prefix = true;
|
||||
result.0.insert(TokenConflictStatus::MATCHES_PREFIX);
|
||||
} else {
|
||||
result.1.matches_prefix = true;
|
||||
result.1.insert(TokenConflictStatus::MATCHES_PREFIX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if can_advance && visited_state_sets.insert(transition.states.clone()) {
|
||||
if can_advance
|
||||
&& visited_state_sets.insert(hash_state_set(&transition.states))
|
||||
{
|
||||
state_set_queue.push(transition.states);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ pub fn split_state_id_groups<S>(
|
|||
) -> bool {
|
||||
let mut result = false;
|
||||
|
||||
// Use a flat bitset instead of Vec::contains for O(1) membership tests.
|
||||
let mut is_split = vec![false; states.len()];
|
||||
|
||||
let mut group_id = start_group_id;
|
||||
while group_id < state_ids_by_group_id.len() {
|
||||
let state_ids = &state_ids_by_group_id[group_id];
|
||||
|
|
@ -16,7 +19,7 @@ pub fn split_state_id_groups<S>(
|
|||
let mut i = 0;
|
||||
while i < state_ids.len() {
|
||||
let left_state_id = state_ids[i];
|
||||
if split_state_ids.contains(&left_state_id) {
|
||||
if is_split[left_state_id] {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -28,14 +31,20 @@ pub fn split_state_id_groups<S>(
|
|||
let mut j = i + 1;
|
||||
while j < state_ids.len() {
|
||||
let right_state_id = state_ids[j];
|
||||
if split_state_ids.contains(&right_state_id) {
|
||||
if is_split[right_state_id] {
|
||||
j += 1;
|
||||
continue;
|
||||
}
|
||||
let right_state = &states[right_state_id];
|
||||
|
||||
if should_split(left_state, right_state, group_ids_by_state_id) {
|
||||
// Reserve on first split to cover the rest of the group in one
|
||||
// allocation, avoiding all subsequent reallocs for this group.
|
||||
if split_state_ids.is_empty() {
|
||||
split_state_ids.reserve(state_ids.len());
|
||||
}
|
||||
split_state_ids.push(right_state_id);
|
||||
is_split[right_state_id] = true;
|
||||
}
|
||||
|
||||
j += 1;
|
||||
|
|
@ -47,11 +56,12 @@ pub fn split_state_id_groups<S>(
|
|||
// If any states were removed from the group, add them all as a new group.
|
||||
if !split_state_ids.is_empty() {
|
||||
result = true;
|
||||
state_ids_by_group_id[group_id].retain(|i| !split_state_ids.contains(i));
|
||||
state_ids_by_group_id[group_id].retain(|i| !is_split[*i]);
|
||||
|
||||
let new_group_id = state_ids_by_group_id.len();
|
||||
for id in &split_state_ids {
|
||||
group_ids_by_state_id[*id] = new_group_id;
|
||||
is_split[*id] = false;
|
||||
}
|
||||
|
||||
state_ids_by_group_id.push(split_state_ids);
|
||||
|
|
|
|||
|
|
@ -229,10 +229,11 @@ impl LexicalGrammar {
|
|||
}
|
||||
|
||||
pub fn variable_index_for_nfa_state(&self, state_id: u32) -> usize {
|
||||
// `start_state` values are monotonically increasing (each variable's entry
|
||||
// is the last NFA state allocated for it). Binary search for the first
|
||||
// variable whose upper-boundary state >= state_id, i.e. the owner variable.
|
||||
self.variables
|
||||
.iter()
|
||||
.position(|v| v.start_state >= state_id)
|
||||
.unwrap()
|
||||
.partition_point(|v| v.start_state < state_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -482,6 +482,18 @@ impl<'a> NfaCursor<'a> {
|
|||
Self::group_transitions(self.raw_transitions())
|
||||
}
|
||||
|
||||
/// Like `transitions()` but also returns whether any raw NFA transition
|
||||
/// is a separator — computed in the same pass, avoiding a second iteration
|
||||
/// over `state_ids` for callers that need both.
|
||||
pub fn transitions_and_any_sep(&self) -> (Vec<NfaTransition>, bool) {
|
||||
let mut any_sep = false;
|
||||
let result = Self::group_transitions(self.raw_transitions().map(|(chars, is_sep, prec, state)| {
|
||||
any_sep |= is_sep;
|
||||
(chars, is_sep, prec, state)
|
||||
}));
|
||||
(result, any_sep)
|
||||
}
|
||||
|
||||
fn raw_transitions(&self) -> impl Iterator<Item = (&CharacterSet, bool, i32, u32)> {
|
||||
self.state_ids.iter().filter_map(move |id| {
|
||||
if let NfaState::Advance {
|
||||
|
|
@ -502,13 +514,22 @@ impl<'a> NfaCursor<'a> {
|
|||
iter: impl Iterator<Item = (&'b CharacterSet, bool, i32, u32)>,
|
||||
) -> Vec<NfaTransition> {
|
||||
let mut result = Vec::<NfaTransition>::new();
|
||||
for (chars, is_sep, prec, state) in iter {
|
||||
let mut chars = chars.clone();
|
||||
// Reuse a single CharacterSet buffer across iterations to avoid one
|
||||
// malloc per raw transition. `assign` refills it in-place; `mem::take`
|
||||
// donates the allocation to a result entry when chars has a remainder.
|
||||
let mut chars = CharacterSet::empty();
|
||||
for (input_chars, is_sep, prec, state) in iter {
|
||||
chars.assign(input_chars);
|
||||
let mut i = 0;
|
||||
while i < result.len() && !chars.is_empty() {
|
||||
let intersection = result[i].characters.remove_intersection(&mut chars);
|
||||
if !intersection.is_empty() {
|
||||
let mut intersection_states = result[i].states.clone();
|
||||
let chars_is_empty = result[i].characters.is_empty();
|
||||
let mut intersection_states = if chars_is_empty {
|
||||
mem::take(&mut result[i].states)
|
||||
} else {
|
||||
result[i].states.clone()
|
||||
};
|
||||
if let Err(j) = intersection_states.binary_search(&state) {
|
||||
intersection_states.insert(j, state);
|
||||
}
|
||||
|
|
@ -518,18 +539,23 @@ impl<'a> NfaCursor<'a> {
|
|||
precedence: max(result[i].precedence, prec),
|
||||
states: intersection_states,
|
||||
};
|
||||
if result[i].characters.is_empty() {
|
||||
if chars_is_empty {
|
||||
result[i] = intersection_transition;
|
||||
} else {
|
||||
result.insert(i, intersection_transition);
|
||||
i += 1;
|
||||
// Push to the tail instead of inserting at i (which
|
||||
// would be O(n)). After remove_intersection, the new
|
||||
// `chars` (C') and `intersection` (I = A∩C) are
|
||||
// disjoint, so when the loop later reaches I at the
|
||||
// tail, remove_intersection(I, C'') will be a no-op.
|
||||
// The final sort makes mid-loop ordering irrelevant.
|
||||
result.push(intersection_transition);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if !chars.is_empty() {
|
||||
result.push(NfaTransition {
|
||||
characters: chars,
|
||||
characters: mem::take(&mut chars),
|
||||
precedence: prec,
|
||||
states: vec![state],
|
||||
is_separator: is_sep,
|
||||
|
|
@ -546,7 +572,7 @@ impl<'a> NfaCursor<'a> {
|
|||
{
|
||||
let characters = mem::take(&mut result[j].characters);
|
||||
result[j].characters = characters.add(&result[i].characters);
|
||||
result.remove(i);
|
||||
result.swap_remove(i);
|
||||
i -= 1;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
|
@ -22,7 +24,7 @@ pub struct FieldInfo {
|
|||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct VariableInfo {
|
||||
pub fields: HashMap<String, FieldInfo>,
|
||||
pub fields: FxHashMap<String, FieldInfo>,
|
||||
pub children: FieldInfo,
|
||||
pub children_without_fields: FieldInfo,
|
||||
pub has_multi_step_production: bool,
|
||||
|
|
@ -195,7 +197,7 @@ pub fn get_variable_info(
|
|||
// immediately combined across all productions, but the child quantities must be
|
||||
// recorded separately for each production.
|
||||
for production in &variable.productions {
|
||||
let mut production_field_quantities = HashMap::new();
|
||||
let mut production_field_quantities = FxHashMap::default();
|
||||
let mut production_children_quantity = ChildQuantity::zero();
|
||||
let mut production_children_without_fields_quantity = ChildQuantity::zero();
|
||||
let mut production_has_uninitialized_invisible_children = false;
|
||||
|
|
@ -379,8 +381,8 @@ pub fn get_variable_info(
|
|||
fn get_aliases_by_symbol(
|
||||
syntax_grammar: &SyntaxGrammar,
|
||||
default_aliases: &AliasMap,
|
||||
) -> HashMap<Symbol, BTreeSet<Option<Alias>>> {
|
||||
let mut aliases_by_symbol = HashMap::new();
|
||||
) -> FxHashMap<Symbol, BTreeSet<Option<Alias>>> {
|
||||
let mut aliases_by_symbol = FxHashMap::default();
|
||||
for (symbol, alias) in default_aliases {
|
||||
aliases_by_symbol.insert(*symbol, {
|
||||
let mut aliases = BTreeSet::new();
|
||||
|
|
@ -426,7 +428,7 @@ pub fn get_supertype_symbol_map(
|
|||
let aliases_by_symbol = get_aliases_by_symbol(syntax_grammar, default_aliases);
|
||||
let mut supertype_symbol_map = BTreeMap::new();
|
||||
|
||||
let mut symbols_by_alias = HashMap::new();
|
||||
let mut symbols_by_alias = FxHashMap::default();
|
||||
for (symbol, aliases) in &aliases_by_symbol {
|
||||
for alias in aliases.iter().flatten() {
|
||||
symbols_by_alias
|
||||
|
|
@ -555,7 +557,7 @@ pub fn generate_node_types_json(
|
|||
)
|
||||
})
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
.collect::<FxHashSet<_>>();
|
||||
|
||||
let mut subtype_map = Vec::new();
|
||||
for (i, info) in variable_info.iter().enumerate() {
|
||||
|
|
@ -1833,7 +1835,7 @@ mod tests {
|
|||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -1853,7 +1855,7 @@ mod tests {
|
|||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1920,7 +1922,7 @@ mod tests {
|
|||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1981,7 +1983,7 @@ mod tests {
|
|||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -2055,7 +2057,7 @@ mod tests {
|
|||
}
|
||||
)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashSet;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
|
@ -160,7 +160,7 @@ fn variable_is_used(
|
|||
extras: &[Rule],
|
||||
externals: &[Rule],
|
||||
target_name: &str,
|
||||
in_progress: &mut HashSet<String>,
|
||||
in_progress: &mut FxHashSet<String>,
|
||||
) -> bool {
|
||||
let root = &grammar_rules.first().unwrap().0;
|
||||
if target_name == root {
|
||||
|
|
@ -241,7 +241,7 @@ pub(crate) fn parse_grammar(input: &str) -> ParseGrammarResult<InputGrammar> {
|
|||
.map(|(n, r)| Ok((n, parse_rule(serde_json::from_value(r)?, false)?)))
|
||||
.collect::<ParseGrammarResult<Vec<_>>>()?;
|
||||
|
||||
let mut in_progress = HashSet::new();
|
||||
let mut in_progress = FxHashSet::default();
|
||||
|
||||
for (name, rule) in &rules {
|
||||
if grammar_json.word.as_ref().is_none_or(|w| w != name)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ mod process_inlines;
|
|||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeSet, HashMap, HashSet, hash_map},
|
||||
collections::{BTreeSet, hash_map},
|
||||
mem,
|
||||
};
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ pub use expand_tokens::ExpandTokensError;
|
|||
pub use extract_tokens::ExtractTokensError;
|
||||
pub use flatten_grammar::FlattenGrammarError;
|
||||
use indexmap::IndexMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
pub use intern_symbols::InternSymbolsError;
|
||||
pub use process_inlines::ProcessInlinesError;
|
||||
use serde::Serialize;
|
||||
|
|
@ -256,7 +257,7 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
|
|||
fn validate(
|
||||
rule_name: &str,
|
||||
rule: &Rule,
|
||||
names: &HashSet<&String>,
|
||||
names: &FxHashSet<&String>,
|
||||
) -> ValidatePrecedenceResult<()> {
|
||||
match rule {
|
||||
Rule::Repeat(rule) => validate(rule_name, rule, names),
|
||||
|
|
@ -281,7 +282,7 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
|
|||
|
||||
// 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 = HashMap::new();
|
||||
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) {
|
||||
|
|
@ -321,7 +322,7 @@ fn validate_precedences(grammar: &InputGrammar) -> ValidatePrecedenceResult<()>
|
|||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<&String>>();
|
||||
.collect::<FxHashSet<&String>>();
|
||||
for variable in &grammar.variables {
|
||||
validate(&variable.name, &variable.rule, &precedence_names)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use std::{collections::HashMap, mem};
|
||||
use std::mem;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::ExtractedSyntaxGrammar;
|
||||
use crate::{
|
||||
|
|
@ -11,7 +13,7 @@ struct Expander {
|
|||
repeat_count_in_variable: usize,
|
||||
preceding_symbol_count: usize,
|
||||
auxiliary_variables: Vec<Variable>,
|
||||
existing_repeats: HashMap<Rule, Symbol>,
|
||||
existing_repeats: FxHashMap<Rule, Symbol>,
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
|
|
@ -106,7 +108,7 @@ pub(super) fn expand_repeats(mut grammar: ExtractedSyntaxGrammar) -> ExtractedSy
|
|||
repeat_count_in_variable: 0,
|
||||
preceding_symbol_count: grammar.variables.len(),
|
||||
auxiliary_variables: Vec::new(),
|
||||
existing_repeats: HashMap::new(),
|
||||
existing_repeats: FxHashMap::default(),
|
||||
};
|
||||
|
||||
for (i, variable) in grammar.variables.iter_mut().enumerate() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
|
@ -89,7 +89,7 @@ pub(super) fn extract_tokens(
|
|||
// will need to have their indices decremented.
|
||||
let mut variables = Vec::with_capacity(grammar.variables.len());
|
||||
let mut symbol_replacer = SymbolReplacer {
|
||||
replacements: HashMap::new(),
|
||||
replacements: FxHashMap::default(),
|
||||
};
|
||||
for (i, variable) in grammar.variables.into_iter().enumerate() {
|
||||
if let Rule::Symbol(Symbol {
|
||||
|
|
@ -268,7 +268,7 @@ struct TokenExtractor {
|
|||
}
|
||||
|
||||
struct SymbolReplacer {
|
||||
replacements: HashMap<usize, usize>,
|
||||
replacements: FxHashMap<usize, usize>,
|
||||
}
|
||||
|
||||
impl TokenExtractor {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
|
@ -31,7 +31,7 @@ unless they are used only as the grammar's start rule.
|
|||
|
||||
struct RuleFlattener {
|
||||
production: Production,
|
||||
reserved_word_set_ids: HashMap<String, ReservedWordSetId>,
|
||||
reserved_word_set_ids: FxHashMap<String, ReservedWordSetId>,
|
||||
precedence_stack: Vec<Precedence>,
|
||||
associativity_stack: Vec<Associativity>,
|
||||
reserved_word_stack: Vec<ReservedWordSetId>,
|
||||
|
|
@ -40,7 +40,7 @@ struct RuleFlattener {
|
|||
}
|
||||
|
||||
impl RuleFlattener {
|
||||
const fn new(reserved_word_set_ids: HashMap<String, ReservedWordSetId>) -> Self {
|
||||
const fn new(reserved_word_set_ids: FxHashMap<String, ReservedWordSetId>) -> Self {
|
||||
Self {
|
||||
production: Production {
|
||||
steps: Vec::new(),
|
||||
|
|
@ -248,7 +248,7 @@ fn symbol_is_used(variables: &[SyntaxVariable], symbol: Symbol) -> bool {
|
|||
pub(super) fn flatten_grammar(
|
||||
grammar: ExtractedSyntaxGrammar,
|
||||
) -> FlattenGrammarResult<SyntaxGrammar> {
|
||||
let mut reserved_word_set_ids_by_name = HashMap::new();
|
||||
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));
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_grammar() {
|
||||
let mut flattener = RuleFlattener::new(HashMap::default());
|
||||
let mut flattener = RuleFlattener::new(FxHashMap::default());
|
||||
let result = flattener
|
||||
.flatten_variable(Variable {
|
||||
name: "test".to_string(),
|
||||
|
|
@ -368,7 +368,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_grammar_with_maximum_dynamic_precedence() {
|
||||
let mut flattener = RuleFlattener::new(HashMap::default());
|
||||
let mut flattener = RuleFlattener::new(FxHashMap::default());
|
||||
let result = flattener
|
||||
.flatten_variable(Variable {
|
||||
name: "test".to_string(),
|
||||
|
|
@ -424,7 +424,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_grammar_with_final_precedence() {
|
||||
let mut flattener = RuleFlattener::new(HashMap::default());
|
||||
let mut flattener = RuleFlattener::new(FxHashMap::default());
|
||||
let result = flattener
|
||||
.flatten_variable(Variable {
|
||||
name: "test".to_string(),
|
||||
|
|
@ -474,7 +474,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_flatten_grammar_with_field_names() {
|
||||
let mut flattener = RuleFlattener::new(HashMap::default());
|
||||
let mut flattener = RuleFlattener::new(FxHashMap::default());
|
||||
let result = flattener
|
||||
.flatten_variable(Variable {
|
||||
name: "test".to_string(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
|
@ -19,7 +19,7 @@ struct ProductionStepId {
|
|||
}
|
||||
|
||||
struct InlinedProductionMapBuilder {
|
||||
production_indices_by_step_id: HashMap<ProductionStepId, Vec<usize>>,
|
||||
production_indices_by_step_id: FxHashMap<ProductionStepId, Vec<usize>>,
|
||||
productions: Vec<Production>,
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +227,7 @@ pub(super) fn process_inlines(
|
|||
|
||||
Ok(InlinedProductionMapBuilder {
|
||||
productions: Vec::new(),
|
||||
production_indices_by_step_id: HashMap::new(),
|
||||
production_indices_by_step_id: FxHashMap::default(),
|
||||
}
|
||||
.build(grammar))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
|
@ -9,6 +8,7 @@ use rquickjs::{
|
|||
Context, Ctx, Function, Module, Object, Runtime, Type, Value,
|
||||
loader::{FileResolver, ScriptLoader},
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::{IoError, JSError, JSResult};
|
||||
|
||||
|
|
@ -49,8 +49,8 @@ fn format_js_exception(v: Value) -> JSError {
|
|||
}
|
||||
}
|
||||
|
||||
static FILE_CACHE: LazyLock<Mutex<HashMap<String, String>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
static FILE_CACHE: LazyLock<Mutex<FxHashMap<String, String>>> =
|
||||
LazyLock::new(|| Mutex::new(FxHashMap::default()));
|
||||
|
||||
#[rquickjs::function]
|
||||
fn load_file(path: String) -> rquickjs::Result<String> {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::{
|
||||
cmp,
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Write,
|
||||
mem::swap,
|
||||
};
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::LANGUAGE_VERSION;
|
||||
use indoc::indoc;
|
||||
use serde::Serialize;
|
||||
|
|
@ -90,11 +92,11 @@ struct Generator {
|
|||
syntax_grammar: SyntaxGrammar,
|
||||
lexical_grammar: LexicalGrammar,
|
||||
default_aliases: AliasMap,
|
||||
symbol_order: HashMap<Symbol, usize>,
|
||||
symbol_ids: HashMap<Symbol, String>,
|
||||
alias_ids: HashMap<Alias, String>,
|
||||
symbol_order: FxHashMap<Symbol, usize>,
|
||||
symbol_ids: FxHashMap<Symbol, String>,
|
||||
alias_ids: FxHashMap<Alias, String>,
|
||||
unique_aliases: Vec<Alias>,
|
||||
symbol_map: HashMap<Symbol, Symbol>,
|
||||
symbol_map: FxHashMap<Symbol, Symbol>,
|
||||
reserved_word_sets: Vec<TokenSet>,
|
||||
reserved_word_set_ids_by_parse_state: Vec<usize>,
|
||||
field_names: Vec<String>,
|
||||
|
|
@ -187,7 +189,7 @@ impl Generator {
|
|||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
let mut symbol_identifiers = HashSet::new();
|
||||
let mut symbol_identifiers = FxHashSet::default();
|
||||
for i in 0..self.parse_table.symbols.len() {
|
||||
self.assign_symbol_id(self.parse_table.symbols[i], &mut symbol_identifiers);
|
||||
}
|
||||
|
|
@ -196,7 +198,7 @@ impl Generator {
|
|||
self.symbol_ids[&Symbol::end()].clone(),
|
||||
);
|
||||
|
||||
self.symbol_map = HashMap::new();
|
||||
self.symbol_map = FxHashMap::default();
|
||||
|
||||
for symbol in &self.parse_table.symbols {
|
||||
let mut mapping = symbol;
|
||||
|
|
@ -610,7 +612,7 @@ impl Generator {
|
|||
}
|
||||
|
||||
fn add_non_terminal_alias_map(&mut self) {
|
||||
let mut alias_ids_by_symbol = HashMap::new();
|
||||
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 {
|
||||
|
|
@ -666,7 +668,7 @@ impl Generator {
|
|||
"static const TSStateId ts_primary_state_ids[STATE_COUNT] = {{"
|
||||
);
|
||||
indent!(self);
|
||||
let mut first_state_for_each_core_id = HashMap::new();
|
||||
let mut first_state_for_each_core_id = FxHashMap::default();
|
||||
for (idx, state) in self.parse_table.states.iter().enumerate() {
|
||||
let primary_state = first_state_for_each_core_id
|
||||
.entry(state.core_id)
|
||||
|
|
@ -1287,7 +1289,7 @@ impl Generator {
|
|||
}
|
||||
|
||||
fn add_parse_table(&mut self) -> RenderResult<()> {
|
||||
let mut parse_table_entries = HashMap::new();
|
||||
let mut parse_table_entries = FxHashMap::default();
|
||||
let mut next_parse_action_list_index = 0;
|
||||
|
||||
// Parse action lists zero is for the default value, when a symbol is not valid.
|
||||
|
|
@ -1368,7 +1370,7 @@ impl Generator {
|
|||
.len()
|
||||
.saturating_sub(self.large_state_count),
|
||||
);
|
||||
let mut symbols_by_value = HashMap::<(usize, SymbolType), Vec<Symbol>>::new();
|
||||
let mut symbols_by_value = FxHashMap::<(usize, SymbolType), Vec<Symbol>>::default();
|
||||
for state in self.parse_table.states.iter().skip(self.large_state_count) {
|
||||
small_state_indices.push(next_table_index);
|
||||
symbols_by_value.clear();
|
||||
|
|
@ -1689,7 +1691,7 @@ impl Generator {
|
|||
|
||||
fn get_parse_action_list_id(
|
||||
entry: &ParseTableEntry,
|
||||
parse_table_entries: &mut HashMap<ParseTableEntry, usize>,
|
||||
parse_table_entries: &mut FxHashMap<ParseTableEntry, usize>,
|
||||
next_parse_action_list_index: &mut usize,
|
||||
) -> usize {
|
||||
if let Some(&index) = parse_table_entries.get(entry) {
|
||||
|
|
@ -1724,7 +1726,7 @@ impl Generator {
|
|||
)
|
||||
}
|
||||
|
||||
fn assign_symbol_id(&mut self, symbol: Symbol, used_identifiers: &mut HashSet<String>) {
|
||||
fn assign_symbol_id(&mut self, symbol: Symbol, used_identifiers: &mut FxHashSet<String>) {
|
||||
let mut id;
|
||||
if symbol == Symbol::end() {
|
||||
id = "ts_builtin_sym_end".to_string();
|
||||
|
|
|
|||
|
|
@ -362,6 +362,12 @@ impl TokenSet {
|
|||
self.terminal_bits.get(index).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Raw u64 word slice backing the terminal bitset.
|
||||
#[inline]
|
||||
pub fn terminal_bits_words(&self) -> &[u64] {
|
||||
self.terminal_bits.as_slice()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, other: Symbol) {
|
||||
let vec = match other.kind {
|
||||
SymbolType::NonTerminal => panic!("Cannot store non-terminals in a TokenSet"),
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ pub enum ParseAction {
|
|||
Recover,
|
||||
Reduce {
|
||||
symbol: Symbol,
|
||||
child_count: usize,
|
||||
child_count: u16,
|
||||
dynamic_precedence: i32,
|
||||
production_id: ProductionInfoId,
|
||||
production_id: u16,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue