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}