Skip to main content

lemma/parsing/
types.rs

1use super::ast::Span;
2use super::Rule;
3use crate::error::LemmaError;
4use crate::semantic::TypeDef;
5use pest::iterators::Pair;
6use std::sync::Arc;
7
8pub(crate) fn parse_type_definition(
9    pair: Pair<Rule>,
10    attribute: &str,
11    doc_name: &str,
12) -> Result<TypeDef, LemmaError> {
13    let span = Span::from_pest_span(pair.as_span());
14    let pair_str = pair.as_str();
15    let source_location = crate::Source::new(attribute, span.clone(), doc_name);
16    let mut type_name = None;
17    let mut type_arrow_chain = None;
18
19    for inner_pair in pair.into_inner() {
20        match inner_pair.as_rule() {
21            Rule::type_name_def => {
22                type_name = Some(inner_pair.as_str().to_string());
23            }
24            Rule::type_arrow_chain => {
25                type_arrow_chain = Some(inner_pair);
26            }
27            _ => {}
28        }
29    }
30
31    let type_name_str = type_name.ok_or_else(|| {
32        LemmaError::engine(
33            "Grammar error: type_definition missing type_name_def",
34            span.clone(),
35            attribute,
36            Arc::from(pair_str),
37            doc_name,
38            1,
39            None::<String>,
40        )
41    })?;
42
43    let arrow_chain_pair = type_arrow_chain.ok_or_else(|| {
44        LemmaError::engine(
45            "Grammar error: type_definition missing type_arrow_chain",
46            span,
47            attribute,
48            Arc::from(pair_str),
49            doc_name,
50            1,
51            None::<String>,
52        )
53    })?;
54
55    let (parent, overrides, _from) =
56        parse_type_arrow_chain_with_commands(arrow_chain_pair, attribute, doc_name)?;
57    // Regular types don't support 'from' - it's only for imports and inline types
58
59    Ok(TypeDef::Regular {
60        source_location,
61        name: type_name_str,
62        parent,
63        overrides,
64    })
65}
66
67pub(crate) fn parse_type_import(
68    pair: Pair<Rule>,
69    attribute: &str,
70    doc_name: &str,
71) -> Result<TypeDef, LemmaError> {
72    let span = Span::from_pest_span(pair.as_span());
73    let pair_str = pair.as_str();
74    let source_location = crate::Source::new(attribute, span.clone(), doc_name);
75    // The pair is type_import, which contains type_import_def
76    let type_import_def = pair.into_inner().next().ok_or_else(|| {
77        LemmaError::engine(
78            "Grammar error: type_import must contain type_import_def",
79            span.clone(),
80            attribute,
81            Arc::from(pair_str),
82            doc_name,
83            1,
84            None::<String>,
85        )
86    })?;
87
88    let mut type_names = Vec::new();
89    let mut imported_doc_name = None;
90
91    for inner_pair in type_import_def.into_inner() {
92        match inner_pair.as_rule() {
93            Rule::type_name_def => {
94                type_names.push(inner_pair.as_str().to_string());
95            }
96            Rule::doc_name => {
97                imported_doc_name = Some(inner_pair.as_str().to_string());
98            }
99            _ => {}
100        }
101    }
102
103    let imported_doc_name = imported_doc_name.ok_or_else(|| {
104        LemmaError::engine(
105            "Grammar error: type_import missing doc_name",
106            span.clone(),
107            attribute,
108            Arc::from(pair_str),
109            doc_name,
110            1,
111            None::<String>,
112        )
113    })?;
114
115    if type_names.is_empty() {
116        return Err(LemmaError::engine(
117            "Grammar error: type_import missing type_name_def",
118            span,
119            attribute,
120            Arc::from(pair_str),
121            doc_name,
122            1,
123            None::<String>,
124        ));
125    }
126
127    let source_type_name = if type_names.len() == 1 {
128        type_names[0].clone()
129    } else {
130        type_names[1].clone()
131    };
132
133    let final_type_name = type_names[0].clone();
134
135    Ok(TypeDef::Import {
136        source_location,
137        name: final_type_name,
138        source_type: source_type_name,
139        from: imported_doc_name,
140        overrides: None,
141    })
142}
143
144type TypeArrowChainResult = (String, Option<Vec<(String, Vec<String>)>>, Option<String>);
145
146pub(crate) fn parse_type_arrow_chain_with_commands(
147    pair: Pair<Rule>,
148    attribute: &str,
149    doc_name: &str,
150) -> Result<TypeArrowChainResult, LemmaError> {
151    let span = Span::from_pest_span(pair.as_span());
152    let pair_str = pair.as_str();
153    let mut inner = pair.into_inner();
154    let first = inner.next().ok_or_else(|| {
155        LemmaError::engine(
156            "Grammar error: type_arrow_chain cannot be empty",
157            span.clone(),
158            attribute,
159            Arc::from(pair_str),
160            doc_name,
161            1,
162            None::<String>,
163        )
164    })?;
165
166    // Store the remaining items for command parsing (after the first element)
167    let remaining_items: Vec<_> = inner.collect();
168
169    let (parent_name, from_doc) = match first.as_rule() {
170        Rule::type_name_def => {
171            // type_name_def can match either type_custom or type_standard
172            let mut inner = first.clone().into_inner();
173            match inner.next() {
174                Some(child) => match child.as_rule() {
175                    Rule::type_standard => {
176                        // Standard type - should be lowercase
177                        (first.as_str().to_lowercase(), None)
178                    }
179                    Rule::type_custom => {
180                        // Custom type (label)
181                        (first.as_str().to_string(), None)
182                    }
183                    _ => {
184                        let child_span = Span::from_pest_span(child.as_span());
185                        return Err(LemmaError::engine(
186                            format!("Unexpected rule in type_name_def: {:?}", child.as_rule()),
187                            child_span,
188                            attribute,
189                            Arc::from(first.as_str()),
190                            doc_name,
191                            1,
192                            None::<String>,
193                        ));
194                    }
195                },
196                None => {
197                    let first_span = Span::from_pest_span(first.as_span());
198                    return Err(LemmaError::engine(
199                        "Grammar error: type_name_def must contain type_custom or type_standard",
200                        first_span,
201                        attribute,
202                        Arc::from(first.as_str()),
203                        doc_name,
204                        1,
205                        None::<String>,
206                    ));
207                }
208            }
209        }
210        Rule::type_import_def => {
211            // Parse: type_name_def ~ "from" ~ doc_name
212            let inner = first.clone().into_inner();
213            let mut type_name_def = None;
214            let mut imported_doc_name = None;
215
216            for item in inner {
217                match item.as_rule() {
218                    Rule::type_name_def => {
219                        let mut type_inner = item.clone().into_inner();
220                        match type_inner.next() {
221                            Some(child) => match child.as_rule() {
222                                Rule::type_standard => {
223                                    type_name_def = Some(item.as_str().to_lowercase());
224                                }
225                                Rule::type_custom => {
226                                    type_name_def = Some(item.as_str().to_string());
227                                }
228                                _ => {
229                                    let child_span = Span::from_pest_span(child.as_span());
230                                    return Err(LemmaError::engine(
231                                        format!(
232                                            "Unexpected rule in type_name_def: {:?}",
233                                            child.as_rule()
234                                        ),
235                                        child_span,
236                                        attribute,
237                                        Arc::from(item.as_str()),
238                                        doc_name,
239                                        1,
240                                        None::<String>,
241                                    ));
242                                }
243                            },
244                            None => {
245                                let item_span = Span::from_pest_span(item.as_span());
246                                return Err(LemmaError::engine(
247                                    "Grammar error: type_name_def must contain type_custom or type_standard",
248                                    item_span,
249                                    attribute,
250                                    Arc::from(item.as_str()),
251                                    doc_name,
252                                    1,
253                                    None::<String>,
254                                ));
255                            }
256                        }
257                    }
258                    Rule::doc_name => {
259                        imported_doc_name = Some(item.as_str().to_string());
260                    }
261                    _ => {}
262                }
263            }
264
265            let first_span = Span::from_pest_span(first.as_span());
266            let source_type = type_name_def.ok_or_else(|| {
267                LemmaError::engine(
268                    "Grammar error: type_import_def missing type_name_def",
269                    first_span.clone(),
270                    attribute,
271                    Arc::from(first.as_str()),
272                    doc_name,
273                    1,
274                    None::<String>,
275                )
276            })?;
277
278            let from = imported_doc_name.ok_or_else(|| {
279                LemmaError::engine(
280                    "Grammar error: type_import_def missing doc_name",
281                    first_span,
282                    attribute,
283                    Arc::from(first.as_str()),
284                    doc_name,
285                    1,
286                    None::<String>,
287                )
288            })?;
289
290            (source_type, Some(from))
291        }
292        _ => {
293            return Err(LemmaError::engine(
294                format!("Unexpected rule in type_arrow_chain: {:?}", first.as_rule()),
295                span.clone(),
296                attribute,
297                Arc::from(pair_str),
298                doc_name,
299                1,
300                None::<String>,
301            ));
302        }
303    };
304
305    let mut commands = Vec::new();
306    let mut expecting_command = false;
307
308    for item in remaining_items {
309        match item.as_rule() {
310            Rule::arrow_symbol => {
311                expecting_command = true;
312            }
313            Rule::command => {
314                if !expecting_command {
315                    let item_span = Span::from_pest_span(item.as_span());
316                    return Err(LemmaError::engine(
317                        "Grammar error: command must follow arrow_symbol",
318                        item_span,
319                        attribute,
320                        Arc::from(item.as_str()),
321                        doc_name,
322                        1,
323                        None::<String>,
324                    ));
325                }
326                let (command_name, args) = parse_command(item, attribute, doc_name)?;
327                commands.push((command_name, args));
328                expecting_command = false;
329            }
330            _ => {
331                let item_span = Span::from_pest_span(item.as_span());
332                return Err(LemmaError::engine(
333                    format!("Unexpected rule in type_arrow_chain: {:?}", item.as_rule()),
334                    item_span,
335                    attribute,
336                    Arc::from(item.as_str()),
337                    doc_name,
338                    1,
339                    None::<String>,
340                ));
341            }
342        }
343    }
344
345    if expecting_command {
346        return Err(LemmaError::engine(
347            "Grammar error: arrow_symbol must be followed by command",
348            span.clone(),
349            attribute,
350            Arc::from(pair_str),
351            doc_name,
352            1,
353            None::<String>,
354        ));
355    }
356
357    let overrides = if commands.is_empty() {
358        None
359    } else {
360        Some(commands)
361    };
362
363    Ok((parent_name, overrides, from_doc))
364}
365
366fn parse_command(
367    pair: Pair<Rule>,
368    attribute: &str,
369    doc_name: &str,
370) -> Result<(String, Vec<String>), LemmaError> {
371    let span = Span::from_pest_span(pair.as_span());
372    let pair_str = pair.as_str();
373    let mut command_name = None;
374    let mut command_args = Vec::new();
375
376    for inner_pair in pair.into_inner() {
377        match inner_pair.as_rule() {
378            Rule::command_name => {
379                command_name = Some(inner_pair.as_str().to_string());
380            }
381            Rule::command_arg => {
382                command_args.push(inner_pair.as_str().to_string());
383            }
384            _ => {}
385        }
386    }
387
388    let name = command_name.ok_or_else(|| {
389        LemmaError::engine(
390            "Grammar error: command must contain command_name",
391            span,
392            attribute,
393            Arc::from(pair_str),
394            doc_name,
395            1,
396            None::<String>,
397        )
398    })?;
399
400    Ok((name, command_args))
401}
402
403// ============================================================================
404// Tests
405// ============================================================================
406
407#[cfg(test)]
408mod tests {
409    use crate::{parse, ResourceLimits};
410
411    #[test]
412    fn type_definition_parsing_produces_regular_typedef_with_overrides() {
413        let code = r#"doc test
414type dice = number -> minimum 0 -> maximum 6"#;
415
416        let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
417        assert_eq!(docs.len(), 1);
418
419        let doc = &docs[0];
420        assert_eq!(doc.name, "test");
421        assert_eq!(doc.types.len(), 1);
422
423        let type_def = &doc.types[0];
424        match type_def {
425            crate::TypeDef::Regular {
426                name,
427                parent,
428                overrides,
429                ..
430            } => {
431                assert_eq!(name, "dice");
432                assert_eq!(parent, "number");
433                assert!(overrides.is_some());
434
435                let overrides = overrides.as_ref().unwrap();
436                assert_eq!(overrides.len(), 2);
437                assert_eq!(overrides[0].0, "minimum");
438                assert_eq!(overrides[0].1, vec!["0"]);
439                assert_eq!(overrides[1].0, "maximum");
440                assert_eq!(overrides[1].1, vec!["6"]);
441            }
442            other => panic!("Expected Regular type definition, got {:?}", other),
443        }
444    }
445}