hzlang_parser/
lib.rs

1#[derive(PartialEq, Eq, Debug, Hash)]
2pub struct Comment(pub String);
3
4#[derive(PartialEq, Eq, Debug, Hash)]
5pub enum Line {
6    Empty {
7        comment: Option<Comment>,
8    },
9    Filled {
10        action_invocation: ActionInvocation,
11        comment: Option<Comment>,
12        attached: Vec<Line>,
13    },
14}
15
16#[derive(PartialEq, Eq, Debug, Hash)]
17pub struct ActionInvocation {
18    pub name: Name,
19}
20
21#[derive(PartialEq, Eq, Debug, Hash)]
22pub struct Program {
23    pub contents: Vec<Line>,
24}
25
26#[derive(Debug, PartialEq, Eq, Hash)]
27pub enum ErrorKind {
28    FirstLineIndented {
29        present_indentation: usize,
30    },
31    OverindentedLine {
32        max_allowed_indentation: usize,
33        present_indentation: usize,
34    },
35    IndentationLevelIncreasedAfterEmptyLine {
36        max_allowed_indentation: usize,
37        present_indentation: usize,
38    },
39    UnclosedStringQuote,
40    UnclosedBracedNameBrace,
41    NameExpectedInBraces,
42    FillerExpectedInList,
43    UnclosedDictPairParen,
44    UnclosedListParen,
45    KeyFillerExpectedInDictPair,
46    ValueFillerExpectedInDictPair,
47    UnclosedDictBracket,
48    DictPairExpectedInDict,
49    UnexpectedClosingBracketInLine,
50    UnexpectedClosingParenInLine,
51    UnexpectedClosingBraceInLine,
52}
53
54#[derive(Debug, PartialEq, Eq, Hash)]
55pub struct Error {
56    pub line_index: usize,
57    pub kind: ErrorKind,
58}
59
60#[derive(PartialEq, Eq, Debug, Hash)]
61pub struct Word(pub String);
62
63#[derive(PartialEq, Eq, Debug, Hash)]
64pub enum NamePart {
65    Word(Word),
66    Filler(Filler),
67}
68
69#[derive(PartialEq, Eq, Debug, Hash)]
70pub struct Name {
71    pub parts: Vec<NamePart>,
72}
73
74#[derive(PartialEq, Eq, Debug, Hash)]
75pub struct Dict {
76    pub pairs: Vec<DictPair>,
77}
78
79#[derive(PartialEq, Eq, Debug, Hash)]
80pub struct DictPair {
81    pub key: Filler,
82    pub value: Filler,
83}
84
85#[derive(PartialEq, Eq, Debug, Hash)]
86pub enum Filler {
87    String(HzString),
88    Name(Name),
89    List(List),
90    Dict(Dict),
91}
92
93#[derive(PartialEq, Eq, Debug, Hash)]
94pub struct List {
95    pub items: Vec<Filler>,
96}
97
98#[derive(PartialEq, Eq, Debug, Hash)]
99pub struct RawHzStringPart(pub String);
100
101#[derive(PartialEq, Eq, Debug, Hash)]
102pub enum HzStringPart {
103    Raw(RawHzStringPart),
104    Name(Name),
105}
106
107#[derive(PartialEq, Eq, Debug, Hash)]
108pub struct HzString {
109    pub parts: Vec<HzStringPart>,
110}
111
112type ParsingResult<'a, T> = parco::Result<T, &'a str, ErrorKind>;
113
114impl<T> From<ErrorKind> for ParsingResult<'_, T> {
115    fn from(error: ErrorKind) -> Self {
116        ParsingResult::Fatal(error)
117    }
118}
119
120trait Shrinkable {
121    fn shrink_to_fit(&mut self);
122}
123
124impl<T> Shrinkable for Vec<T> {
125    fn shrink_to_fit(&mut self) {
126        self.shrink_to_fit();
127    }
128}
129
130impl Shrinkable for String {
131    fn shrink_to_fit(&mut self) {
132        self.shrink_to_fit();
133    }
134}
135
136fn shrink<C: Shrinkable>(mut container: C) -> C {
137    container.shrink_to_fit();
138    container
139}
140
141fn skip_whitespace(s: &str) -> &str {
142    for (i, c) in s.char_indices() {
143        if !c.is_whitespace() {
144            return &s[i..];
145        }
146    }
147    ""
148}
149
150mod string {
151    use super::*;
152
153    mod raw_part {
154        use super::*;
155
156        fn parse_char(rest: &str) -> ParsingResult<char> {
157            parco::one_matching_part(rest, |c| !['"', '{'].contains(c))
158        }
159
160        pub fn parse(rest: &str) -> ParsingResult<RawHzStringPart> {
161            parse_char(rest)
162                .and(|c, rest| {
163                    parco::collect_repeating(String::from(c), rest, |rest| parse_char(rest))
164                })
165                .map(|part| RawHzStringPart(shrink(part)))
166        }
167    }
168
169    pub fn parse(rest: &str) -> ParsingResult<HzString> {
170        parco::one_matching_part(rest, |c| *c == '"')
171            .and(|_, rest| {
172                parco::collect_repeating(Vec::new(), rest, |rest| {
173                    raw_part::parse(rest)
174                        .map(|raw_part| HzStringPart::Raw(raw_part))
175                        .or(|| braced_name::parse(rest).map(|name| HzStringPart::Name(name)))
176                })
177            })
178            .and(|parts, rest| {
179                parco::one_matching_part(rest, |c| *c == '"')
180                    .or(|| ErrorKind::UnclosedStringQuote.into())
181                    .and(|_, rest| {
182                        ParsingResult::Ok(
183                            HzString {
184                                parts: shrink(parts),
185                            },
186                            rest,
187                        )
188                    })
189            })
190    }
191
192    #[cfg(test)]
193    mod tests {
194        use super::*;
195
196        #[test]
197        fn nothing() {
198            assert_eq!(parse(""), ParsingResult::Err);
199        }
200
201        #[test]
202        fn empty_string() {
203            assert_eq!(
204                parse(r#""" something else"#),
205                ParsingResult::Ok(HzString { parts: vec![] }, " something else")
206            );
207        }
208
209        #[test]
210        fn unclosed_quote() {
211            assert_eq!(
212                parse(r#""text"#),
213                ParsingResult::Fatal(ErrorKind::UnclosedStringQuote)
214            );
215        }
216
217        #[test]
218        fn unclosed_quote_2() {
219            assert_eq!(
220                parse(r#"""#),
221                ParsingResult::Fatal(ErrorKind::UnclosedStringQuote)
222            );
223        }
224
225        #[test]
226        fn correct_string() {
227            assert_eq!(
228                parse(r#"" a {b "c" {d}} e " something else"#),
229                ParsingResult::Ok(
230                    HzString {
231                        parts: vec![
232                            HzStringPart::Raw(RawHzStringPart(" a ".to_owned())),
233                            HzStringPart::Name(Name {
234                                parts: vec![
235                                    NamePart::Word(Word("b".to_owned())),
236                                    NamePart::Filler(Filler::String(HzString {
237                                        parts: vec![HzStringPart::Raw(RawHzStringPart(
238                                            "c".to_owned()
239                                        ))]
240                                    })),
241                                    NamePart::Filler(Filler::Name(Name {
242                                        parts: vec![NamePart::Word(Word("d".to_owned()))]
243                                    }))
244                                ]
245                            }),
246                            HzStringPart::Raw(RawHzStringPart(" e ".to_owned()))
247                        ]
248                    },
249                    " something else"
250                )
251            );
252        }
253    }
254}
255
256mod word {
257    use super::*;
258
259    fn parse_char(rest: &str) -> ParsingResult<char> {
260        parco::one_matching_part(rest, |c| !("()[]{}\"|".contains(*c) || c.is_whitespace()))
261    }
262
263    pub fn parse(rest: &str) -> ParsingResult<Word> {
264        parse_char(rest)
265            .and(|c, rest| parco::collect_repeating(String::from(c), rest, |rest| parse_char(rest)))
266            .map(|raw_word| Word(shrink(raw_word)))
267    }
268
269    #[cfg(test)]
270    mod tests {
271        use super::*;
272
273        #[test]
274        fn nothing() {
275            assert_eq!(parse(""), ParsingResult::Err);
276        }
277
278        #[test]
279        fn correct_word() {
280            assert_eq!(
281                parse("a something else"),
282                ParsingResult::Ok(Word("a".to_owned()), " something else")
283            );
284        }
285
286        #[test]
287        fn unexpected_quote() {
288            assert_eq!(parse("\""), ParsingResult::Err,);
289        }
290
291        #[test]
292        fn a_word_and_a_comment() {
293            assert_eq!(
294                parse("blah|abc"),
295                ParsingResult::Ok(Word("blah".to_owned()), "|abc")
296            );
297        }
298
299        #[test]
300        fn just_a_comment() {
301            assert_eq!(parse("|abc"), ParsingResult::Err,);
302        }
303    }
304}
305
306mod name {
307    use super::*;
308
309    mod part {
310        use super::*;
311
312        pub fn parse(rest: &str) -> ParsingResult<NamePart> {
313            filler::parse(rest)
314                .map(|filler| NamePart::Filler(filler))
315                .or(|| word::parse(rest).map(|word| NamePart::Word(word)))
316        }
317    }
318
319    pub fn parse(rest: &str) -> ParsingResult<Name> {
320        part::parse(rest)
321            .and(|name_part, rest| {
322                parco::collect_repeating(Vec::from([name_part]), rest, |rest| {
323                    part::parse(skip_whitespace(rest))
324                })
325            })
326            .map(|parts| Name {
327                parts: shrink(parts),
328            })
329    }
330
331    #[cfg(test)]
332    mod tests {
333        use super::*;
334
335        #[test]
336        fn nothing() {
337            assert_eq!(parse(""), ParsingResult::Err);
338        }
339
340        #[test]
341        fn unexpected_closing_bracket() {
342            assert_eq!(parse("]"), ParsingResult::Err);
343        }
344
345        #[test]
346        fn just_a_list() {
347            assert_eq!(
348                parse("()"),
349                ParsingResult::Ok(
350                    Name {
351                        parts: vec![NamePart::Filler(Filler::List(List { items: vec![] }))]
352                    },
353                    ""
354                )
355            );
356        }
357
358        #[test]
359        fn correct_name() {
360            assert_eq!(
361                parse("a ] something else"),
362                ParsingResult::Ok(
363                    Name {
364                        parts: vec![NamePart::Word(Word("a".to_owned()))]
365                    },
366                    " ] something else"
367                )
368            );
369        }
370
371        #[test]
372        fn correct_name_2() {
373            assert_eq!(
374                parse("a b] something else"),
375                ParsingResult::Ok(
376                    Name {
377                        parts: vec![
378                            NamePart::Word(Word("a".to_owned())),
379                            NamePart::Word(Word("b".to_owned()))
380                        ]
381                    },
382                    "] something else"
383                )
384            );
385        }
386    }
387}
388
389mod braced_name {
390    use super::*;
391
392    pub fn parse(rest: &str) -> ParsingResult<Name> {
393        parco::one_matching_part(rest, |c| *c == '{')
394            .and(|_, rest| name::parse(rest).or(|| ErrorKind::NameExpectedInBraces.into()))
395            .and(|name, rest| {
396                parco::one_matching_part(rest, |c| *c == '}')
397                    .or(|| ErrorKind::UnclosedBracedNameBrace.into())
398                    .and(|_, rest| ParsingResult::Ok(name, rest))
399            })
400    }
401
402    #[cfg(test)]
403    mod tests {
404        use super::*;
405
406        #[test]
407        fn nothing() {
408            assert_eq!(parse(""), ParsingResult::Err);
409        }
410
411        #[test]
412        fn plain_name() {
413            assert_eq!(parse("a"), ParsingResult::Err);
414        }
415
416        #[test]
417        fn correct_braced_name() {
418            assert_eq!(
419                parse("{a} something else"),
420                ParsingResult::Ok(
421                    Name {
422                        parts: vec![NamePart::Word(Word("a".to_owned()))]
423                    },
424                    " something else"
425                )
426            );
427        }
428
429        #[test]
430        fn closing_brace_missing() {
431            assert_eq!(
432                parse("{a "),
433                ParsingResult::Fatal(ErrorKind::UnclosedBracedNameBrace.into())
434            );
435        }
436
437        #[test]
438        fn closing_brace_missing_2() {
439            assert_eq!(
440                parse("{a"),
441                ParsingResult::Fatal(ErrorKind::UnclosedBracedNameBrace.into())
442            );
443        }
444
445        #[test]
446        fn nothing_in_braces() {
447            assert_eq!(
448                parse("{ }"),
449                ParsingResult::Fatal(ErrorKind::NameExpectedInBraces.into())
450            );
451        }
452
453        #[test]
454        fn nothing_in_braces_2() {
455            assert_eq!(
456                parse("{}"),
457                ParsingResult::Fatal(ErrorKind::NameExpectedInBraces.into())
458            );
459        }
460
461        #[test]
462        fn nothing_in_braces_3() {
463            assert_eq!(
464                parse("{"),
465                ParsingResult::Fatal(ErrorKind::NameExpectedInBraces.into())
466            );
467        }
468    }
469}
470
471mod filler {
472    use super::*;
473
474    pub fn parse(rest: &str) -> ParsingResult<Filler> {
475        braced_name::parse(rest)
476            .map(|name| Filler::Name(name))
477            .or(|| string::parse(rest).map(|string| Filler::String(string)))
478            .or(|| list::parse(rest).map(|list| Filler::List(list)))
479            .or(|| dict::parse(rest).map(|dict| Filler::Dict(dict)))
480    }
481
482    #[cfg(test)]
483    mod tests {
484        use super::*;
485
486        #[test]
487        fn unrelated_word() {
488            assert_eq!(parse("a"), ParsingResult::Err);
489        }
490
491        #[test]
492        fn nothing() {
493            assert_eq!(parse(""), ParsingResult::Err);
494        }
495
496        #[test]
497        fn string() {
498            assert_eq!(
499                parse(r#""" something else"#),
500                ParsingResult::Ok(
501                    Filler::String(HzString { parts: vec![] }),
502                    " something else"
503                )
504            );
505        }
506
507        #[test]
508        fn list() {
509            assert_eq!(
510                parse(r#"() something else"#),
511                ParsingResult::Ok(Filler::List(List { items: vec![] }), " something else")
512            );
513        }
514
515        #[test]
516        fn dict() {
517            assert_eq!(
518                parse(r#"[] something else"#),
519                ParsingResult::Ok(Filler::Dict(Dict { pairs: vec![] }), " something else")
520            );
521        }
522
523        #[test]
524        fn braced_name() {
525            assert_eq!(
526                parse(r#"{a} something else"#),
527                ParsingResult::Ok(
528                    Filler::Name(Name {
529                        parts: vec![NamePart::Word(Word("a".to_owned()))]
530                    }),
531                    " something else"
532                )
533            );
534        }
535    }
536}
537
538mod list {
539    use super::*;
540
541    pub fn parse(rest: &str) -> ParsingResult<List> {
542        parco::one_matching_part(rest, |c| *c == '(')
543            .and(|_, rest| {
544                parco::collect_repeating(Vec::new(), rest, |rest| {
545                    let rest = skip_whitespace(rest);
546                    filler::parse(rest).or(|| {
547                        parco::one_part(rest)
548                            .or(|| ErrorKind::UnclosedListParen.into())
549                            .and(|c, _rest| match c {
550                                ')' => ParsingResult::Err,
551                                _ => ErrorKind::FillerExpectedInList.into(),
552                            })
553                    })
554                })
555            })
556            .and(|items, rest| {
557                parco::one_matching_part(rest, |c| *c == ')')
558                    .or(|| unreachable!())
559                    .and(|_, rest| {
560                        ParsingResult::Ok(
561                            List {
562                                items: shrink(items),
563                            },
564                            rest,
565                        )
566                    })
567            })
568    }
569
570    #[cfg(test)]
571    mod tests {
572        use super::*;
573
574        fn string_filler(part: &str) -> Filler {
575            Filler::String(HzString {
576                parts: vec![HzStringPart::Raw(RawHzStringPart(part.to_owned()))],
577            })
578        }
579
580        #[test]
581        fn nothing() {
582            assert_eq!(parse(""), ParsingResult::Err);
583        }
584
585        #[test]
586        fn empty_list() {
587            assert_eq!(
588                parse("() something else"),
589                ParsingResult::Ok(List { items: vec![] }, " something else")
590            );
591        }
592
593        #[test]
594        fn unrelated_word() {
595            assert_eq!(parse("a"), ParsingResult::Err);
596        }
597
598        #[test]
599        fn unfinished_list() {
600            assert_eq!(
601                parse("("),
602                ParsingResult::Fatal(ErrorKind::UnclosedListParen.into())
603            );
604        }
605
606        #[test]
607        fn word_in_list() {
608            assert_eq!(
609                parse("(a"),
610                ParsingResult::Fatal(ErrorKind::FillerExpectedInList.into())
611            );
612        }
613
614        #[test]
615        fn word_in_list_2() {
616            assert_eq!(
617                parse(r#"("a" a"#),
618                ParsingResult::Fatal(ErrorKind::FillerExpectedInList.into())
619            );
620        }
621
622        #[test]
623        fn word_in_list_3() {
624            assert_eq!(
625                parse(r#"("a" a"#),
626                ParsingResult::Fatal(ErrorKind::FillerExpectedInList.into())
627            );
628        }
629
630        #[test]
631        fn word_in_list_4() {
632            assert_eq!(
633                parse(r#"("a""#),
634                ParsingResult::Fatal(ErrorKind::UnclosedListParen.into())
635            );
636        }
637
638        #[test]
639        fn correct_list() {
640            assert_eq!(
641                parse(r#"("a" "b") something else"#),
642                ParsingResult::Ok(
643                    List {
644                        items: vec![string_filler("a"), string_filler("b")]
645                    },
646                    " something else"
647                )
648            );
649        }
650    }
651}
652
653mod dict {
654    use super::*;
655
656    fn parse_pair(rest: &str) -> ParsingResult<DictPair> {
657        parco::one_matching_part(rest, |c| *c == '(')
658            .and(|_, rest| {
659                filler::parse(skip_whitespace(rest))
660                    .or(|| ErrorKind::KeyFillerExpectedInDictPair.into())
661            })
662            .and(|key, rest| {
663                filler::parse(skip_whitespace(rest))
664                    .or(|| ErrorKind::ValueFillerExpectedInDictPair.into())
665                    .and(|value, rest| {
666                        parco::one_matching_part(rest, |c| *c == ')')
667                            .or(|| ErrorKind::UnclosedDictPairParen.into())
668                            .and(|_, rest| ParsingResult::Ok(DictPair { key, value }, rest))
669                    })
670            })
671    }
672
673    pub fn parse(rest: &str) -> ParsingResult<Dict> {
674        parco::one_matching_part(rest, |c| *c == '[')
675            .and(|_, rest| {
676                parco::collect_repeating(Vec::new(), rest, |rest| {
677                    let rest = skip_whitespace(rest);
678                    parse_pair(rest).or(|| {
679                        parco::one_part(rest)
680                            .or(|| ErrorKind::UnclosedDictBracket.into())
681                            .and(|c, _rest| match c {
682                                ']' => ParsingResult::Err,
683                                _ => ErrorKind::DictPairExpectedInDict.into(),
684                            })
685                    })
686                })
687            })
688            .and(|pairs, rest| {
689                parco::one_matching_part(rest, |c| *c == ']')
690                    .or(|| unreachable!())
691                    .and(|_, rest| {
692                        ParsingResult::Ok(
693                            Dict {
694                                pairs: shrink(pairs),
695                            },
696                            rest,
697                        )
698                    })
699            })
700    }
701
702    #[cfg(test)]
703    mod tests {
704        use super::*;
705
706        fn string_filler(part: &str) -> Filler {
707            Filler::String(HzString {
708                parts: vec![HzStringPart::Raw(RawHzStringPart(part.to_owned()))],
709            })
710        }
711
712        #[test]
713        fn nothing() {
714            assert_eq!(parse(""), ParsingResult::Err);
715        }
716
717        #[test]
718        fn empty_dict() {
719            assert_eq!(
720                parse("[] something else"),
721                ParsingResult::Ok(Dict { pairs: vec![] }, " something else")
722            );
723        }
724
725        #[test]
726        fn unrelated_word() {
727            assert_eq!(parse("a"), ParsingResult::Err);
728        }
729
730        #[test]
731        fn correct_dict() {
732            assert_eq!(
733                parse(r#"[("a" "b") ("c" "d")] something else"#),
734                ParsingResult::Ok(
735                    Dict {
736                        pairs: vec![
737                            DictPair {
738                                key: string_filler("a"),
739                                value: string_filler("b")
740                            },
741                            DictPair {
742                                key: string_filler("c"),
743                                value: string_filler("d")
744                            },
745                        ]
746                    },
747                    " something else"
748                )
749            )
750        }
751
752        #[test]
753        fn unfinished_dict() {
754            assert_eq!(
755                parse("["),
756                ParsingResult::Fatal(ErrorKind::UnclosedDictBracket.into())
757            );
758        }
759
760        #[test]
761        fn unfinished_dict_2() {
762            assert_eq!(
763                parse("[("),
764                ParsingResult::Fatal(ErrorKind::KeyFillerExpectedInDictPair.into())
765            );
766        }
767
768        #[test]
769        fn unfinished_dict_3() {
770            assert_eq!(
771                parse(r#"[("a" "#),
772                ParsingResult::Fatal(ErrorKind::ValueFillerExpectedInDictPair.into())
773            );
774        }
775
776        #[test]
777        fn unfinished_dict_4() {
778            assert_eq!(
779                parse(r#"[("a" "b""#),
780                ParsingResult::Fatal(ErrorKind::UnclosedDictPairParen.into())
781            );
782        }
783
784        #[test]
785        fn unfinished_dict_5() {
786            assert_eq!(
787                parse(r#"[("a" "b")"#),
788                ParsingResult::Fatal(ErrorKind::UnclosedDictBracket.into())
789            );
790        }
791
792        #[test]
793        fn word_in_unfinished_dict() {
794            assert_eq!(
795                parse(r#"[("a" "b" a"#),
796                ParsingResult::Fatal(ErrorKind::UnclosedDictPairParen.into())
797            );
798        }
799
800        #[test]
801        fn word_in_unfinished_dict_2() {
802            assert_eq!(
803                parse(r#"[("a" "b") a"#),
804                ParsingResult::Fatal(ErrorKind::DictPairExpectedInDict.into())
805            );
806        }
807
808        #[test]
809        fn unexpected_word() {
810            assert_eq!(
811                parse("[a"),
812                ParsingResult::Fatal(ErrorKind::DictPairExpectedInDict.into())
813            );
814        }
815
816        #[test]
817        fn unexpected_word_2() {
818            assert_eq!(
819                parse("[(a"),
820                ParsingResult::Fatal(ErrorKind::KeyFillerExpectedInDictPair.into())
821            );
822        }
823
824        #[test]
825        fn unexpected_word_3() {
826            assert_eq!(
827                parse(r#"[("a" a"#),
828                ParsingResult::Fatal(ErrorKind::ValueFillerExpectedInDictPair.into())
829            );
830        }
831    }
832}
833
834mod line {
835    use super::*;
836
837    fn map_unexpected_character(c: char, rest: &str) -> Result<Comment, ErrorKind> {
838        match c {
839            '}' => Err(ErrorKind::UnexpectedClosingBraceInLine),
840            ']' => Err(ErrorKind::UnexpectedClosingBracketInLine),
841            ')' => Err(ErrorKind::UnexpectedClosingParenInLine),
842            '|' => Ok(Comment(rest.to_owned())),
843            _ => unreachable!(),
844        }
845    }
846
847    pub fn parse(unindented: &str) -> Result<Line, ErrorKind> {
848        if unindented.is_empty() {
849            Ok(Line::Empty { comment: None })
850        } else {
851            match name::parse(unindented) {
852                ParsingResult::Ok(name, rest) => {
853                    let rest = skip_whitespace(rest);
854                    let action_invocation = ActionInvocation { name };
855                    match parco::Input::take_one_part(&rest) {
856                        None => Ok(Line::Filled {
857                            action_invocation,
858                            comment: None,
859                            attached: Vec::new(),
860                        }),
861                        Some((unexpected_character, rest)) => {
862                            map_unexpected_character(unexpected_character, rest).map(|comment| {
863                                Line::Filled {
864                                    action_invocation,
865                                    comment: Some(comment),
866                                    attached: Vec::new(),
867                                }
868                            })
869                        }
870                    }
871                }
872                ParsingResult::Err => match parco::Input::take_one_part(&unindented) {
873                    None => unreachable!(),
874                    Some((unexpected_character, rest)) => {
875                        map_unexpected_character(unexpected_character, rest).map(|comment| {
876                            Line::Empty {
877                                comment: Some(comment),
878                            }
879                        })
880                    }
881                },
882                ParsingResult::Fatal(error) => Err(error),
883            }
884        }
885    }
886
887    #[cfg(test)]
888    mod tests {
889        use super::*;
890
891        #[test]
892        fn unexpected_brace() {
893            assert_eq!(parse("a}"), Err(ErrorKind::UnexpectedClosingBraceInLine));
894        }
895
896        #[test]
897        fn unexpected_brace_2() {
898            assert_eq!(parse("}"), Err(ErrorKind::UnexpectedClosingBraceInLine));
899        }
900
901        #[test]
902        fn unexpected_paren() {
903            assert_eq!(parse("a)"), Err(ErrorKind::UnexpectedClosingParenInLine));
904        }
905
906        #[test]
907        fn unexpected_paren_2() {
908            assert_eq!(parse(")"), Err(ErrorKind::UnexpectedClosingParenInLine));
909        }
910
911        #[test]
912        fn unexpected_bracket() {
913            assert_eq!(parse("a]"), Err(ErrorKind::UnexpectedClosingBracketInLine));
914        }
915
916        #[test]
917        fn unexpected_bracket_2() {
918            assert_eq!(parse("]"), Err(ErrorKind::UnexpectedClosingBracketInLine));
919        }
920
921        #[test]
922        fn empty_line() {
923            assert_eq!(parse(""), Ok(Line::Empty { comment: None }));
924        }
925
926        #[test]
927        fn correct_line() {
928            assert_eq!(
929                parse("a b | c"),
930                Ok(Line::Filled {
931                    attached: vec![],
932                    comment: Some(Comment(" c".to_owned())),
933                    action_invocation: ActionInvocation {
934                        name: Name {
935                            parts: vec![
936                                NamePart::Word(Word("a".to_owned())),
937                                NamePart::Word(Word("b".to_owned()))
938                            ]
939                        }
940                    }
941                })
942            );
943        }
944
945        #[test]
946        fn just_a_comment() {
947            assert_eq!(
948                parse("|abc"),
949                Ok(Line::Empty {
950                    comment: Some(Comment("abc".to_owned()))
951                })
952            );
953        }
954    }
955}
956
957mod program {
958    use super::*;
959
960    fn unindent(mut line: &str) -> (usize, &str) {
961        let mut level = 0;
962        loop {
963            line = line.trim_start_matches(char::is_whitespace);
964            line = match line.strip_prefix("-") {
965                Some(line) => {
966                    level += 1;
967                    line
968                }
969                None => break (level, line),
970            }
971        }
972    }
973
974    pub fn parse(program: &str) -> Result<Program, Error> {
975        let mut program = program.lines().enumerate().peekable();
976        let mut root = Vec::new();
977        let mut levels: Vec<*mut Vec<Line>> = Vec::new();
978        while let Some((index, line)) = program.next() {
979            let (level, unindented) = unindent(line);
980            if level != 0 && index == 0 {
981                return Err(Error {
982                    line_index: index,
983                    kind: ErrorKind::FirstLineIndented {
984                        present_indentation: level,
985                    },
986                });
987            }
988            let line = line::parse(skip_whitespace(unindented)).map_err(|error_kind| Error {
989                line_index: index,
990                kind: error_kind,
991            })?;
992            let line = match levels.iter_mut().rev().next() {
993                Some(level) => {
994                    let level: &mut _ = unsafe { &mut **level };
995                    level.push(line);
996                    level.last_mut().unwrap()
997                }
998                None => {
999                    root.push(line);
1000                    root.last_mut().unwrap()
1001                }
1002            };
1003            if let Some((next_index, next_line)) = program.peek() {
1004                let (next_level, _next_unindented) = unindent(next_line);
1005                match &line {
1006                    Line::Filled { attached, .. } => {
1007                        if next_level == level + 1 {
1008                            levels.push(attached as *const _ as *mut _);
1009                        } else if next_level > level {
1010                            return Err(Error {
1011                                line_index: *next_index,
1012                                kind: ErrorKind::OverindentedLine {
1013                                    max_allowed_indentation: level + 1,
1014                                    present_indentation: next_level,
1015                                },
1016                            });
1017                        }
1018                    }
1019                    Line::Empty { .. } => {
1020                        if next_level > level {
1021                            return Err(Error {
1022                                line_index: *next_index,
1023                                kind: ErrorKind::IndentationLevelIncreasedAfterEmptyLine {
1024                                    max_allowed_indentation: level,
1025                                    present_indentation: next_level,
1026                                },
1027                            });
1028                        }
1029                    }
1030                }
1031                if let Some(rollback) = level.checked_sub(next_level) {
1032                    for _ in 0..rollback {
1033                        levels.pop().unwrap();
1034                    }
1035                }
1036            }
1037        }
1038        Ok(Program { contents: root })
1039    }
1040
1041    #[cfg(test)]
1042    mod tests {
1043        use super::*;
1044
1045        fn line(contents: Vec<NamePart>, attached: Vec<Line>) -> Line {
1046            Line::Filled {
1047                attached,
1048                action_invocation: ActionInvocation {
1049                    name: Name { parts: contents },
1050                },
1051                comment: None,
1052            }
1053        }
1054
1055        #[test]
1056        fn empty_program() {
1057            assert_eq!(parse(""), Ok(Program { contents: vec![] }));
1058        }
1059
1060        #[test]
1061        #[cfg_attr(rustfmt, rustfmt_skip)]
1062        fn correct_indentation() {
1063            fn word(word: &str) -> NamePart {
1064                NamePart::Word(Word(word.to_owned()))
1065            }
1066
1067            assert_eq!(
1068                parse("a-d\n-b\n-  -   c\n  --d\ne"),
1069                Ok(Program { contents: vec![
1070                    line(
1071                        Vec::from([word("a-d")]),
1072                        vec![
1073                            line(Vec::from([word("b")]), vec![
1074                                 line(Vec::from([word("c")]), vec![]),
1075                                 line(Vec::from([word("d")]), vec![])
1076                            ]),
1077                        ],
1078                    ),
1079                    line(Vec::from([word("e")]), vec![]),
1080                ]})
1081            );
1082        }
1083
1084        #[test]
1085        fn first_line_indented() {
1086            assert_eq!(
1087                parse("-a"),
1088                Err(Error {
1089                    line_index: 0,
1090                    kind: ErrorKind::FirstLineIndented {
1091                        present_indentation: 1
1092                    }
1093                })
1094            );
1095        }
1096
1097        #[test]
1098        fn overindented() {
1099            assert_eq!(
1100                parse("a\n--b"),
1101                Err(Error {
1102                    line_index: 1,
1103                    kind: ErrorKind::OverindentedLine {
1104                        max_allowed_indentation: 1,
1105                        present_indentation: 2
1106                    }
1107                })
1108            );
1109        }
1110
1111        #[test]
1112        fn overindented_2() {
1113            assert_eq!(
1114                parse("a\n-b\n---c"),
1115                Err(Error {
1116                    line_index: 2,
1117                    kind: ErrorKind::OverindentedLine {
1118                        max_allowed_indentation: 2,
1119                        present_indentation: 3
1120                    }
1121                })
1122            );
1123        }
1124
1125        #[test]
1126        fn indentation_after_empty_line() {
1127            assert_eq!(
1128                parse("a\n-\n--b"),
1129                Err(Error {
1130                    line_index: 2,
1131                    kind: ErrorKind::IndentationLevelIncreasedAfterEmptyLine {
1132                        max_allowed_indentation: 1,
1133                        present_indentation: 2,
1134                    }
1135                })
1136            )
1137        }
1138    }
1139}
1140
1141pub fn parse(program: &str) -> Result<Program, Error> {
1142    program::parse(program)
1143}