use std::collections::HashMap;
pub type Alt = Vec<Symbol>;
#[derive(Debug, Clone, PartialEq)]
pub enum Symbol {
Terminal(u8),
AnyByte,
NonTerminal(usize),
}
#[derive(Debug, Clone)]
pub struct Rule {
pub name: String,
pub alts: Vec<Alt>,
}
#[derive(Debug, Clone)]
pub struct CompiledGrammar {
pub rules: Vec<Rule>,
}
impl CompiledGrammar {
pub fn num_rules(&self) -> usize {
self.rules.len()
}
pub fn root(&self) -> &Rule {
&self.rules[0]
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StackFrame {
pub rule_id: usize,
pub alt_idx: usize,
pub sym_pos: usize,
}
#[derive(Debug, Clone)]
pub struct GrammarState {
pub stack: Vec<StackFrame>,
pub partial_token_bytes: Vec<u8>,
pub complete: bool,
}
impl GrammarState {
pub fn initial() -> Self {
Self {
stack: vec![StackFrame {
rule_id: 0,
alt_idx: 0,
sym_pos: 0,
}],
partial_token_bytes: Vec::new(),
complete: false,
}
}
pub fn is_complete(&self) -> bool {
self.complete
}
pub fn can_accept_more(&self) -> bool {
!self.complete || !self.stack.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StepResult {
Accepted,
Rejected,
}
pub fn advance_byte(state: &mut GrammarState, grammar: &CompiledGrammar, b: u8) -> StepResult {
let snapshot = state.stack.clone();
if try_advance_byte(state, grammar, b) {
state.partial_token_bytes.push(b);
state.complete = is_accepting(state, grammar);
StepResult::Accepted
} else {
state.stack = snapshot;
StepResult::Rejected
}
}
fn try_advance_byte(state: &mut GrammarState, grammar: &CompiledGrammar, b: u8) -> bool {
let mut stack = state.stack.clone();
if try_advance_stack(&mut stack, grammar, b) {
state.stack = stack;
return true;
}
false
}
fn try_advance_stack(stack: &mut Vec<StackFrame>, grammar: &CompiledGrammar, b: u8) -> bool {
loop {
if stack.is_empty() {
return false;
}
let frame_idx = stack.len() - 1;
let frame = &stack[frame_idx];
let rule = &grammar.rules[frame.rule_id];
if rule.alts.is_empty() {
return try_next_alt(stack, grammar, b, frame_idx);
}
let alt = &rule.alts[frame.alt_idx];
if frame.sym_pos >= alt.len() {
stack.pop();
if let Some(parent) = stack.last_mut() {
parent.sym_pos += 1;
}
continue;
}
let sym = &alt[frame.sym_pos].clone();
match sym {
Symbol::Terminal(t) => {
if *t == b {
stack[frame_idx].sym_pos += 1;
collapse_exhausted(stack, grammar);
return true;
} else {
if frame.sym_pos == 0 {
return try_next_alt(stack, grammar, b, frame_idx);
} else {
if frame_idx == 0 {
return false;
}
stack.truncate(frame_idx);
let parent_idx = stack.len() - 1;
return try_next_alt(stack, grammar, b, parent_idx);
}
}
}
Symbol::AnyByte => {
stack[frame_idx].sym_pos += 1;
collapse_exhausted(stack, grammar);
return true;
}
Symbol::NonTerminal(rule_id) => {
let rid = *rule_id;
if grammar.rules[rid].alts.is_empty() {
stack[frame_idx].sym_pos += 1;
continue;
}
stack.push(StackFrame {
rule_id: rid,
alt_idx: 0,
sym_pos: 0,
});
}
}
}
}
fn try_next_alt(
stack: &mut Vec<StackFrame>,
grammar: &CompiledGrammar,
b: u8,
frame_idx: usize,
) -> bool {
let rule_id = stack[frame_idx].rule_id;
let next_alt = stack[frame_idx].alt_idx + 1;
let num_alts = grammar.rules[rule_id].alts.len();
if next_alt >= num_alts {
if frame_idx == 0 {
return false;
}
stack.truncate(frame_idx);
let parent_idx = stack.len() - 1;
return try_next_alt(stack, grammar, b, parent_idx);
}
stack[frame_idx].alt_idx = next_alt;
stack[frame_idx].sym_pos = 0;
stack.truncate(frame_idx + 1);
try_advance_stack(stack, grammar, b)
}
fn collapse_exhausted(stack: &mut Vec<StackFrame>, grammar: &CompiledGrammar) {
loop {
match stack.last() {
None => break,
Some(frame) => {
let rule = &grammar.rules[frame.rule_id];
if rule.alts.is_empty() {
stack.pop();
if let Some(parent) = stack.last_mut() {
parent.sym_pos += 1;
}
continue;
}
let alt = &rule.alts[frame.alt_idx];
if frame.sym_pos < alt.len() {
break;
}
stack.pop();
if let Some(parent) = stack.last_mut() {
parent.sym_pos += 1;
}
}
}
}
}
fn is_accepting(state: &GrammarState, grammar: &CompiledGrammar) -> bool {
let n = state.stack.len();
for (i, frame) in state.stack.iter().enumerate() {
if frame.rule_id >= grammar.rules.len() {
return false;
}
let rule = &grammar.rules[frame.rule_id];
if rule.alts.is_empty() {
continue;
}
if frame.alt_idx >= rule.alts.len() {
return false;
}
let alt = &rule.alts[frame.alt_idx];
let check_from = if i == n - 1 {
frame.sym_pos
} else {
frame.sym_pos + 1
};
if !remaining_is_nullable(
grammar,
alt,
check_from,
&mut std::collections::HashSet::new(),
) {
return false;
}
}
true
}
fn remaining_is_nullable(
grammar: &CompiledGrammar,
alt: &[Symbol],
pos: usize,
visited: &mut std::collections::HashSet<usize>,
) -> bool {
for sym in &alt[pos..] {
match sym {
Symbol::Terminal(_) | Symbol::AnyByte => return false,
Symbol::NonTerminal(rid) => {
if !visited.insert(*rid) {
return false;
}
if !rule_is_nullable(grammar, *rid, visited) {
visited.remove(rid);
return false;
}
visited.remove(rid);
}
}
}
true
}
fn rule_is_nullable(
grammar: &CompiledGrammar,
rule_id: usize,
visited: &mut std::collections::HashSet<usize>,
) -> bool {
if rule_id >= grammar.rules.len() {
return false;
}
for alt in &grammar.rules[rule_id].alts {
if remaining_is_nullable(grammar, alt, 0, visited) {
return true;
}
}
false
}
#[derive(Debug, Clone, PartialEq)]
pub enum SimResult {
Accept,
ContextDependent,
Reject,
}
pub fn simulate_token(
start: &GrammarState,
grammar: &CompiledGrammar,
token: &[u8],
) -> (SimResult, GrammarState) {
let mut state = start.clone();
for (i, &b) in token.iter().enumerate() {
match advance_byte(&mut state, grammar, b) {
StepResult::Accepted => {}
StepResult::Rejected => {
if i > 0 {
return (SimResult::ContextDependent, state);
}
return (SimResult::Reject, state);
}
}
}
(SimResult::Accept, state)
}
pub struct GrammarBuilder {
rules: Vec<Rule>,
name_to_id: HashMap<String, usize>,
}
impl GrammarBuilder {
pub fn new() -> Self {
Self {
rules: Vec::new(),
name_to_id: HashMap::new(),
}
}
pub fn reserve(&mut self, name: &str) -> usize {
if let Some(&id) = self.name_to_id.get(name) {
return id;
}
let id = self.rules.len();
self.rules.push(Rule {
name: name.to_string(),
alts: Vec::new(),
});
self.name_to_id.insert(name.to_string(), id);
id
}
pub fn set_alts(&mut self, id: usize, alts: Vec<Alt>) {
self.rules[id].alts = alts;
}
pub fn add_rule(&mut self, name: &str, alts: Vec<Alt>) -> usize {
let id = self.reserve(name);
self.set_alts(id, alts);
id
}
pub fn rule_id(&self, name: &str) -> Option<usize> {
self.name_to_id.get(name).copied()
}
pub fn build(self) -> CompiledGrammar {
CompiledGrammar { rules: self.rules }
}
}
impl Default for GrammarBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ab_grammar() -> CompiledGrammar {
let mut b = GrammarBuilder::new();
b.add_rule(
"root",
vec![vec![Symbol::Terminal(b'a'), Symbol::Terminal(b'b')]],
);
b.build()
}
fn or_grammar() -> CompiledGrammar {
let mut b = GrammarBuilder::new();
b.add_rule(
"root",
vec![vec![Symbol::Terminal(b'a')], vec![Symbol::Terminal(b'b')]],
);
b.build()
}
fn digits_grammar() -> CompiledGrammar {
let mut b = GrammarBuilder::new();
let digit_id = b.reserve("digit");
let digit_alts: Vec<Alt> = (b'0'..=b'9')
.map(|byte| vec![Symbol::Terminal(byte)])
.collect();
b.set_alts(digit_id, digit_alts);
let rest_id = b.reserve("digit_rest");
b.set_alts(
rest_id,
vec![
vec![Symbol::NonTerminal(digit_id), Symbol::NonTerminal(rest_id)],
vec![], ],
);
let root_id = b.reserve("root");
b.set_alts(
root_id,
vec![vec![
Symbol::NonTerminal(digit_id),
Symbol::NonTerminal(rest_id),
]],
);
let mut grammar = b.build();
let root_pos = grammar.rules.iter().position(|r| r.name == "root").unwrap();
grammar.rules.swap(0, root_pos);
let orig_root_id = root_pos;
let swapped_to_id = 0usize;
if orig_root_id != 0 {
for rule in &mut grammar.rules {
for alt in &mut rule.alts {
for sym in alt.iter_mut() {
if let Symbol::NonTerminal(rid) = sym {
if *rid == orig_root_id {
*rid = swapped_to_id;
} else if *rid == 0 {
*rid = orig_root_id;
}
}
}
}
}
}
grammar
}
#[test]
fn ab_grammar_accepts_ab() {
let g = ab_grammar();
let mut state = GrammarState::initial();
assert_eq!(advance_byte(&mut state, &g, b'a'), StepResult::Accepted);
assert!(!state.is_complete()); assert_eq!(advance_byte(&mut state, &g, b'b'), StepResult::Accepted);
assert!(state.is_complete());
}
#[test]
fn ab_grammar_rejects_ba() {
let g = ab_grammar();
let mut state = GrammarState::initial();
assert_eq!(advance_byte(&mut state, &g, b'b'), StepResult::Rejected);
}
#[test]
fn ab_grammar_rejects_partial_a_then_wrong() {
let g = ab_grammar();
let mut state = GrammarState::initial();
advance_byte(&mut state, &g, b'a');
assert_eq!(advance_byte(&mut state, &g, b'x'), StepResult::Rejected);
}
#[test]
fn or_grammar_accepts_a_or_b() {
let g = or_grammar();
let mut s = GrammarState::initial();
assert_eq!(advance_byte(&mut s, &g, b'a'), StepResult::Accepted);
let mut s2 = GrammarState::initial();
assert_eq!(advance_byte(&mut s2, &g, b'b'), StepResult::Accepted);
}
#[test]
fn or_grammar_rejects_c() {
let g = or_grammar();
let mut s = GrammarState::initial();
assert_eq!(advance_byte(&mut s, &g, b'c'), StepResult::Rejected);
}
#[test]
fn simulate_token_full_match() {
let g = ab_grammar();
let state = GrammarState::initial();
let (result, _) = simulate_token(&state, &g, b"ab");
assert_eq!(result, SimResult::Accept);
}
#[test]
fn simulate_token_reject() {
let g = ab_grammar();
let state = GrammarState::initial();
let (result, _) = simulate_token(&state, &g, b"ba");
assert_eq!(result, SimResult::Reject);
}
#[test]
fn simulate_token_partial_is_context_dependent() {
let g = ab_grammar();
let state = GrammarState::initial();
let (result, _) = simulate_token(&state, &g, b"ax");
assert_eq!(result, SimResult::ContextDependent);
}
#[test]
fn state_partial_bytes_recorded() {
let g = ab_grammar();
let mut state = GrammarState::initial();
advance_byte(&mut state, &g, b'a');
assert_eq!(state.partial_token_bytes, vec![b'a']);
advance_byte(&mut state, &g, b'b');
assert_eq!(state.partial_token_bytes, vec![b'a', b'b']);
}
#[test]
fn any_byte_matches_any_value() {
let mut b = GrammarBuilder::new();
b.add_rule("root", vec![vec![Symbol::AnyByte]]);
let g = b.build();
for byte in [b'a', b'z', b'0', b'\n', 0xffu8] {
let mut s = GrammarState::initial();
assert_eq!(advance_byte(&mut s, &g, byte), StepResult::Accepted);
assert!(s.is_complete());
}
}
#[test]
fn digits_grammar_accepts_single_digit() {
let g = digits_grammar();
let state = GrammarState::initial();
let (result, _) = simulate_token(&state, &g, b"5");
assert_eq!(result, SimResult::Accept);
}
#[test]
fn digits_grammar_accepts_multi_digit() {
let g = digits_grammar();
let state = GrammarState::initial();
let (result, final_state) = simulate_token(&state, &g, b"123");
assert_eq!(result, SimResult::Accept);
assert!(final_state.is_complete());
}
#[test]
fn digits_grammar_rejects_letter() {
let g = digits_grammar();
let state = GrammarState::initial();
let (result, _) = simulate_token(&state, &g, b"abc");
assert_eq!(result, SimResult::Reject);
}
#[test]
fn grammar_builder_reserve_idempotent() {
let mut builder = GrammarBuilder::new();
let id1 = builder.reserve("foo");
let id2 = builder.reserve("foo");
assert_eq!(id1, id2);
}
}