Skip to main content

wagon_gll/
label.rs

1use regex_automata::dfa::Automaton;
2use regex_automata::dfa::dense::DFA;
3
4use crate::Hash;
5use crate::Hasher;
6use crate::GLLImplementationError;
7use crate::ImplementationResult;
8use crate::GLLError;
9use crate::from_utf8;
10use crate::GLLBlockLabel;
11use crate::Terminal;
12use crate::GLLResult;
13use crate::HashSet;
14use crate::Rc;
15use crate::Value;
16use std::fmt::Debug;
17
18use crate::GLLState;
19
20/// The main trait all elements in a GLL grammar should implement.
21///
22/// Every single element, both non-terminals and terminals are defined as a "Label". 
23/// This trait defines methods that those labels should implement. For [`Terminal`]s, it is
24/// already implemented, but non-terminals should be implemented specifically based on how they should
25/// operate.
26pub trait Label<'a>: Debug {
27	/// Is this Label epsilon?
28	///
29	/// Defaults to `false`.
30	fn is_eps(&self) -> bool {
31		false
32	}
33	/// Returns the first-follow set of the label.
34	///
35	/// Encoded as a vector of tuples. The outer vector represents all the alternatives of this label, 
36	/// while the inner vector represents all other labels that we need to check the first set from (since they may be epsilon or not).
37	/// 
38	/// After we have exhausted the inner vector, if the optional [`Terminal`] is not `None` then we can stop after checking whether the current
39	/// character is this terminal.
40	///
41	/// # Example
42	/// Assume we have the following rules: 
43	/// ```ignore
44	/// S -> A B 'b' | 'b';
45	/// A -> 'a';
46	/// B -> 'b'
47	/// ```
48	/// The result of this method for S should then be:
49	///
50	/// `[([A, B], 'b'), ([], 'b')]`
51	///
52	/// # Why calculate this at runtime?
53	/// The possible existence of weights makes it so that the first-follow set of any non-terminal can change at any point. 
54	/// As such, we must calculate the set at runtime, depending on the context. This is the main cause of inefficiency in this library.
55	///
56	/// There is a possibility that this will change in the future, as the meaning of the first set in the context of WAGs is re-evaluated.
57	/// But for now, it remains in it's current functional state.
58	///
59	/// # Errors
60	/// Should return an error if we fail to retrieve the label somewhere.
61	fn first_set(&self, state: &GLLState<'a>) -> ImplementationResult<'a, Vec<(Vec<GLLBlockLabel<'a>>, Option<Terminal<'a>>)>>;
62	/// Any code to run when encountering this label.
63	///
64	/// This is called by `GLLState::goto` and is used to make the `goto` from the original paper work.
65	///
66	/// # Errors
67	/// Should return a `GLLParseError` if an error occurs during the parsing or evaluation of attributes.
68	fn code(&self, state: &mut GLLState<'a>) -> GLLResult<'a, ()>;
69	/// Check if the next token in the current state is accepted by this label's first-follow set.
70	///
71	/// # Errors
72	/// Returns an error if any label in the first set can't be found.
73	fn first(&self, state: &mut GLLState<'a>) -> GLLResult<'a, bool> {
74		self._first(state, &mut HashSet::default())
75	}
76	/// Internal method for [`Label::first`] to do the recursive step.
77	///
78	/// Rust does not allow me to make this private, so it will be public.
79	///
80	/// # Errors
81	/// Returns an error if any label in the first set can't be found.
82	fn _first(&self, state: &mut GLLState<'a>, seen: &mut HashSet<Rc<str>>) -> GLLResult<'a, bool> {
83		let fst = self.first_set(state)?;
84		for (alt, fin) in fst {
85			let mut check_fin = true;
86			for sub in alt {
87				let uuid = sub.uuid();
88				if seen.contains(uuid) {
89					continue
90				}
91				seen.insert(uuid.into());
92				if sub._first(state, seen)? {
93					return Ok(true);
94				} else if !sub.is_nullable(state)? {
95					check_fin = false;
96				    break;
97				}
98			}
99			if check_fin {
100				if let Some(last) = fin {
101					return Ok(last.is_empty() || state.has_next(last))
102				}
103			}
104		}
105		Ok(false)
106	}
107	/// Is this label a terminal? 
108	///
109	/// Defaults to `false`.
110	fn is_terminal(&self) -> bool {
111		false
112	}
113	/// Could this label resolve to epsilon?
114	///
115	/// # Errors
116	/// Returns an error if any label in the first set can't be found.
117	fn is_nullable(&self, state: &GLLState<'a>) -> ImplementationResult<'a, bool> {
118		self._is_nullable(state, &mut HashSet::default())
119	}
120
121	/// Internal method for [`Label::is_nullable`] to do the recursive step.
122	///
123	/// Rust does not allow me to make this private, so it will be public.
124	///
125	/// # Errors
126	/// Returns an error if any label in the first set can't be found.
127	fn _is_nullable(&self, state: &GLLState<'a>, seen: &mut HashSet<Rc<str>>) -> ImplementationResult<'a, bool> {
128		if self.is_eps() {
129			Ok(true)
130		} else {
131			let str_repr = self.uuid();
132			if !seen.contains(str_repr) {
133				seen.insert(str_repr.into());
134				let fst = self.first_set(state)?;
135				for (alt, _) in fst {
136					if let Some(sub) = alt.into_iter().next() {
137						if sub._is_nullable(state, seen)? {
138							return Ok(true)
139						}
140					}
141				}
142			}
143			Ok(false)
144		}
145	}
146
147	/// Optionally return the weight of this label.
148	///
149	/// This should either calculate the weight for this label, as denoted in the WAGon DSL, or `None`.
150	fn _weight(&self, state: &GLLState<'a>) -> Option<ImplementationResult<'a, Value<'a>>>;
151	/// Returns either the weight of this label as calculated by [`_weight`](`Label::_weight`), or `1`.
152	///
153	/// # Errors
154	/// Should return a [`GLLImplementationError::ValueError`] if something goes wrong during the evaluation of the weight.
155	fn weight(&self, state: &GLLState<'a>) -> ImplementationResult<'a, Value<'a>> {
156		self._weight(state).map_or_else(|| Ok(1.into()), |weight| weight)
157	}
158	/// A string representation of the chunk (likely a GLL block) that this label represents.
159	fn to_string(&self) -> &str;
160	/// The chunk represented by [`Label::to_string`], but split by symbol into a vector.
161	fn str_parts(&self) -> Vec<&str>;
162	/// A unique identifier for this label.
163	fn uuid(&self) -> &str;
164	/// A tuple of string representations for any associated attributes.
165	///
166	/// The first element is a vector of all the inherited or local attributes. The second elements is a vector
167	/// of all currently synthesized attributes.
168	fn attr_rep_map(&self) -> (Vec<&str>, Vec<&str>);
169}
170
171impl<'a> Label<'a> for Terminal<'a> {
172    fn is_eps(&self) -> bool {
173        self.is_empty()
174    }
175
176    fn first_set(&self, _: &GLLState<'a>) -> ImplementationResult<'a, Vec<(Vec<GLLBlockLabel<'a>>, Option<Terminal<'a>>)>> {
177        Ok(vec![(Vec::new(), Some(*self))])
178    }
179
180    fn _first(&self, state: &mut GLLState<'a>, _: &mut HashSet<Rc<str>>) -> GLLResult<'a, bool> {
181        Ok(self.is_eps() || state.has_next(self))
182    }
183
184    fn code(&self, _: &mut GLLState<'a>) -> GLLResult<'a, ()> {
185        Err(GLLError::ImplementationError(GLLImplementationError::Fatal("Attempted running the `code` method on a terminal.")))
186    }
187
188    fn is_terminal(&self) -> bool {
189        true
190    }
191
192    /// The string represented by the byte array
193    ///
194    /// # Panics
195    /// This will panic if the byte array is not a string. 
196    /// This should never happen
197    /// and also is an issue specifically only for [`Terminal`] (any other [`Label`] can not crash here.)
198    /// As such, it was decided that this function will not return a proper [`GLLResult`].
199    fn to_string(&self) -> &str {
200    	#[allow(clippy::expect_used)]
201        from_utf8(self).expect("Terminal was non-utf8")
202    }
203
204    fn _is_nullable(&self, _: &GLLState<'a>, _: &mut HashSet<Rc<str>>) -> ImplementationResult<'a, bool> {
205        Ok(self.is_eps())
206    }
207
208    fn uuid(&self) -> &str {
209        self.to_string()
210    }
211
212	fn str_parts(&self) -> Vec<&str> { 
213		vec![self.to_string()]
214	}
215
216	fn attr_rep_map(&self) -> (Vec<&str>, Vec<&str>) { 
217		(Vec::new(), Vec::new())
218	}
219
220	fn _weight(&self, _state: &GLLState<'a>) -> Option<ImplementationResult<'a, Value<'a>>> {
221		Some(Err(GLLImplementationError::Fatal("Attempted running the `_weight` method on a terminal.")))
222	}
223}
224
225#[derive(Debug)]
226/// A special type of Terminal which is a regex recognizer.
227///
228/// Implements label so that regex machines can be used. The string representation/uuid of the machine is its regex pattern.
229pub struct RegexTerminal<'a> {
230	pattern: &'a str,
231	/// The regex automaton this terminal represents.
232	pub automaton: DFA<&'a [u32]>
233}
234
235impl<'a> RegexTerminal<'a> {
236	/// Construct a new `RegexTerminal`.
237	#[must_use]
238	pub const fn new(pattern: &'a str, automaton: DFA<&'a [u32]>) -> Self {
239		Self { pattern, automaton }
240	}
241}
242
243impl<'a> Label<'a> for RegexTerminal<'a> {
244    fn first_set(&self, _: &GLLState<'a>) -> ImplementationResult<'a, Vec<(Vec<GLLBlockLabel<'a>>, Option<Terminal<'a>>)>> {
245        Err(GLLImplementationError::Fatal("Attempted running the `first_set` method on a regex."))
246    }
247
248    fn code(&self, _: &mut GLLState<'a>) -> GLLResult<'a, ()> {
249        Err(GLLError::ImplementationError(GLLImplementationError::Fatal("Attempted running the `code` method on a regex.")))
250    }
251
252    fn _weight(&self, _: &GLLState<'a>) -> Option<ImplementationResult<'a, Value<'a>>> {
253        Some(Err(GLLImplementationError::Fatal("Attempted running the `weight` method on a regex.")))
254    }
255
256    fn to_string(&self) -> &str {
257        self.pattern
258    }
259
260    fn str_parts(&self) -> Vec<&str> {
261        vec![self.to_string()]
262    }
263
264    fn uuid(&self) -> &str {
265        self.to_string()
266    }
267
268    fn attr_rep_map(&self) -> (Vec<&str>, Vec<&str>) {
269        (Vec::new(), Vec::new())
270    }
271    fn is_eps(&self) -> bool {
272	    self.automaton.pattern_len() == 0
273    }
274
275    /// A regex is sort of between a non-terminal and a terminal. They way `first` is used, we want
276    /// it to return `true` if some terminal is parsable from this point. In the case of a regex, this means the pattern is accepting.
277    fn _first(&self, state: &mut GLLState<'a>, _: &mut HashSet<Rc<str>>) -> GLLResult<'a, bool> {
278	    state.has_regex(self.pattern)
279    }
280
281    fn is_terminal(&self) -> bool {
282	    true
283    }
284
285    fn _is_nullable(&self, _: &GLLState<'a>, _: &mut HashSet<Rc<str>>) -> ImplementationResult<'a, bool> {
286	    Ok(self.automaton.has_empty())
287    }
288}
289
290impl<'a> Hash for dyn Label<'a> {
291    fn hash<H: Hasher>(&self, state: &mut H) {
292        self.uuid().hash(state);
293    }
294}
295
296impl<'a> PartialEq for dyn Label<'a> {
297    fn eq(&self, other: &Self) -> bool {
298        self.uuid() == other.uuid()
299    }
300}
301
302impl<'a> Eq for dyn Label<'a>{}