1#![cfg_attr(feature = "html", feature(proc_macro_hygiene))]
18
19#[macro_use]
20extern crate nom;
21#[cfg(feature = "html")]
22extern crate maud;
23
24pub mod ast;
25pub mod generator;
26#[cfg(feature = "html")]
27pub mod html;
28pub mod stats;
29pub mod typography;
30
31use ast::*;
32pub use generator::render;
33use generator::Output;
34use typography::Typography;
35
36use nom::character::streaming::{alphanumeric1, anychar};
37
38const BARRIER_TOKENS: &str = "!?.\"«»`+*[]<>|_'’,;-—: \n\r\t ";
39
40macro_rules! cond_reduce (
41 ($input:expr, $cond:expr, $sub:ident!( $($args:tt)* )) => (
42 map_opt!($input, cond!($cond, $sub!($($args)*)), |x| x)
43 );
44);
45
46macro_rules! some (
48 ($input:expr, $sub:ident!( $($args:tt)* )) => (
49 many1!($input, complete!($sub!($($args)*)))
50 );
51 ($input:expr, $f:expr) => (
52 some!($input, call!($f))
53 );
54);
55
56macro_rules! consume_until (
61 ($input:expr, $arr:expr) => (
62 {
63 use nom::Err;
64 use nom::error::ErrorKind;
65
66 match take_till1!($input, |c| $arr.contains(c)) {
67 Err(Err::Incomplete(_)) => if $input.len() != 0 {
68 call!($input, nom::combinator::rest)
69 } else {
70 Err(Err::Error(error_position!($input, ErrorKind::TakeUntil)))
71 },
72 Ok((i, o)) => Ok((i, o)),
73 Err(e) => Err(e)
74 }
75 }
76 );
77);
78
79macro_rules! recover_incomplete (
82 ($i:expr, $submac:ident!( $($args:tt)* )) => (
83 {
84 use nom::lib::std::result::Result::*;
85 use nom::Err;
86
87 match $submac!($i, $($args)*) {
88 Err(Err::Incomplete(_)) => {
89 Ok(("", ()))
90 },
91 Ok((rest, _)) => {
92 Ok((rest, ()))
93 },
94 Err(rest) => Err(rest)
95 }
96 }
97 );
98);
99
100named!(white_spaces<&str, ()>,
101 recover_incomplete!(take_while!(|c| "\r\t ".contains(c)))
102);
103
104named!(blank<&str, ()>,
105 do_parse!(
106 white_spaces >>
107 opt!(do_parse!(char!('\n') >> white_spaces >> (()))) >>
108 (())
109 )
110);
111
112#[test]
113fn test_white_spaces() {
114 assert_eq!(white_spaces(","), Ok((",", ())));
115 assert_eq!(white_spaces(" ,"), Ok((",", ())));
116 assert_eq!(white_spaces(" "), Ok(("", ())));
117}
118
119named!(atom<&str, Atom>, do_parse!(
120 opt!(do_parse!(char!('\n') >> white_spaces >> (()))) >>
121 r: alt!( map!(consume_until!(BARRIER_TOKENS), Atom::Word)
122 | do_parse!(char!(';') >> (Atom::Punctuation(Mark::Semicolon)))
123 | do_parse!(char!(':') >> (Atom::Punctuation(Mark::Colon)))
124 | do_parse!(char!('?') >> (Atom::Punctuation(Mark::Question)))
125 | do_parse!(char!('!') >> (Atom::Punctuation(Mark::Exclamation)))
126 | do_parse!(tag!("---") >> (Atom::Punctuation(Mark::LongDash)))
127 | do_parse!(char!('—') >> (Atom::Punctuation(Mark::LongDash)))
128 | do_parse!(tag!("--") >> (Atom::Punctuation(Mark::Dash)))
129 | do_parse!(char!('–') >> (Atom::Punctuation(Mark::LongDash)))
130 | do_parse!(char!(',') >> (Atom::Punctuation(Mark::Comma)))
131 | do_parse!(char!('-') >> (Atom::Punctuation(Mark::Hyphen)))
132 | do_parse!(char!('…') >> (Atom::Punctuation(Mark::SuspensionPoints)))
133 | do_parse!(char!('.') >> some!(char!('.'))
134 >> (Atom::Punctuation(Mark::SuspensionPoints)))
135 | do_parse!(char!('.') >> (Atom::Punctuation(Mark::Point)))
136 | do_parse!(char!('\'') >> (Atom::Punctuation(Mark::Apostrophe)))
137 | do_parse!(char!('’') >> (Atom::Punctuation(Mark::Apostrophe)))
138 | do_parse!(char!('`') >> lw: take_until!("`")
139 >> char!('`')
140 >> (Atom::Word(lw)))
141 ) >>
142 white_spaces >>
143 (r)
144));
145
146#[test]
147fn test_atom() {
148 assert_eq!(atom(","), Ok(("", Atom::Punctuation(Mark::Comma))));
149 assert_eq!(atom(", "), Ok(("", Atom::Punctuation(Mark::Comma))));
150 assert_eq!(
151 atom("......."),
152 Ok(("", Atom::Punctuation(Mark::SuspensionPoints)))
153 );
154 assert_eq!(atom("’"), Ok(("", Atom::Punctuation(Mark::Apostrophe))));
155 assert_eq!(atom("`@test`"), Ok(("", Atom::Word("@test"))));
156 assert_eq!(atom("test"), Ok(("", Atom::Word("test"))));
157}
158
159named_args!(format_rec(in_strong: bool, in_emph: bool, in_quote: bool)<&str, Format>,
160 alt!( map!(some!(atom), Format::Raw)
161 | cond_reduce!(!in_strong, do_parse!(
162 opt!(do_parse!(char!('\n') >> white_spaces >> (()))) >>
163 char!('+') >>
164 white_spaces >>
165 st: some!(call!(format_rec, true, in_emph, in_quote)) >>
166 blank >>
167 char!('+') >>
168 white_spaces >>
169 (Format::StrongEmph(st))
170 ))
171 | cond_reduce!(!in_emph, do_parse!(
172 opt!(do_parse!(char!('\n') >> white_spaces >> (()))) >>
173 char!('*') >>
174 white_spaces >>
175 st: some!(call!(format_rec, in_strong, true, in_quote)) >>
176 blank >>
177 char!('*') >>
178 white_spaces >>
179 (Format::Emph(st))
180 ))
181 | cond_reduce!(!in_quote, do_parse!(
182 opt!(do_parse!(char!('\n') >> white_spaces >> (()))) >>
183 alt!(char!('"') | char!('«')) >>
184 white_spaces >>
185 st: some!(call!(format_rec, in_strong, in_emph, true)) >>
186 white_spaces >>
187 alt!(char!('"') | char!('»')) >>
188 white_spaces >>
189 (Format::Quote(st))
190 ))
191 )
192);
193
194named!(format<&str, Format>, call!(format_rec, false, false, false));
195
196#[test]
197fn test_format() {
198 assert_eq!(
199 format("Hi stranger, how are you?"),
200 Ok((
201 "",
202 Format::Raw(vec![
203 Atom::Word("Hi"),
204 Atom::Word("stranger"),
205 Atom::Punctuation(Mark::Comma),
206 Atom::Word("how"),
207 Atom::Word("are"),
208 Atom::Word("you"),
209 Atom::Punctuation(Mark::Question),
210 ])
211 ))
212 );
213
214 assert_eq!(
215 format(
216 r#"Hi stranger, how
217are you?"#
218 ),
219 Ok((
220 "",
221 Format::Raw(vec![
222 Atom::Word("Hi"),
223 Atom::Word("stranger"),
224 Atom::Punctuation(Mark::Comma),
225 Atom::Word("how"),
226 Atom::Word("are"),
227 Atom::Word("you"),
228 Atom::Punctuation(Mark::Question),
229 ])
230 ))
231 );
232
233 assert_eq!(
234 format(
235 r#"Hi stranger, how
236
237are you?"#
238 ),
239 Ok((
240 "\n\nare you?",
241 Format::Raw(vec![
242 Atom::Word("Hi"),
243 Atom::Word("stranger"),
244 Atom::Punctuation(Mark::Comma),
245 Atom::Word("how")
246 ])
247 ))
248 );
249
250 assert_eq!(
251 format("+Hi stranger+, how are you?"),
252 Ok((
253 ", how are you?",
254 Format::StrongEmph(vec![Format::Raw(vec![
255 Atom::Word("Hi"),
256 Atom::Word("stranger")
257 ])])
258 ))
259 );
260
261 assert_eq!(
262 format("+Hi *stranger*+, how are you?"),
263 Ok((
264 ", how are you?",
265 Format::StrongEmph(vec![
266 Format::Raw(vec![Atom::Word("Hi")]),
267 Format::Emph(vec![Format::Raw(vec![Atom::Word("stranger")])])
268 ])
269 ))
270 );
271
272 assert_eq!(format("+Hi *+ stranger*, how are you?").is_err(), true);
273}
274
275named_args!(reply(b: char, e: char)<&str, Reply>, do_parse!(
276 char!(b) >>
277 call!(white_spaces) >>
278 before: some!(format) >>
279 x: call!(anychar) >>
280 r: alt!( cond_reduce!(x == e, do_parse!((None)))
281 | cond_reduce!(x == '|', do_parse!(
282 call!(white_spaces) >>
283 prep: some!(format) >>
284 char!('|') >>
285 call!(white_spaces) >>
286 after: opt!(some!(format)) >>
287 char!(e) >>
288 (Some((prep, after)))
289 ))
290 ) >>
291 call!(white_spaces) >>
292 (match r {
293 None => {
294 Reply::Simple(before)
295 },
296 Some((prep, after)) => {
297 Reply::WithSay(before, prep, after)
298 }
299 })
300));
301
302#[test]
303fn test_reply() {
304 assert_eq!(
305 reply("[Hi stranger]", '[', ']'),
306 Ok((
307 "",
308 Reply::Simple(vec![Format::Raw(vec![
309 Atom::Word("Hi"),
310 Atom::Word("stranger"),
311 ])])
312 ))
313 );
314
315 assert_eq!(
316 reply("[Hi stranger,| they salute.|]", '[', ']'),
317 Ok((
318 "",
319 Reply::WithSay(
320 vec![Format::Raw(vec![
321 Atom::Word("Hi"),
322 Atom::Word("stranger"),
323 Atom::Punctuation(Mark::Comma)
324 ])],
325 vec![Format::Raw(vec![
326 Atom::Word("they"),
327 Atom::Word("salute"),
328 Atom::Punctuation(Mark::Point)
329 ])],
330 None
331 )
332 ))
333 );
334}
335
336named!(component<&str, Component>, alt! (
344 do_parse!(
345 tel: some!(format) >>
346 (Component::Teller(tel)))
347 | do_parse!(
348 blank >>
349 dial: call!(reply, '[' , ']') >>
350 by: opt!(complete!(do_parse!(
351 char!('(') >>
352 name: call!(alphanumeric1) >>
353 char!(')') >>
354 white_spaces >>
355 (name)))) >>
356 (Component::Dialogue(dial, by)))
357 | do_parse!(
358 blank >>
359 th: call!(reply, '<' , '>') >>
360 by: opt!(complete!(do_parse!(
361 char!('(') >>
362 name: call!(alphanumeric1) >>
363 char!(')') >>
364 white_spaces >>
365 (name)))) >>
366 (Component::Thought(th, by)))
367 | map!(consume_until!("\n"), Component::IllFormed)
368 )
369);
370
371#[test]
372fn test_component() {
373 assert_eq!(
374 component("[Hi]"),
375 Ok((
376 "",
377 Component::Dialogue(
378 Reply::Simple(vec![Format::Raw(vec![Atom::Word("Hi")])]),
379 None
380 )
381 ))
382 );
383
384 assert_eq!(
385 component("Hi stranger,\n*this* is me."),
386 Ok((
387 "",
388 Component::Teller(vec![
389 Format::Raw(vec![
390 Atom::Word("Hi"),
391 Atom::Word("stranger"),
392 Atom::Punctuation(Mark::Comma),
393 ]),
394 Format::Emph(vec![Format::Raw(vec![Atom::Word("this"),])]),
395 Format::Raw(vec![
396 Atom::Word("is"),
397 Atom::Word("me"),
398 Atom::Punctuation(Mark::Point)
399 ])
400 ])
401 ))
402 );
403
404 assert_eq!(
405 component("Hi stranger, this is me."),
406 Ok((
407 "",
408 Component::Teller(vec![Format::Raw(vec![
409 Atom::Word("Hi"),
410 Atom::Word("stranger"),
411 Atom::Punctuation(Mark::Comma),
412 Atom::Word("this"),
413 Atom::Word("is"),
414 Atom::Word("me"),
415 Atom::Punctuation(Mark::Point)
416 ])])
417 ))
418 );
419
420 assert_eq!(
421 component("[Hi](alice)"),
422 Ok((
423 "",
424 Component::Dialogue(
425 Reply::Simple(vec![Format::Raw(vec![Atom::Word("Hi")])]),
426 Some("alice")
427 )
428 ))
429 );
430
431 assert_eq!(
432 component("[Hi \ntest\n\n"),
433 Ok(("\ntest\n\n", Component::IllFormed("[Hi ")))
434 );
435}
436
437named!(empty_line<&str, ()>, do_parse!(
438 white_spaces >>
439 char!('\n') >>
440 white_spaces >>
441 (())
442));
443
444named!(
445 paragraph<&str, Paragraph>,
446 do_parse!(
447 not!(peek!(one_of!("_="))) >>
448 p: some!(component) >>
449 many0!(complete!(empty_line)) >>
450 (Paragraph(p))
451 )
452);
453
454#[test]
455fn test_paragraph() {
456 assert_eq!(
457 paragraph("+Hi+"),
458 Ok((
459 "",
460 Paragraph(vec![Component::Teller(vec![Format::StrongEmph(vec![
461 Format::Raw(vec![Atom::Word("Hi")]),
462 ])])])
463 ))
464 );
465
466 assert_eq!(
467 paragraph("[Hi stranger, this is me.] Indeed.\n\n[Hi]"),
468 Ok((
469 "[Hi]",
470 Paragraph(vec![
471 Component::Dialogue(
472 Reply::Simple(vec![Format::Raw(vec![
473 Atom::Word("Hi"),
474 Atom::Word("stranger"),
475 Atom::Punctuation(Mark::Comma),
476 Atom::Word("this"),
477 Atom::Word("is"),
478 Atom::Word("me"),
479 Atom::Punctuation(Mark::Point)
480 ])]),
481 None
482 ),
483 Component::Teller(vec![Format::Raw(vec![
484 Atom::Word("Indeed"),
485 Atom::Punctuation(Mark::Point)
486 ])])
487 ])
488 ))
489 );
490}
491
492named_args!(
493 search_recovery_point_rec<'a>(acc: &mut Vec<&'a str>)<&'a str, ()>,
494 alt!(
495 map!(some!(empty_line), |_| ())
496 | do_parse!(
497 l: consume_until!("\n") >>
498 do_parse!(({ acc.push(l) })) >>
499 char!('\n') >>
500 call!(search_recovery_point_rec, acc) >>
501 (())
502 )
503 )
504);
505
506fn search_recovery_point<'input>(
507 input: &'input str,
508) -> nom::IResult<&'input str, Vec<&'input str>> {
509 let mut acc = vec![];
510 match search_recovery_point_rec(input, &mut acc) {
511 Ok((input, _)) => Ok((input, acc)),
512 Err(_) => Ok(("", acc)),
513 }
514}
515
516#[test]
517fn test_recovery() {
518 assert_eq!(
519 search_recovery_point(
520 r#"We need
521to try.
522
523Recover!"#
524 ),
525 Ok(("Recover!", vec!["We need", "to try."]))
526 );
527}
528
529named!(
530 section<&str, Section>, do_parse!(
531 res: alt!(
532 complete!(do_parse!(
533 some!(char!('_')) >>
534 cls: opt!(
535 do_parse!(
536 cls: call!(alphanumeric1) >>
537 some!(char!('_')) >>
538 (cls)
539 )
540 ) >>
541 some!(empty_line) >>
542 sec: some!(paragraph) >>
543 some!(char!('_')) >>
544 (Section::Aside(cls, sec))
545 ))
546 | do_parse!(
547 opt!(do_parse!(some!(char!('=')) >> some!(empty_line) >> (()))) >>
548 r: map!(some!(paragraph), Section::Story) >>
549 (r)
550 )
551 | map_opt!(search_recovery_point, |x: Vec<_>| if !x.is_empty() { Some(Section::IllFormed(x)) } else { None } )
552 ) >>
553 many0!(complete!(empty_line)) >>
554 (res)
555));
556
557#[test]
558fn test_section() {
559 assert!(section("").is_err());
560
561 assert_eq!(
562 section("+\nHi \n +"),
563 Ok((
564 "",
565 Section::Story(vec![Paragraph(vec![Component::Teller(vec![
566 Format::StrongEmph(vec![Format::Raw(vec![Atom::Word("Hi")])])
567 ])])])
568 ))
569 );
570
571 assert_eq!(
572 section("+Hi+"),
573 Ok((
574 "",
575 Section::Story(vec![Paragraph(vec![Component::Teller(vec![
576 Format::StrongEmph(vec![Format::Raw(vec![Atom::Word("Hi")])])
577 ])])])
578 ))
579 );
580
581 assert_eq!(
582 section(
583 r#"_____letter____
584Dear friend.
585
586I love you.
587_______________"#
588 ),
589 Ok((
590 "",
591 Section::Aside(
592 Some("letter"),
593 vec![
594 Paragraph(vec![Component::Teller(vec![Format::Raw(vec![
595 Atom::Word("Dear"),
596 Atom::Word("friend"),
597 Atom::Punctuation(Mark::Point)
598 ])])]),
599 Paragraph(vec![Component::Teller(vec![Format::Raw(vec![
600 Atom::Word("I"),
601 Atom::Word("love"),
602 Atom::Word("you"),
603 Atom::Punctuation(Mark::Point)
604 ])])])
605 ]
606 )
607 ))
608 );
609
610 assert_eq!(
611 section(
612 r#"_____letter____
613Dear friend.
614
615I love you."#
616 ),
617 Ok((
618 "I love you.",
619 Section::IllFormed(vec!["_____letter____", "Dear friend."])
620 ))
621 );
622
623 assert_eq!(
624 section(r#"Dear friend."#),
625 Ok((
626 "",
627 Section::Story(vec![Paragraph(vec![Component::Teller(vec![Format::Raw(
628 vec![
629 Atom::Word("Dear"),
630 Atom::Word("friend"),
631 Atom::Punctuation(Mark::Point)
632 ]
633 )])])])
634 ))
635 );
636}
637
638named!(
639 document<&str, Document>, do_parse!(
640 opt!(complete!(blank)) >>
641 many0!(complete!(empty_line)) >>
642 x: many0!(section) >>
643 (Document(x))
644 )
645);
646
647#[test]
648fn test_empty_document() {
649 assert_eq!(document(""), Ok(("", Document(vec![]))));
650}
651
652#[test]
653fn test_document_with_leading_ws() {
654 assert_eq!(
655 document(" \n \n She opened the letter."),
656 Ok((
657 "",
658 Document(vec![Section::Story(vec![Paragraph(vec![
659 Component::Teller(vec![Format::Raw(vec![
660 Atom::Word("She"),
661 Atom::Word("opened"),
662 Atom::Word("the"),
663 Atom::Word("letter"),
664 Atom::Punctuation(Mark::Point),
665 ])])
666 ])])])
667 ))
668 );
669}
670
671#[test]
672fn test_incomplete_aside() {
673 assert_eq!(
674 document("________________"),
675 Ok((
676 "",
677 Document(vec![Section::IllFormed(vec!["________________"])])
678 ))
679 );
680}
681
682#[test]
683fn test_document() {
684 assert_eq!(
685 document(
686 r#"She opened the letter.
687
688======
689
690She cry."#
691 ),
692 Ok((
693 "",
694 Document(vec![
695 Section::Story(vec![Paragraph(vec![Component::Teller(vec![Format::Raw(
696 vec![
697 Atom::Word("She"),
698 Atom::Word("opened"),
699 Atom::Word("the"),
700 Atom::Word("letter"),
701 Atom::Punctuation(Mark::Point),
702 ]
703 )])])]),
704 Section::Story(vec![Paragraph(vec![Component::Teller(vec![Format::Raw(
705 vec![
706 Atom::Word("She"),
707 Atom::Word("cry"),
708 Atom::Punctuation(Mark::Point),
709 ]
710 )])])]),
711 ])
712 ))
713 );
714 assert_eq!(
715 document(
716 r#"She opened the letter.
717
718======She cry."#
719 ),
720 Ok((
721 "",
722 Document(vec![
723 Section::Story(vec![Paragraph(vec![Component::Teller(vec![Format::Raw(
724 vec![
725 Atom::Word("She"),
726 Atom::Word("opened"),
727 Atom::Word("the"),
728 Atom::Word("letter"),
729 Atom::Punctuation(Mark::Point),
730 ]
731 )])])]),
732 Section::IllFormed(vec!["======She cry."])
733 ])
734 ))
735 );
736
737 assert_eq!(
738 document(
739 r#"She opened the letter, and read it.
740
741_____letter____
742Dear friend.
743
744I love you.
745_______________"#
746 ),
747 Ok((
748 "",
749 Document(vec![
750 Section::Story(vec![Paragraph(vec![Component::Teller(vec![Format::Raw(
751 vec![
752 Atom::Word("She"),
753 Atom::Word("opened"),
754 Atom::Word("the"),
755 Atom::Word("letter"),
756 Atom::Punctuation(Mark::Comma),
757 Atom::Word("and"),
758 Atom::Word("read"),
759 Atom::Word("it"),
760 Atom::Punctuation(Mark::Point)
761 ]
762 )])])]),
763 Section::Aside(
764 Some("letter"),
765 vec![
766 Paragraph(vec![Component::Teller(vec![Format::Raw(vec![
767 Atom::Word("Dear"),
768 Atom::Word("friend"),
769 Atom::Punctuation(Mark::Point)
770 ])])]),
771 Paragraph(vec![Component::Teller(vec![Format::Raw(vec![
772 Atom::Word("I"),
773 Atom::Word("love"),
774 Atom::Word("you"),
775 Atom::Punctuation(Mark::Point)
776 ])])])
777 ]
778 )
779 ])
780 ))
781 );
782}
783
784#[derive(PartialEq, Eq, Debug)]
785pub enum Error<'input> {
786 IncompleteParsing(Document<'input>, &'input str),
787 ParsingError,
788}
789
790pub fn parse(input: &str) -> Result<Document, Error> {
791 match document(input) {
792 Ok(("", res)) => Ok(res),
793 Ok((rest, res)) => Err(Error::IncompleteParsing(res, rest)),
794 _ => Err(Error::ParsingError),
795 }
796}
797
798pub fn compile<'input, O: Output, T: Typography + ?Sized>(
799 input: &'input str,
800 typo: &T,
801) -> Result<O, Error<'input>> {
802 let mut out = O::empty(input.len());
803
804 render(&parse(input)?, typo, &mut out);
805
806 Ok(out)
807}
808
809#[test]
810fn test_render() {
811 use stats::Digest;
812 use typography::ENGLISH;
813
814 let res: Digest = compile(r#"Hi everyone."#, &ENGLISH).unwrap();
815
816 assert_eq!(res.words_count, 2);
817
818 let res: Digest = compile(r#"Hi everyone. +My name is.. Suly+."#, &ENGLISH).unwrap();
819
820 assert_eq!(res.signs_count, 3);
821
822 let res: Digest = compile(
823 r#"Hi everyone.
824
825 +My name is.. Suly+.
826
827____test____
828
829What is your name?
830____________"#,
831 &ENGLISH,
832 )
833 .unwrap();
834
835 assert_eq!(res.spaces_count, 7);
836
837 let res: Digest = compile(
838 r#"Hi everyone.
839
840[+My name is.. Suly+.](john)
841
842[Really?](merida)
843
844[Yay](john)
845
846____test____
847
848What is your name?
849____________"#,
850 &ENGLISH,
851 )
852 .unwrap();
853
854 assert_eq!(
855 res.characters,
856 [String::from("john"), String::from("merida")]
857 .iter()
858 .cloned()
859 .collect()
860 );
861}