1use crate::{
4 Explicit, ExplicitBuildError, ExplicitBuilder, FxHashMap, Interner, Signature, SignatureError,
5 StateId,
6 alto_ast::{AstFta, AstState, LexError, Tok, lex},
7 alto_grammar,
8};
9use lalrpop_util::ParseError;
10use thiserror::Error;
11
12#[derive(Clone, Debug)]
18pub struct ExplicitWithSignature {
19 pub automaton: Explicit,
21 pub states: Interner<String>,
23 pub signature: Signature,
25}
26
27#[deprecated(note = "renamed to ExplicitWithSignature")]
29pub type ParsedTreeAutomaton = ExplicitWithSignature;
30
31#[derive(Clone, Debug, Error, PartialEq)]
33pub enum AltoParseError {
34 #[error("expected {expected} at byte {offset}")]
36 Expected {
37 expected: &'static str,
39 offset: usize,
41 },
42 #[error("unexpected token {found:?} at byte {offset}; expected {expected}")]
44 Unexpected {
45 expected: &'static str,
47 found: String,
49 offset: usize,
51 },
52 #[error("unterminated quoted name starting at byte {offset}")]
54 UnterminatedQuote {
55 offset: usize,
57 },
58 #[error("unterminated block comment starting at byte {offset}")]
60 UnterminatedComment {
61 offset: usize,
63 },
64 #[error("invalid variable at byte {offset}")]
66 InvalidVariable {
67 offset: usize,
69 },
70 #[error("invalid weight {text:?} at byte {offset}")]
72 InvalidWeight {
73 text: String,
75 offset: usize,
77 },
78 #[error("symbol {symbol:?} used with arities {first} and {second}")]
80 ArityMismatch {
81 symbol: String,
83 first: usize,
85 second: usize,
87 },
88 #[error("{0}")]
90 Syntax(String),
91 #[error("{0}")]
93 Automaton(#[from] ExplicitBuildError),
94}
95
96impl From<SignatureError> for AltoParseError {
97 fn from(value: SignatureError) -> Self {
98 match value {
99 SignatureError::ArityMismatch {
100 symbol,
101 first,
102 second,
103 } => Self::ArityMismatch {
104 symbol,
105 first,
106 second,
107 },
108 }
109 }
110}
111
112impl From<LexError> for AltoParseError {
113 fn from(value: LexError) -> Self {
114 match value {
115 LexError::UnterminatedQuote { offset } => Self::UnterminatedQuote { offset },
116 LexError::UnterminatedComment { offset } => Self::UnterminatedComment { offset },
117 LexError::InvalidVariable { offset } => Self::InvalidVariable { offset },
118 LexError::Unexpected { found, offset } => Self::Unexpected {
119 expected: "Alto token",
120 found: found.to_string(),
121 offset,
122 },
123 }
124 }
125}
126
127pub fn parse_alto(input: &str) -> Result<ExplicitWithSignature, AltoParseError> {
138 let mut signature = Signature::new();
139 parse_alto_with_signature(input, &mut signature)
140}
141
142pub fn parse_alto_with_signature(
149 input: &str,
150 signature: &mut Signature,
151) -> Result<ExplicitWithSignature, AltoParseError> {
152 let tokens = lex(input)?;
153 let ast = alto_grammar::FtaParser::new()
154 .parse(tokens.into_iter().map(Ok))
155 .map_err(parse_error_to_alto)?;
156 build_alto(ast, signature)
157}
158
159fn build_alto(
160 ast: AstFta,
161 signature: &mut Signature,
162) -> Result<ExplicitWithSignature, AltoParseError> {
163 let mut builder = ExplicitBuilder::new();
164 let mut states = Interner::new();
165 let mut state_ids = FxHashMap::default();
166
167 for rule in ast.rules {
168 let parent = state_id(&mut builder, &mut states, &mut state_ids, &rule.parent);
169 if rule.parent.is_final {
170 builder.add_accepting(parent);
171 }
172 let child_ids: Vec<_> = rule
173 .children
174 .iter()
175 .map(|child| {
176 let id = state_id(&mut builder, &mut states, &mut state_ids, child);
177 if child.is_final {
178 builder.add_accepting(id);
179 }
180 id
181 })
182 .collect();
183 let symbol = signature.intern(rule.symbol.clone(), child_ids.len())?;
184 builder.add_weighted_rule(symbol, child_ids, parent, rule.weight.unwrap_or(1.0));
185 }
186
187 Ok(ExplicitWithSignature {
188 automaton: builder.try_build()?,
189 states,
190 signature: signature.clone(),
191 })
192}
193
194fn state_id(
195 builder: &mut ExplicitBuilder,
196 states: &mut Interner<String>,
197 state_ids: &mut FxHashMap<String, StateId>,
198 state: &AstState,
199) -> StateId {
200 if let Some(&id) = state_ids.get(&state.name) {
201 return id;
202 }
203 let id = builder.new_state();
204 let interned = states.intern(state.name.clone());
205 debug_assert_eq!(id, interned);
206 state_ids.insert(state.name.clone(), id);
207 id
208}
209
210fn parse_error_to_alto(err: ParseError<usize, Tok, String>) -> AltoParseError {
211 match err {
212 ParseError::InvalidToken { location } => AltoParseError::Unexpected {
213 expected: "Alto token",
214 found: "<invalid>".to_owned(),
215 offset: location,
216 },
217 ParseError::UnrecognizedEof { location, expected } => AltoParseError::Expected {
218 expected: leak_expected(expected),
219 offset: location,
220 },
221 ParseError::UnrecognizedToken { token, expected } => AltoParseError::Unexpected {
222 expected: leak_expected(expected),
223 found: token_display(&token.1),
224 offset: token.0,
225 },
226 ParseError::ExtraToken { token } => AltoParseError::Unexpected {
227 expected: "end of input",
228 found: token_display(&token.1),
229 offset: token.0,
230 },
231 ParseError::User { error } => AltoParseError::Syntax(error),
232 }
233}
234
235fn leak_expected(expected: Vec<String>) -> &'static str {
236 if expected.is_empty() {
237 "valid syntax"
238 } else {
239 Box::leak(expected.join(", ").into_boxed_str())
240 }
241}
242
243fn token_display(kind: &Tok) -> String {
244 match kind {
245 Tok::Name(name) => format!("name {name:?}"),
246 Tok::Number(number) => format!("number {number:?}"),
247 Tok::Variable(variable) => format!("variable ?{variable}"),
248 Tok::Interpretation => "interpretation".to_owned(),
249 Tok::Feature => "feature".to_owned(),
250 Tok::Arrow => "->".to_owned(),
251 Tok::LParen => "(".to_owned(),
252 Tok::RParen => ")".to_owned(),
253 Tok::LBracket => "[".to_owned(),
254 Tok::RBracket => "]".to_owned(),
255 Tok::Comma => ",".to_owned(),
256 Tok::Colon => ":".to_owned(),
257 Tok::Fin => "!".to_owned(),
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::{BottomUpTa, DetBottomUpTa};
265
266 #[test]
267 fn parses_wiki_example_as_bottom_up() {
268 let parsed = parse_alto(
269 "
270 S! -> f(A,A) [1.0]
271 A -> g(A) [0.5]
272 A -> a [0.5]
273 ",
274 )
275 .unwrap();
276 let s = parsed.states.get(&"S".to_owned()).unwrap();
277 let a_state = parsed.states.get(&"A".to_owned()).unwrap();
278 let f = parsed.signature.get("f").unwrap();
279 let leaf = parsed.signature.get("a").unwrap();
280
281 let mut out = Vec::new();
282 parsed.automaton.step(leaf, &[], &mut |q| out.push(q));
283 assert_eq!(out, vec![a_state]);
284 assert_eq!(parsed.automaton.step_det(f, &[a_state, a_state]), Some(s));
285 assert!(parsed.automaton.is_accepting(&s));
286 let g = parsed.signature.get("g").unwrap();
287 let g_rule = parsed
288 .automaton
289 .rules()
290 .find(|rule| rule.symbol == g)
291 .unwrap();
292 assert_eq!(g_rule.weight, 0.5);
293 }
294
295 #[test]
296 fn accepts_whitespace_separated_rules_and_comments() {
297 let parsed =
298 parse_alto("q1 -> a /* ignored -> text */ q2! -> f(q1, // line\n q1)").unwrap();
299 let q1 = parsed.states.get(&"q1".to_owned()).unwrap();
300 let q2 = parsed.states.get(&"q2".to_owned()).unwrap();
301 let f = parsed.signature.get("f").unwrap();
302 assert_eq!(parsed.automaton.step_det(f, &[q1, q1]), Some(q2));
303 }
304
305 #[test]
306 fn parses_quoted_names_and_scientific_weights() {
307 let parsed =
308 parse_alto("'S,0-1'! -> r('A,0-1', \"B state\") [3.3921302578018993E-4]").unwrap();
309 let parent = parsed.states.get(&"S,0-1".to_owned()).unwrap();
310 let left = parsed.states.get(&"A,0-1".to_owned()).unwrap();
311 let right = parsed.states.get(&"B state".to_owned()).unwrap();
312 let symbol = parsed.signature.get("r").unwrap();
313 let rule = parsed.automaton.rules().next().unwrap();
314 assert_eq!(rule.result, parent);
315 assert_eq!(rule.children, &[left, right]);
316 assert_eq!(rule.symbol, symbol);
317 assert!((rule.weight - 3.3921302578018993E-4).abs() < 1e-12);
318 }
319
320 #[test]
321 fn detects_arity_mismatch() {
322 let err = parse_alto("S -> f(A) T -> f(A,A)").unwrap_err();
323 assert!(matches!(err, AltoParseError::ArityMismatch { .. }));
324 }
325
326 #[test]
327 fn can_parse_with_shared_signature() {
328 let mut signature = Signature::new();
329 let a = signature.intern("a".to_owned(), 0).unwrap();
330 let parsed = parse_alto_with_signature("S! -> a", &mut signature).unwrap();
331 assert_eq!(parsed.signature.get("a"), Some(a));
332 assert_eq!(signature.get("a"), Some(a));
333 }
334
335 #[test]
336 fn parses_nullary_empty_parens() {
337 let parsed = parse_alto("S! -> a()").unwrap();
338 let rule = parsed.automaton.rules().next().unwrap();
339 assert!(rule.children.is_empty());
340 assert_eq!(parsed.signature.arity(rule.symbol), 0);
341 }
342
343 #[test]
344 fn final_marker_on_child_marks_accepting_state() {
345 let parsed = parse_alto("S -> f(A!)").unwrap();
346 let a = parsed.states.get(&"A".to_owned()).unwrap();
347 assert!(parsed.automaton.is_accepting(&a));
348 }
349
350 #[test]
351 fn rejects_variable_tokens_in_automata() {
352 let err = parse_alto("S -> f(?1)").unwrap_err();
353 assert!(matches!(err, AltoParseError::Unexpected { .. }));
354 }
355
356 #[test]
357 fn lalrpop_rejects_trailing_junk() {
358 let err = parse_alto("S -> a [oops]").unwrap_err();
359 assert!(matches!(err, AltoParseError::Unexpected { .. }));
360 }
361
362 #[test]
363 fn rejects_duplicate_transitions() {
364 let err = parse_alto("S -> a [1.0] S -> a [2.0]").unwrap_err();
365 assert!(matches!(err, AltoParseError::Automaton(_)));
366 }
367}