metrale_grammar/fsm/compact_with_start_end.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// CompactFsmWithStartEnd — the compact-FSM counterpart of
4// FsmWithStartEnd. Split out of `with_start_end.rs` to keep each
5// file under the 250-line cap. Port of `CompactFSMWithStartEnd`
6// from `cpp/fsm.{h,cc}`.
7
8use ahash::AHashSet;
9
10use super::compact::CompactFsm;
11use super::edge::edge_type;
12use super::with_start_end::FsmWithStartEnd;
13
14/// A compact FSM paired with start/accepting states.
15///
16/// `node_num` / `edge_num` record the size of the *logical* FSM this
17/// value represents. For a standalone FSM that equals the backing
18/// `fsm`'s own counts; for a per-rule view spliced into a shared
19/// `complete_fsm`, they record the sub-FSM's size — not the whole
20/// completed FSM. Distinguishing the two is the upstream #600 fix
21/// (`CompactFSMWithStartEndWithSize`, commit 58494db).
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct CompactFsmWithStartEnd {
24 pub(crate) fsm: CompactFsm,
25 pub(crate) start: usize,
26 pub(crate) ends: Vec<bool>,
27 pub(crate) is_dfa: bool,
28 node_num: usize,
29 edge_num: usize,
30}
31
32impl CompactFsmWithStartEnd {
33 /// Build a standalone compact FSM; `node_num`/`edge_num` are the
34 /// FSM's own counts.
35 pub fn new(fsm: CompactFsm, start: usize, ends: Vec<bool>) -> Self {
36 let node_num = fsm.num_states();
37 let edge_num = fsm.num_edges();
38 Self {
39 fsm,
40 start,
41 ends,
42 is_dfa: false,
43 node_num,
44 edge_num,
45 }
46 }
47
48 /// Build a per-rule view onto a shared `complete_fsm`. `node_num`
49 /// and `edge_num` are the spliced-in sub-FSM's counts, *not* the
50 /// backing FSM's totals (upstream commit 58494db, #600).
51 pub fn new_view(
52 fsm: CompactFsm,
53 start: usize,
54 ends: Vec<bool>,
55 node_num: usize,
56 edge_num: usize,
57 ) -> Self {
58 Self {
59 fsm,
60 start,
61 ends,
62 is_dfa: false,
63 node_num,
64 edge_num,
65 }
66 }
67
68 /// The underlying compact FSM.
69 pub fn fsm(&self) -> &CompactFsm {
70 &self.fsm
71 }
72
73 /// Start state id.
74 pub fn start(&self) -> usize {
75 self.start
76 }
77
78 /// Accepting-state bitmap.
79 pub fn ends(&self) -> &[bool] {
80 &self.ends
81 }
82
83 /// True if `state` is accepting.
84 pub fn is_end_state(&self, state: usize) -> bool {
85 self.ends[state]
86 }
87
88 /// Number of states in the logical (sub-)FSM. For a per-rule view
89 /// this is the spliced-in sub-FSM's count, not the backing
90 /// `complete_fsm`'s total (upstream commit 58494db, #600).
91 pub fn num_states(&self) -> usize {
92 self.node_num
93 }
94
95 /// Number of edges in the logical (sub-)FSM. For a per-rule view
96 /// this is the spliced-in sub-FSM's count, not the backing
97 /// `complete_fsm`'s total (upstream commit 58494db, #600).
98 pub fn num_edges(&self) -> usize {
99 self.edge_num
100 }
101
102 /// Number of states in the backing FSM storage — for a per-rule
103 /// view this is the whole shared `complete_fsm`. Use this (not
104 /// `num_states`) when indexing into the backing edge table.
105 pub fn backing_num_states(&self) -> usize {
106 self.fsm.num_states()
107 }
108
109 /// True if the FSM accepts the byte string.
110 pub fn accept_string(&self, bytes: &[u8]) -> bool {
111 let mut start_states: AHashSet<i32> = AHashSet::from_iter([self.start as i32]);
112 self.fsm.epsilon_closure(&mut start_states);
113 let mut result_states = AHashSet::new();
114 for &byte in bytes {
115 result_states.clear();
116 self.fsm.advance(
117 &start_states,
118 byte as i32,
119 &mut result_states,
120 edge_type::CHAR_RANGE,
121 false,
122 );
123 if result_states.is_empty() {
124 return false;
125 }
126 std::mem::swap(&mut start_states, &mut result_states);
127 }
128 start_states.iter().any(|&s| self.ends[s as usize])
129 }
130
131 /// All states reachable from the start state.
132 pub fn reachable_states(&self, result: &mut AHashSet<i32>) {
133 self.fsm.reachable_states(&[self.start as i32], result);
134 }
135
136 /// Expand into the mutable form.
137 pub fn to_fsm(&self) -> FsmWithStartEnd {
138 FsmWithStartEnd::new(
139 self.fsm.to_fsm(),
140 self.start,
141 self.ends.clone(),
142 self.is_dfa,
143 )
144 }
145
146 /// Approximate heap footprint.
147 pub fn memory_size(&self) -> usize {
148 self.fsm.memory_size() + self.ends.len()
149 }
150}