ssd_core/
parser.rs

1use std::{io::Write, num::ParseIntError, path::Path};
2
3use once_cell::sync::Lazy;
4use pest::{
5    iterators::{Pair, Pairs},
6    Parser, Span,
7};
8use pest_derive::Parser;
9use regex::Regex;
10use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
11
12use crate::ast::{
13    Attribute, DataType, Dependency, Enum, EnumValue, Event, Function, Import, Namespace,
14    OrderedMap, Service, SsdModule, TypeName,
15};
16
17use crate::ast::{AstElement, ServiceAstElement};
18
19fn parse_attribute_arg(node: Pair<Rule>) -> Result<(String, Option<String>), ParseError> {
20    let span = node.as_span();
21    let mut p = node.into_inner();
22    let name = p
23        .next()
24        .ok_or_else(|| ParseError::new(ParseErrorType::IncompleteAttributeArg, span))?
25        .as_str()
26        .to_string();
27    let value = p.next().map(|p| p.into_inner().as_str().to_string());
28    Ok((name, value))
29}
30
31fn parse_attribute(node: Pair<Rule>) -> Result<Attribute, ParseError> {
32    let span = node.as_span();
33    let mut p = node.into_inner();
34    let name = p.next();
35    let mut args = Vec::new();
36    for p in p {
37        args.push(parse_attribute_arg(p)?);
38    }
39    Ok(Attribute::new(
40        Namespace::new(
41            name.ok_or_else(|| ParseError::new(ParseErrorType::IncompleteAttribute, span))?
42                .as_str(),
43        ),
44        args,
45    ))
46}
47
48fn parse_attributes(node: Pair<Rule>) -> Result<Vec<Attribute>, ParseError> {
49    node.into_inner().map(parse_attribute).collect()
50}
51
52fn parse_name(p: &mut Pairs<Rule>, n: Pair<Rule>) -> Result<(String, Vec<Attribute>), ParseError> {
53    let span = n.as_span();
54    if n.as_rule() == Rule::attributes {
55        let attributes = parse_attributes(n)?;
56        let name = p
57            .next()
58            .ok_or_else(|| ParseError::new(ParseErrorType::IncompleteName, span))?
59            .as_str()
60            .to_string();
61        Ok((name, attributes))
62    } else {
63        let name = n.as_str().to_string();
64        Ok((name, Vec::new()))
65    }
66}
67
68#[derive(Parser)]
69#[grammar = "grammar.pest"]
70pub(crate) struct FileParser;
71
72#[derive(Debug)]
73pub struct ParseError {
74    pub error_type: ParseErrorType,
75    pub span: String,
76}
77
78impl ParseError {
79    fn new(error_type: ParseErrorType, span: Span) -> Self {
80        Self {
81            error_type,
82            span: format!("{span:?}"),
83        }
84    }
85}
86
87#[derive(Debug)]
88pub enum ParseErrorType {
89    IncompleteImport,
90    IncompleteDatatype,
91    IncompleteProperty,
92    MissingType(String),
93    IncompleteEnum,
94    IncompleteEnumValue,
95    InvalidEnumValue(String),
96    IncompleteService,
97    IncompleteDepends,
98    IncompleteCall,
99    IncompleteEvent,
100    IncompleteArgumentIdent,
101    IncompleteAttributeArg,
102    IncompleteAttribute,
103    IncompleteName,
104    UnexpectedElement(String),
105    OtherError(String),
106}
107
108impl std::fmt::Display for ParseError {
109    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110        match &self.error_type {
111            ParseErrorType::IncompleteImport => write!(f, "Import incomplete. ({})", self.span),
112            ParseErrorType::IncompleteDatatype => write!(f, "Datatype incomplete. ({})", self.span),
113            ParseErrorType::IncompleteProperty => write!(f, "Property incomplete. ({})", self.span),
114            ParseErrorType::MissingType(name) => {
115                write!(f, "Type missing after {}. ({:?})", name, self.span)
116            }
117            ParseErrorType::IncompleteService => write!(f, "Service incomplete. ({})", self.span),
118            ParseErrorType::IncompleteDepends => write!(f, "Depends incomplete. ({})", self.span),
119            ParseErrorType::IncompleteCall => write!(f, "Call incomplete. ({})", self.span),
120            ParseErrorType::IncompleteEvent => write!(f, "Event incomplete. ({})", self.span),
121            ParseErrorType::IncompleteArgumentIdent => {
122                write!(f, "Argument ident incomplete. ({})", self.span)
123            }
124            ParseErrorType::IncompleteAttributeArg => {
125                write!(f, "Attribute argument incomplete. ({})", self.span)
126            }
127            ParseErrorType::IncompleteAttribute => {
128                write!(f, "Attribute incomplete. ({})", self.span)
129            }
130            ParseErrorType::IncompleteName => {
131                write!(f, "Name incomplete. ({})", self.span)
132            }
133            ParseErrorType::UnexpectedElement(info) => {
134                write!(f, "Unexpected element {} ({})", info, self.span)
135            }
136            ParseErrorType::IncompleteEnum => write!(f, "Incomplete enum. ({})", self.span),
137            ParseErrorType::IncompleteEnumValue => {
138                write!(f, "Incomplete enum value. ({})", self.span)
139            }
140            ParseErrorType::InvalidEnumValue(info) => {
141                write!(f, "Invalid enum value. {} ({})", info, self.span)
142            }
143            ParseErrorType::OtherError(inner) => {
144                write!(f, "Other({inner})")
145            }
146        }
147    }
148}
149
150impl ParseError {
151    fn from_dyn_error<T: std::error::Error>(err: T) -> Self {
152        ParseError {
153            error_type: ParseErrorType::OtherError(format!("{err}")),
154            span: String::new(),
155        }
156    }
157}
158
159impl std::error::Error for ParseError {
160    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
161        None
162    }
163
164    fn cause(&self) -> Option<&dyn std::error::Error> {
165        self.source()
166    }
167}
168
169fn parse_type(typ: &str) -> (&str, bool, Option<usize>) {
170    static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\d+)\s+of").unwrap());
171    if let Some(stripped) = typ.strip_prefix("list of") {
172        (stripped.trim(), true, None)
173    } else if let Some(cap) = RE.captures(typ) {
174        let count_str = cap.get(1).unwrap().as_str();
175        let count = count_str.parse::<usize>().unwrap();
176        (typ[count_str.len() + 3..].trim(), true, Some(count))
177    } else {
178        (typ, false, None)
179    }
180}
181
182#[allow(clippy::too_many_lines)]
183pub fn parse_raw(content: &str) -> Result<Vec<AstElement>, ParseError> {
184    use ParseErrorType::{
185        IncompleteArgumentIdent, IncompleteCall, IncompleteDatatype, IncompleteDepends,
186        IncompleteEnum, IncompleteEnumValue, IncompleteEvent, IncompleteImport, IncompleteProperty,
187        IncompleteService, InvalidEnumValue, MissingType, UnexpectedElement,
188    };
189    let pairs = FileParser::parse(Rule::file, content).map_err(ParseError::from_dyn_error)?;
190    let mut result = Vec::new();
191
192    for p in pairs {
193        match p.as_rule() {
194            Rule::import => {
195                let span = p.as_span();
196                let mut p = p.into_inner();
197                let n = p
198                    .next()
199                    .ok_or_else(|| ParseError::new(IncompleteImport, span))?;
200                let (name, attributes) = parse_name(&mut p, n)?;
201                result.push(AstElement::Import(Import::new(
202                    Namespace::new(&name),
203                    attributes,
204                )));
205            }
206            Rule::data => {
207                let span = p.as_span();
208                let mut p = p.into_inner();
209                let n = p
210                    .next()
211                    .ok_or_else(|| ParseError::new(IncompleteDatatype, span))?;
212                let (name, attributes) = parse_name(&mut p, n)?;
213
214                let mut properties = OrderedMap::new();
215                let mut comments = Vec::new();
216
217                for p in p {
218                    if let Rule::COMMENT = p.as_rule() {
219                        comments.push(p.as_span().as_str()[3..].trim().to_string());
220                        continue;
221                    }
222                    let span = p.as_span();
223                    let mut p = p.into_inner();
224                    let n = p
225                        .next()
226                        .ok_or_else(|| ParseError::new(IncompleteProperty, span))?;
227                    let (name, attributes) = parse_name(&mut p, n)?;
228                    let typ = p
229                        .next()
230                        .ok_or_else(|| ParseError::new(MissingType(name.clone()), span))?
231                        .as_str()
232                        .to_string();
233                    let (typ, is_list, count) = parse_type(typ.as_str());
234                    properties.push((
235                        name,
236                        TypeName::new(Namespace::new(typ), is_list, count, attributes)
237                            .with_comments(&mut comments),
238                    ));
239                    // properties.insert(
240                    //     name,
241                    //     TypeName::new(Namespace::new(&typ), attributes)
242                    //         .with_comments(&mut comments),
243                    // );
244                }
245
246                result.push(AstElement::DataType((
247                    name,
248                    DataType::new(properties, attributes),
249                )));
250            }
251            Rule::enum_ => {
252                let span = p.as_span();
253                let mut p = p.into_inner();
254                let n = p
255                    .next()
256                    .ok_or_else(|| ParseError::new(IncompleteEnum, span))?;
257                let (name, attributes) = parse_name(&mut p, n)?;
258
259                let mut values = OrderedMap::new();
260
261                let mut comments = Vec::new();
262                for p in p {
263                    if let Rule::COMMENT = p.as_rule() {
264                        comments.push(p.as_span().as_str()[3..].trim().to_string());
265                        continue;
266                    }
267                    let span = p.as_span();
268                    let mut p = p.into_inner();
269                    let n = p
270                        .next()
271                        .ok_or_else(|| ParseError::new(IncompleteEnumValue, span))?;
272                    let (name, attributes) = parse_name(&mut p, n)?;
273                    let value = if let Some(v) = p.next() {
274                        Some(v.as_str().parse().map_err(|err: ParseIntError| {
275                            ParseError::new(InvalidEnumValue(err.to_string()), span)
276                        })?)
277                    } else {
278                        None
279                    };
280                    values.push((
281                        name,
282                        EnumValue::new(value, attributes).with_comments(&mut comments),
283                    ));
284                    // values.insert(
285                    //     name,
286                    //     EnumValue::new(value, attributes).with_comments(&mut comments),
287                    // );
288                }
289
290                result.push(AstElement::Enum((name, Enum::new(values, attributes))));
291            }
292            Rule::service => {
293                let span = p.as_span();
294                let mut p = p.into_inner();
295                let n = p
296                    .next()
297                    .ok_or_else(|| ParseError::new(IncompleteService, span))?;
298                let (service_name, attributes) = parse_name(&mut p, n)?;
299
300                let mut service_parts = Vec::new();
301
302                for p in p {
303                    let rule = p.as_rule();
304                    match rule {
305                        Rule::depends => {
306                            let span = p.as_span();
307                            let mut p = p.into_inner();
308                            let n = p
309                                .next()
310                                .ok_or_else(|| ParseError::new(IncompleteDepends, span))?;
311                            let (name, attributes) = parse_name(&mut p, n)?;
312                            service_parts.push(ServiceAstElement::Dependency(Dependency::new(
313                                Namespace::new(&name),
314                                attributes,
315                            )));
316                        }
317                        Rule::function | Rule::handler => {
318                            if rule == Rule::handler {
319                                const DEPRECATED: &str =  "Using 'handlers' is deprecated and will be removed in future versions. Use 'fn' instead.";
320                                let mut stderr = StandardStream::stderr(ColorChoice::Always);
321                                if stderr
322                                    .set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))
323                                    .is_ok()
324                                {
325                                    writeln!(&mut stderr, "{DEPRECATED}").unwrap();
326
327                                    let _ = stderr.set_color(&ColorSpec::default());
328                                } else {
329                                    eprintln!("{DEPRECATED}");
330                                }
331                            }
332                            let span = p.as_span();
333                            let mut p = p.into_inner();
334                            let n = p
335                                .next()
336                                .ok_or_else(|| ParseError::new(IncompleteCall, span))?;
337                            let (call_name, call_attributes) = parse_name(&mut p, n)?;
338                            let mut arguments = OrderedMap::new();
339                            let mut return_type = None;
340                            let mut attributes = Vec::new();
341                            for p in p.by_ref() {
342                                match p.as_rule() {
343                                    Rule::argument => {
344                                        let span = p.as_span();
345                                        let mut p = p.clone().into_inner();
346                                        while let Some(n) = p.next() {
347                                            match n.as_rule() {
348                                                Rule::ident => {
349                                                    let name = n.as_str().to_string();
350                                                    let typ = p.next().ok_or_else(|| ParseError::new(IncompleteArgumentIdent, span))?.as_str().to_string();
351                                                    let (typ, is_list, count) = parse_type(typ.as_str());
352                                                    arguments.push((name, TypeName::new(Namespace::new(typ), is_list, count, attributes.clone())));
353                                                    // arguments.insert(name, TypeName::new(Namespace::new(&typ), attributes.clone()));
354                                                    attributes.clear();
355                                                }
356                                                Rule::attributes => {
357                                                    attributes = parse_attributes(n)?;
358                                                }
359                                                _ => Err(ParseError::new(
360                                                    UnexpectedElement(format!(
361                                                        "while parsing argument for call \"{call_name}\" in service \"{service_name}\"! {p}"
362                                                    )),
363                                                    span,
364                                                ))?,
365                                            }
366                                        }
367                                    }
368                                    Rule::typ => {
369                                        static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s+").unwrap());
370                                        let typ = RE.replace_all(p.as_str(), " ");
371                                        let (typ, is_list, count) = parse_type(&typ);
372                                        return_type = Some(TypeName::new(
373                                            Namespace::new(typ),
374                                            is_list,
375                                            count,
376                                            Vec::new(),
377                                        ));
378                                    }
379                                    _ => Err(ParseError::new(
380                                        UnexpectedElement(format!(
381                                            "while parsing call \"{call_name}\" in service \"{service_name}\"! {p}"
382                                        )),
383                                        p.as_span(),
384                                    ))?,
385                                }
386                            }
387
388                            if let Some(p) = p.next() {
389                                if p.as_rule() == Rule::typ {
390                                    let (typ, is_list, count) = parse_type(p.as_str());
391                                    return_type = Some(TypeName::new(
392                                        Namespace::new(typ),
393                                        is_list,
394                                        count,
395                                        Vec::new(),
396                                    ));
397                                } else {
398                                    Err(ParseError::new(
399                                        UnexpectedElement(format!(
400                                            "while parsing return type for call \"{call_name}\" in service \"{service_name}\"! {p}"
401                                        )),
402                                        p.as_span(),
403                                    ))?;
404                                }
405                            }
406                            service_parts.push(ServiceAstElement::Function((
407                                call_name,
408                                Function::new(arguments, return_type, call_attributes),
409                            )));
410                        }
411                        Rule::event => {
412                            let span = p.as_span();
413                            let mut p = p.into_inner();
414                            let n = p
415                                .next()
416                                .ok_or_else(|| ParseError::new(IncompleteEvent, span))?;
417                            let (event_name, event_attributes) = parse_name(&mut p, n)?;
418                            let mut arguments = OrderedMap::new();
419                            let mut attributes = Vec::new();
420                            for p in p.by_ref() {
421                                match p.as_rule() {
422                                    Rule::argument => {
423                                        let span = p.as_span();
424                                        let mut p = p.clone().into_inner();
425                                        while let Some(n) = p.next() {
426                                            match n.as_rule() {
427                                                Rule::ident => {
428                                                    let name = n.as_str().to_string();
429                                                    let typ = p.next().ok_or_else(|| ParseError::new(IncompleteArgumentIdent, span))?.as_str().to_string();
430                                                    let (typ, is_list, count) = parse_type(typ.as_str());
431                                                    arguments.push((name, TypeName::new(Namespace::new(typ), is_list, count, attributes.clone())));
432                                                    // arguments.insert(name, TypeName::new(Namespace::new(&typ), attributes.clone()));
433                                                    attributes.clear();
434                                                }
435                                                Rule::attributes => {
436                                                    attributes = parse_attributes(n)?;
437                                                }
438                                                _ => Err(ParseError::new(
439                                                    UnexpectedElement(format!(
440                                                        "while parsing argument for event \"{event_name}\" in service \"{service_name}\"! {p}"
441                                                    )),
442                                                    span,
443                                                ))?,
444                                            }
445                                        }
446                                    }
447                                    _ => Err(ParseError::new(
448                                        UnexpectedElement(format!(
449                                            "while parsing event \"{event_name}\" in service \"{service_name}\"! {p}"
450                                        )),
451                                        p.as_span(),
452                                    ))?,
453                                }
454                            }
455
456                            service_parts.push(ServiceAstElement::Event((
457                                event_name,
458                                Event::new(arguments, event_attributes),
459                            )));
460                        }
461                        Rule::COMMENT => service_parts.push(ServiceAstElement::Comment(
462                            p.as_span().as_str()[3..].trim().to_string(),
463                        )),
464                        _ => Err(ParseError::new(
465                            UnexpectedElement(format!(
466                                "while parsing service \"{service_name}\"! {p}"
467                            )),
468                            p.as_span(),
469                        ))?,
470                    }
471                }
472
473                result.push(AstElement::Service((
474                    service_name,
475                    service_parts,
476                    attributes,
477                )));
478            }
479            Rule::EOI => {}
480            Rule::COMMENT => {
481                let span = p.as_span();
482                result.push(AstElement::Comment(span.as_str()[3..].trim().to_string()));
483            }
484            _ => Err(ParseError::new(
485                UnexpectedElement(format!("{p}")),
486                p.as_span(),
487            ))?,
488        }
489    }
490
491    Ok(result)
492}
493
494#[allow(unused)]
495pub fn parse(content: &str, namespace: Namespace) -> Result<SsdModule, ParseError> {
496    let raw = parse_raw(content)?;
497    Ok(raw_to_ssd_file(namespace, &raw))
498}
499
500pub(crate) fn raw_service_to_service(
501    raw: &[ServiceAstElement],
502    attributes: &[Attribute],
503) -> Service {
504    let mut dependencies = Vec::new();
505    let mut functions = OrderedMap::new();
506    let mut events = OrderedMap::new();
507
508    let mut comments = Vec::new();
509    for element in raw {
510        match element {
511            ServiceAstElement::Dependency(import) => {
512                dependencies.push(import.clone().with_comments(&mut comments));
513            }
514            ServiceAstElement::Function((key, value)) => {
515                assert!(
516                    !functions.iter().any(|(name, _)| name == key),
517                    "Duplicate function {key}!"
518                );
519                functions.push((key.clone(), value.clone().with_comments(&mut comments)));
520                // assert!(
521                //     functions
522                //         .insert(key.clone(), value.clone().with_comments(&mut comments))
523                //         .is_none(),
524                //     "Duplicate function {key}!"
525                // );
526            }
527            ServiceAstElement::Event((key, value)) => {
528                assert!(
529                    !events.iter().any(|(name, _)| name == key),
530                    "Duplicate event {key}!"
531                );
532                events.push((key.clone(), value.clone().with_comments(&mut comments)));
533                // assert!(
534                //     events
535                //         .insert(key.clone(), value.clone().with_comments(&mut comments))
536                //         .is_none(),
537                //     "Duplicate event {key}!"
538                // );
539            }
540            ServiceAstElement::Comment(c) => comments.push(c.to_string()),
541        }
542    }
543
544    Service::new(dependencies, functions, events, attributes.into())
545}
546
547pub(crate) fn raw_to_ssd_file(namespace: Namespace, raw: &[AstElement]) -> SsdModule {
548    let mut imports = Vec::new();
549    let mut datatypes = OrderedMap::new();
550    let mut enums = OrderedMap::new();
551    let mut services = OrderedMap::new();
552
553    for element in raw {
554        match element {
555            AstElement::Import(import) => imports.push(import.clone()),
556            AstElement::DataType((key, value)) => {
557                assert!(
558                    !datatypes.iter().any(|(name, _)| name == key),
559                    "Duplicate datatype {key}!"
560                );
561                datatypes.push((key.clone(), value.clone()));
562                // assert!(
563                //     datatypes.insert(key.clone(), value.clone()).is_none(),
564                //     "Duplicate datatype {key}!"
565                // );
566            }
567            AstElement::Enum((key, value)) => {
568                assert!(
569                    !enums.iter().any(|(name, _)| name == key),
570                    "Duplicate enum {key}!"
571                );
572                enums.push((key.clone(), value.clone()));
573                // assert!(
574                //     enums.insert(key.clone(), value.clone()).is_none(),
575                //     "Duplicate enum {key}!"
576                // );
577            }
578
579            AstElement::Service((key, value, attributes)) => {
580                assert!(
581                    !services.iter().any(|(name, _)| name == key),
582                    "Duplicate service {key}!"
583                );
584                services.push((key.clone(), raw_service_to_service(value, attributes)));
585                // assert!(
586                //     services.insert(key.clone(), raw_service_to_service(value, attributes)).is_none(),
587                //     "Duplicate service {key}!"
588                // );
589            }
590            AstElement::Comment(_) => (),
591        }
592    }
593
594    SsdModule::new(namespace, imports, datatypes, enums, services)
595}
596
597pub fn parse_file_raw<P: AsRef<Path>>(path: P) -> Result<Vec<AstElement>, ParseError> {
598    let content = std::fs::read_to_string(path).map_err(ParseError::from_dyn_error)?;
599
600    parse_raw(&content)
601}
602
603/// Parses the given file and returns the corresponding `SsdModule`.
604///
605/// The namespace of the file is taken from the file's path, with the base directory removed.
606///
607/// # Arguments
608///
609/// * `base` - The base path of the file.
610/// * `path` - The path to the file to parse.
611pub fn parse_file<P: AsRef<Path>>(base: &P, path: &P) -> Result<SsdModule, ParseError> {
612    let base = base.as_ref();
613    let path = path.as_ref();
614    let mut components = if path.starts_with(base) {
615        path.strip_prefix(base)
616            .map_err(ParseError::from_dyn_error)?
617            .to_owned()
618    } else {
619        path.to_owned()
620    };
621
622    components.set_extension("");
623    let components = components
624        .components()
625        .map(|c| c.as_os_str().to_string_lossy().to_string())
626        .collect::<Vec<_>>();
627
628    parse_file_with_namespace(path, Namespace::from_vec(components))
629}
630
631#[allow(unused)]
632pub fn parse_file_with_namespace<P: AsRef<Path>>(
633    path: P,
634    namespace: Namespace,
635) -> Result<SsdModule, ParseError> {
636    let raw = parse_file_raw(path)?;
637
638    Ok(raw_to_ssd_file(namespace, &raw))
639}
640
641#[test]
642fn test_simple() {
643    insta::assert_json_snapshot!(parse(
644        include_str!("../../../data/test.svc"),
645        Namespace::new("__test__")
646    )
647    .unwrap());
648}
649
650#[test]
651fn test_raw() {
652    insta::assert_json_snapshot!(parse_raw(include_str!("../../../data/test.svc"),).unwrap());
653}