~lthms/ogmarkup

feaf8d0cbc7aa34ac521020914df91489ef24595 — Thomas Letan 3 years ago a7323e5
refactor: Drop the [Renderer] trait in favor of [Output]

The rational behind this choice is to try use less allocation. Rather
than aggregating small pieces of output together, we use the same
variable that we pass as a mutable reference during the generation.

The key insight here is that it shall be more efficient.
3 files changed, 401 insertions(+), 540 deletions(-)

M src/generator.rs
M src/lib.rs
M src/stats.rs
M src/generator.rs => src/generator.rs +305 -426
@@ 1,36 1,61 @@
use ast::*;
use typography::{choose, previous_dialogue, Space, Typography};

pub trait Renderer<O> {
    fn append(&self, O, O) -> O;
    fn empty(&self) -> O;
pub trait Output {
    fn empty(input_size: usize) -> Self;

    fn render_space(&self, space: Space) -> O;
    fn render_word(&self, word: &str) -> O;
    fn render_mark(&self, mark: &str) -> O;
    fn render_illformed(&self, err: &str) -> O;
    fn render_space(&mut self, space: Space) -> ();
    fn render_word(&mut self, word: &str) -> ();
    fn render_mark(&mut self, mark: &str) -> ();
    fn render_illformed(&mut self, err: &str) -> ();

    fn emph_template(&self, format: O) -> O;
    fn strong_emph_template(&self, format: O) -> O;
    fn emph_template<F>(&mut self, format: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn reply_template(&self, reply: O, author: &Option<&str>) -> O;
    fn strong_emph_template<F>(&mut self, format: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn thought_template(&self, reply: O, author: &Option<&str>) -> O;
    fn dialogue_template(&self, reply: O, author: &Option<&str>) -> O;
    fn between_dialogue(&self) -> O;
    fn illformed_inline_template(&self, err: O) -> O;
    fn reply_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn paragraph_template(&self, para: O) -> O;
    fn thought_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn illformed_block_template(&self, err: O) -> O;
    fn story_template(&self, err: O) -> O;
    fn aside_template<'input>(&self, cls: &Option<&'input str>, err: O) -> O;
    fn dialogue_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn render_atom<T: Typography>(&self, atom: &Atom, typo: &T) -> O {
    fn between_dialogue(&mut self) -> ();

    fn illformed_inline_template<F>(&mut self, err: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn paragraph_template<F>(&mut self, para: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn illformed_block_template<F>(&mut self, err: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn story_template<F>(&mut self, err: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn aside_template<'input, F>(&mut self, cls: &Option<&'input str>, err: F) -> ()
    where
        F: FnOnce(&mut Self) -> ();

    fn render_atom<T: Typography>(&mut self, atom: &Atom, typo: &T) -> () {
        match atom {
            Atom::Punctuation(ref p) => self.render_mark(typo.output(p)),
            Atom::Word(w) => self.render_word(w),
            Atom::Void => self.empty(),
            Atom::Void => (),
        }
    }
}


@@ 70,128 95,98 @@ impl<'ast, 'input: 'ast> Memory<'ast, 'input> {
}

trait Renderable<'ast, 'input: 'ast> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O;
    ) -> ();

    fn render_one_shot<O, T: Typography, R: Renderer<O>>(&'ast self, typo: &T, renderer: &R) -> O {
        let mut start_with = Memory::init();
        self.render(typo, renderer, &mut start_with)
    fn render_one_shot<O: Output, T: Typography>(&'ast self, typo: &T, out: &mut O) -> () {
        self.render(typo, out, &mut Memory::init())
    }
}

impl<'ast, 'input: 'ast, A: Renderable<'ast, 'input>> Renderable<'ast, 'input> for Vec<A> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
        let mut res_str = renderer.empty();

    ) -> () {
        for el in self {
            let seg = el.render(typo, renderer, mem);
            res_str = renderer.append(res_str, seg);
            el.render(typo, out, mem);
        }

        res_str
    }
}

impl<'ast, 'input: 'ast> Renderable<'ast, 'input> for Atom<'input> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
    ) -> () {
        let space = choose(typo.after_atom(mem.atom), typo.before_atom(self));

        mem.remember_atom(self);
        out.render_space(space);
        out.render_atom(self, typo);

        renderer.append(
            renderer.render_space(space),
            renderer.render_atom(self, typo),
        )
        // TODO: be sure it is not before rendering
        mem.remember_atom(self);
    }
}

impl<'ast, 'input: 'ast> Renderable<'ast, 'input> for Format<'input> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
    ) -> () {
        match self {
            Format::Raw(atoms) => atoms.render(typo, renderer, mem),
            Format::Emph(atoms) => renderer.emph_template(atoms.render(typo, renderer, mem)),
            Format::Raw(atoms) => atoms.render(typo, out, mem),
            Format::Emph(atoms) => out.emph_template(|out| atoms.render(typo, out, mem)),
            Format::StrongEmph(atoms) => {
                renderer.strong_emph_template(atoms.render(typo, renderer, mem))
                out.strong_emph_template(|out| atoms.render(typo, out, mem))
            }
            Format::Quote(atoms) => {
                let before = Atom::Punctuation(Mark::OpenQuote).render(typo, renderer, mem);
                let content = atoms.render(typo, renderer, mem);
                let after = Atom::Punctuation(Mark::CloseQuote).render(typo, renderer, mem);

                renderer.append(renderer.append(before, content), after)
                Atom::Punctuation(Mark::OpenQuote).render(typo, out, mem);
                atoms.render(typo, out, mem);
                Atom::Punctuation(Mark::CloseQuote).render(typo, out, mem);
            }
        }
    }
}

impl<'ast, 'input: 'ast> Reply<'input> {
    fn render_reply<O, T: Typography, R: Renderer<O>>(
    fn render_reply<O: Output, T: Typography>(
        &'ast self,
        author: &Option<&'input str>,
        typo: &T,
        renderer: &R,
        out: &mut O,
        open: Option<&'static Atom<'static>>,
        close: Option<&'static Atom<'static>>,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
    ) -> () {
        match self {
            Reply::Simple(atoms) => {
                let o1 = open
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());
                let o2 = renderer.reply_template(atoms.render(typo, renderer, mem), author);
                let o3 = close
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());

                renderer.append(o1, renderer.append(o2, o3))
                open.map(|x| x.render(typo, out, mem));
                out.reply_template(|out| atoms.render(typo, out, mem), author);
                close.map(|x| x.render(typo, out, mem));
            }
            Reply::WithSay(atoms, insert, None) => {
                let o1 = open
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());
                let o2 = renderer.reply_template(atoms.render(typo, renderer, mem), author);
                let o3 = close
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());
                let o4 = insert.render(typo, renderer, mem);

                renderer.append(o1, renderer.append(o2, renderer.append(o3, o4)))
                open.map(|x| x.render(typo, out, mem));
                out.reply_template(|out| atoms.render(typo, out, mem), author);
                close.map(|x| x.render(typo, out, mem));
                insert.render(typo, out, mem);
            }
            Reply::WithSay(atoms1, insert, Some(atoms2)) => {
                let o1 = open
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());
                let o2 = renderer.reply_template(atoms1.render(typo, renderer, mem), author);
                let o3 = insert.render(typo, renderer, mem);
                let o4 = renderer.reply_template(atoms2.render(typo, renderer, mem), author);
                let o5 = close
                    .map(|x| x.render(typo, renderer, mem))
                    .unwrap_or_else(|| renderer.empty());

                renderer.append(
                    o1,
                    renderer.append(o2, renderer.append(o3, renderer.append(o4, o5))),
                )
                open.map(|x| x.render(typo, out, mem));
                out.reply_template(|out| atoms1.render(typo, out, mem), author);
                insert.render(typo, out, mem);
                out.reply_template(|out| atoms2.render(typo, out, mem), author);
                close.map(|x| x.render(typo, out, mem));
            }
        }
    }


@@ 205,21 200,22 @@ impl<'ast, 'input: 'ast> Component<'input> {
        }
    }

    fn render_component<O, T: Typography, R: Renderer<O>>(
    fn render_component<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        will_be_dialog: bool,
        is_last: bool,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
        let res = match self {
            Component::Teller(atoms) => atoms.render(typo, renderer, mem),
    ) -> () {
        match self {
            Component::Teller(atoms) => atoms.render(typo, out, mem),
            Component::IllFormed(err) => {
                renderer.illformed_inline_template(renderer.render_illformed(err))
                // TODO: Is it really what we want to do?
                out.illformed_inline_template(|out| out.render_illformed(err))
            }
            Component::Thought(reply, cls) => renderer.thought_template(
                reply.render_reply(cls, typo, renderer, None, None, mem),
            Component::Thought(reply, cls) => out.thought_template(
                |out| reply.render_reply(cls, typo, out, None, None, mem),
                cls,
            ),
            Component::Dialogue(reply, cls) => {


@@ 227,64 223,50 @@ impl<'ast, 'input: 'ast> Component<'input> {
                let o = typo.open_dialog(was_dialogue);
                let e = typo.close_dialog(will_be_dialog);

                let res = renderer
                    .dialogue_template(reply.render_reply(cls, typo, renderer, o, e, mem), cls);
                out.dialogue_template(|out| reply.render_reply(cls, typo, out, o, e, mem), cls);

                let sep = if will_be_dialog && !is_last {
                if will_be_dialog && !is_last {
                    mem.reset_atom();
                    renderer.between_dialogue()
                } else {
                    renderer.empty()
                };

                renderer.append(res, sep)
                    out.between_dialogue();
                }
            }
        };

        mem.remember_dialogue(self.is_dialog());

        res
    }
}

impl<'ast, 'input: 'ast> Renderable<'ast, 'input> for Paragraph<'input> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
        let mut res = renderer.empty();

        for i in 0..self.0.len() {
            let is_last = i + 1 >= self.0.len();
            let will_be_dialogue = if !is_last {
                self.0[i + 1].is_dialog().is_some()
            } else {
                mem.next_paragraph_starts_with_dialogue
            };

            let comp: &'ast Component<'input> = &self.0[i];
            res = renderer.append(
                res,
                comp.render_component(typo, renderer, will_be_dialogue, is_last, mem),
            );
        }
    ) -> () {
        out.paragraph_template(|out| {
            for i in 0..self.0.len() {
                let is_last = i + 1 >= self.0.len();
                let will_be_dialogue = if !is_last {
                    self.0[i + 1].is_dialog().is_some()
                } else {
                    mem.next_paragraph_starts_with_dialogue
                };

        mem.reset_atom();
                let comp: &'ast Component<'input> = &self.0[i];
                comp.render_component(typo, out, will_be_dialogue, is_last, mem);
            }
        });

        renderer.paragraph_template(res)
        mem.reset_atom();
    }
}

fn render_paragraphs<'ast, 'input: 'ast, O, T: Typography, R: Renderer<O>>(
fn render_paragraphs<'ast, 'input: 'ast, O: Output, T: Typography>(
    ast: &'ast [Paragraph<'input>],
    typo: &T,
    renderer: &R,
    out: &mut O,
    mem: &mut Memory<'ast, 'input>,
) -> O {
    let mut res = renderer.empty();

) -> () {
    for i in 0..ast.len() {
        mem.next_paragraph_starts_with_dialogue = if i + 1 < ast.len() {
            ast[i + 1].0[0].is_dialog().is_some()


@@ 292,57 274,56 @@ fn render_paragraphs<'ast, 'input: 'ast, O, T: Typography, R: Renderer<O>>(
            false
        };

        res = renderer.append(res, ast[i].render(typo, renderer, mem));
        ast[i].render(typo, out, mem);
    }

    res
}

impl<'ast, 'input: 'ast> Renderable<'ast, 'input> for Section<'input> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
    ) -> () {
        mem.reset();

        match self {
            Section::Story(atoms) => {
                renderer.story_template(render_paragraphs(atoms, typo, renderer, mem))
                out.story_template(|out| render_paragraphs(atoms, typo, out, mem));
            }
            Section::Aside(cls, atoms) => {
                renderer.aside_template(cls, render_paragraphs(atoms, typo, renderer, mem))
                out.aside_template(cls, |out| render_paragraphs(atoms, typo, out, mem));
            }
            Section::IllFormed(vec) => {
                renderer.illformed_block_template(vec.iter().fold(renderer.empty(), |res, x| {
                    renderer.append(res, renderer.render_illformed(x))
                }))
                out.illformed_block_template(|out| {
                    for l in vec {
                        out.render_illformed(l);
                    }
                });
            }
        }
    }
}

impl<'ast, 'input: 'ast> Renderable<'ast, 'input> for Document<'input> {
    fn render<O, T: Typography, R: Renderer<O>>(
    fn render<O: Output, T: Typography>(
        &'ast self,
        typo: &T,
        renderer: &R,
        out: &mut O,
        mem: &mut Memory<'ast, 'input>,
    ) -> O {
    ) -> () {
        match self {
            Document(atoms) => atoms.render(typo, renderer, mem),
            Document(atoms) => atoms.render(typo, out, mem),
        }
    }
}

pub fn render<'ast, 'input: 'ast, O, T: Typography, R: Renderer<O>>(
pub fn render<'ast, 'input: 'ast, O: Output, T: Typography>(
    doc: &'ast Document<'input>,
    typo: &T,
    renderer: &R,
) -> O {
    let mut start_with = Memory::init();
    doc.render(typo, renderer, &mut start_with)
    out: &mut O,
) -> () {
    doc.render(typo, out, &mut Memory::init())
}

#[cfg(test)]


@@ 351,334 332,232 @@ pub mod test {

    use typography::FRENCH;

    pub struct Html;
    #[derive(Debug, PartialEq, Eq)]
    struct Html(String);

    impl Renderer<String> for Html {
        fn append(&self, before: String, after: String) -> String {
            let mut before = before;
            before.push_str(after.as_ref());

            before
    impl Html {
        fn push_str(&mut self, s: &str) -> () {
            self.0.push_str(s);
        }
    }

        fn empty(&self) -> String {
            String::from("")
    impl Output for Html {
        fn empty(_: usize) -> Html {
            Html(String::from(""))
        }

        fn render_space(&self, space: Space) -> String {
        fn render_space(&mut self, space: Space) -> () {
            match space {
                Space::Normal => String::from(" "),
                Space::Nbsp => String::from("&nbsp;"),
                Space::None => String::from(""),
                Space::Normal => self.push_str(" "),
                Space::Nbsp => self.push_str("&nbsp;"),
                Space::None => (),
            }
        }

        fn render_word(&self, word: &str) -> String {
            String::from(word)
        fn render_word(&mut self, word: &str) -> () {
            self.push_str(word);
        }

        fn render_mark(&self, mark: &str) -> String {
            String::from(mark)
        fn render_mark(&mut self, mark: &str) -> () {
            self.push_str(mark);
        }

        fn render_illformed(&self, err: &str) -> String {
            String::from(err)
        fn render_illformed(&mut self, err: &str) -> () {
            self.push_str(err);
        }

        fn emph_template(&self, format: String) -> String {
            format!("<em>{}</em>", format)
        fn emph_template<F>(&mut self, format: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<em>");
            format(self);
            self.push_str("</em>");
        }

        fn strong_emph_template(&self, format: String) -> String {
            format!("<strong>{}</strong>", format)
        fn strong_emph_template<F>(&mut self, format: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<strong>");
            format(self);
            self.push_str("</strong>");
        }

        fn reply_template(&self, reply: String, _author: &Option<&str>) -> String {
            format!("<span div=\"reply\">{}</span>", reply)
        fn reply_template<F>(&mut self, reply: F, _author: &Option<&str>) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<span div=\"reply\">");
            reply(self);
            self.push_str("</span>");
        }

        fn thought_template(&self, reply: String, author: &Option<&str>) -> String {
            format!(
                "<span div=\"thought{}\">{}</span>",
                author
                    .map(|x| format!(" by-{}", x))
                    .unwrap_or(String::from("")),
                reply,
            )
        fn thought_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<span div=\"thought");
            author.map(|a| {
                self.push_str(" by-");
                self.push_str(a)
            });
            self.push_str("\">");
            reply(self);
            self.push_str("</span>");
        }

        fn dialogue_template(&self, reply: String, author: &Option<&str>) -> String {
            format!(
                "<span div=\"dialogue{}\">{}</span>",
                author
                    .map(|x| format!(" by-{}", x))
                    .unwrap_or(String::from("")),
                reply,
            )
        fn dialogue_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<span div=\"dialogue");
            author.map(|a| {
                self.push_str(" by-");
                self.push_str(a)
            });
            self.push_str("\">");
            reply(self);
            self.push_str("</span>");
        }

        fn between_dialogue(&self) -> String {
            String::from("</p><p>")
        fn between_dialogue(&mut self) -> () {
            self.push_str("</p><p>");
        }

        fn illformed_inline_template(&self, err: String) -> String {
            format!("<span div=\"illformed_inline\">{}</span>", err)
        fn illformed_inline_template<F>(&mut self, err: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<span div=\"illformed_inline\">");
            err(self);
            self.push_str("</span>");
        }

        fn paragraph_template(&self, para: String) -> String {
            format!("<p>{}</p>", para)
        fn paragraph_template<F>(&mut self, para: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<p>");
            para(self);
            self.push_str("</p>");
        }

        fn illformed_block_template(&self, err: String) -> String {
            format!("<div class=\"illformed_block\">{}</div>", err)
        fn illformed_block_template<F>(&mut self, err: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<div class=\"illformed_block\">");
            err(self);
            self.push_str("</div>");
        }

        fn story_template(&self, story: String) -> String {
            format!("<div class=\"story\">{}</div>", story)
        fn story_template<F>(&mut self, story: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<div class\"story\">");
            story(self);
            self.push_str("</div>");
        }

        fn aside_template(&self, cls: &Option<&str>, aside: String) -> String {
            format!("<div class=\"aside {}\">{}</div>", cls.unwrap_or(""), aside)
        fn aside_template<F>(&mut self, cls: &Option<&str>, aside: F) -> ()
        where
            F: FnOnce(&mut Html) -> (),
        {
            self.push_str("<div class=\"aside");
            cls.map(|x| {
                self.push_str(" ");
                self.push_str(x);
            });
            self.push_str("\">");
            aside(self);
            self.push_str("</div>");
        }
    }

    #[test]
    fn test_generation() {
        let html: Html = Html;
        let mut out = Html::empty(0);

        assert_eq!(
            vec![Atom::Word("Bonjour"), Atom::Word("toi")].render_one_shot(&FRENCH, &html),
            "Bonjour toi"
        );
        vec![Atom::Word("Bonjour"), Atom::Word("toi")].render_one_shot(&FRENCH, &mut out);
        assert_eq!(out.0, "Bonjour toi");

        assert_eq!(
            Format::StrongEmph(vec![Format::Raw(vec![
                Atom::Word("Bonjour"),
                Atom::Word("toi")
            ])])
            .render_one_shot(&FRENCH, &html),
            "<strong>Bonjour toi</strong>"
        );
        out = Html::empty(0);

        assert_eq!(
            Format::Quote(vec![Format::Raw(vec![
                Atom::Word("Bonjour"),
                Atom::Word("toi")
            ])])
            .render_one_shot(&FRENCH, &html),
            "«&nbsp;Bonjour toi&nbsp;»"
        );
        Format::StrongEmph(vec![Format::Raw(vec![
            Atom::Word("Bonjour"),
            Atom::Word("toi"),
        ])])
        .render_one_shot(&FRENCH, &mut out);

        assert_eq!(
            Paragraph(
                vec![
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Salut")
                                    ]
                                )
                            ]
                        ),
                        None
                    ),
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Bonjour")
                                    ]
                                )
                            ]
                        ),
                        None
                    )
                ]
            ).render_one_shot(&FRENCH, &html),
            "<p><span div=\"dialogue\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue\">—<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );
        assert_eq!(out.0, "<strong>Bonjour toi</strong>");

        assert_eq!(
            Paragraph(
                vec![
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Salut")
                                    ]
                                )
                            ]
                        ),
                        Some("foo")
                    ),
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Bonjour")
                                    ]
                                )
                            ]
                        ),
                        Some("foo")
                    )
                ]
            ).render_one_shot(&FRENCH, &html),
            "<p><span div=\"dialogue by-foo\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue by-foo\">»<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );
        out = Html::empty(0);

        assert_eq!(
            Paragraph(
                vec![
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Salut")
                                    ]
                                )
                            ]
                        ),
                        None
                    ),
                    Component::Dialogue(
                        Reply::Simple(
                            vec![
                                Format::Raw(
                                    vec![
                                        Atom::Word("Bonjour")
                                    ]
                                )
                            ]
                        ),
                        Some("foo")
                    )
                ]
            ).render_one_shot(&FRENCH, &html),
            "<p><span div=\"dialogue\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue by-foo\">—<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );
    }
}
        Format::Quote(vec![Format::Raw(vec![
            Atom::Word("Bonjour"),
            Atom::Word("toi"),
        ])])
        .render_one_shot(&FRENCH, &mut out);

impl<R, A: Renderer<R>, S, B: Renderer<S>> Renderer<(R, S)> for (A, B) {
    fn append(&self, before: (R, S), after: (R, S)) -> (R, S) {
        (
            self.0.append(before.0, after.0),
            self.1.append(before.1, after.1),
        )
    }
        assert_eq!(out.0, "«&nbsp;Bonjour toi&nbsp;»");

    fn empty(&self) -> (R, S) {
        (self.0.empty(), self.1.empty())
    }
        out = Html::empty(0);

    fn render_space(&self, space: Space) -> (R, S) {
        match space {
            Space::Normal => (
                self.0.render_space(Space::Normal),
                self.1.render_space(Space::Normal),
            ),
            Space::Nbsp => (
                self.0.render_space(Space::Nbsp),
                self.1.render_space(Space::Nbsp),
        Paragraph(vec![
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Salut")])]),
                None,
            ),
            Space::None => (
                self.0.render_space(Space::None),
                self.1.render_space(Space::None),
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Bonjour")])]),
                None,
            ),
        }
    }

    fn render_word(&self, word: &str) -> (R, S) {
        (self.0.render_word(word), self.1.render_word(word))
    }

    fn render_mark(&self, mark: &str) -> (R, S) {
        (self.0.render_mark(mark), self.1.render_mark(mark))
    }

    fn render_illformed(&self, err: &str) -> (R, S) {
        (self.0.render_illformed(err), self.1.render_illformed(err))
    }

    fn emph_template(&self, format: (R, S)) -> (R, S) {
        (
            self.0.emph_template(format.0),
            self.1.emph_template(format.1),
        )
    }

    fn strong_emph_template(&self, format: (R, S)) -> (R, S) {
        (
            self.0.strong_emph_template(format.0),
            self.1.strong_emph_template(format.1),
        )
    }

    fn reply_template(&self, reply: (R, S), author: &Option<&str>) -> (R, S) {
        (
            self.0.reply_template(reply.0, author),
            self.1.reply_template(reply.1, author),
        )
    }

    fn thought_template(&self, reply: (R, S), author: &Option<&str>) -> (R, S) {
        (
            self.0.thought_template(reply.0, author),
            self.1.thought_template(reply.1, author),
        )
    }
        ])
        .render_one_shot(&FRENCH, &mut out);

    fn dialogue_template(&self, reply: (R, S), author: &Option<&str>) -> (R, S) {
        (
            self.0.dialogue_template(reply.0, author),
            self.1.dialogue_template(reply.1, author),
        )
    }
        assert_eq!(
            out.0,
            "<p><span div=\"dialogue\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue\">—<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );

    fn between_dialogue(&self) -> (R, S) {
        (self.0.between_dialogue(), self.1.between_dialogue())
    }
        out = Html::empty(0);

    fn illformed_inline_template(&self, err: (R, S)) -> (R, S) {
        (
            self.0.illformed_inline_template(err.0),
            self.1.illformed_inline_template(err.1),
        )
    }
        Paragraph(vec![
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Salut")])]),
                Some("foo"),
            ),
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Bonjour")])]),
                Some("foo"),
            ),
        ])
        .render_one_shot(&FRENCH, &mut out);

    fn paragraph_template(&self, para: (R, S)) -> (R, S) {
        (
            self.0.paragraph_template(para.0),
            self.1.paragraph_template(para.1),
        )
    }
        assert_eq!(
            out.0,
            "<p><span div=\"dialogue by-foo\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue by-foo\">»<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );

    fn illformed_block_template(&self, err: (R, S)) -> (R, S) {
        (
            self.0.illformed_block_template(err.0),
            self.1.illformed_block_template(err.1),
        )
    }
        out = Html::empty(0);

    fn story_template(&self, story: (R, S)) -> (R, S) {
        (
            self.0.story_template(story.0),
            self.1.story_template(story.1),
        )
    }
        Paragraph(vec![
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Salut")])]),
                None,
            ),
            Component::Dialogue(
                Reply::Simple(vec![Format::Raw(vec![Atom::Word("Bonjour")])]),
                Some("foo"),
            ),
        ])
        .render_one_shot(&FRENCH, &mut out);

    fn aside_template(&self, cls: &Option<&str>, aside: (R, S)) -> (R, S) {
        (
            self.0.aside_template(cls, aside.0),
            self.1.aside_template(cls, aside.1),
        )
        assert_eq!(
            out.0,
            "<p><span div=\"dialogue\">«<span div=\"reply\">&nbsp;Salut</span></span></p><p><span div=\"dialogue by-foo\">—<span div=\"reply\"> Bonjour</span>&nbsp;»</span></p>"
        );
    }
}

M src/lib.rs => src/lib.rs +29 -35
@@ 14,7 14,7 @@ pub mod typography;

use ast::*;
pub use generator::render;
use generator::Renderer;
use generator::Output;
use typography::Typography;

use nom::character::streaming::{alphanumeric1, anychar};


@@ 779,36 779,32 @@ pub fn parse(input: &str) -> Result<Document, Error> {
    }
}

pub fn compile<'input, O, T: Typography, R: Renderer<O>>(
pub fn compile<'input, O: Output, T: Typography>(
    input: &'input str,
    typo: &T,
    renderer: &R,
) -> Result<O, Error<'input>> {
    Ok(render(&parse(input)?, typo, renderer))
    let mut out = O::empty(input.len());

    render(&parse(input)?, typo, &mut out);

    Ok(out)
}

#[test]
fn test_render() {
    use stats::Stats;
    use stats::Digest;
    use typography::ENGLISH;

    assert_eq!(
        compile(r#"Hi everyone."#, &ENGLISH, &Stats)
            .unwrap()
            .words_count,
        2
    );
    let res: Digest = compile(r#"Hi everyone."#, &ENGLISH).unwrap();

    assert_eq!(
        compile(r#"Hi everyone. +My name is.. Suly+."#, &ENGLISH, &Stats)
            .unwrap()
            .signs_count,
        3
    );
    assert_eq!(res.words_count, 2);

    assert_eq!(
        compile(
            r#"Hi everyone.
    let res: Digest = compile(r#"Hi everyone. +My name is.. Suly+."#, &ENGLISH).unwrap();

    assert_eq!(res.signs_count, 3);

    let res: Digest = compile(
        r#"Hi everyone.

 +My name is.. Suly+.



@@ 816,17 812,14 @@ ____test____

What is your name?
____________"#,
            &ENGLISH,
            &Stats
        )
        .unwrap()
        .spaces_count,
        7
    );
        &ENGLISH,
    )
    .unwrap();

    assert_eq!(
        compile(
            r#"Hi everyone.
    assert_eq!(res.spaces_count, 7);

    let res: Digest = compile(
        r#"Hi everyone.

[+My name is.. Suly+.](john)



@@ 838,11 831,12 @@ ____test____

What is your name?
____________"#,
            &ENGLISH,
            &Stats
        )
        .unwrap()
        .characters,
        &ENGLISH,
    )
    .unwrap();

    assert_eq!(
        res.characters,
        [String::from("john"), String::from("merida")]
            .iter()
            .cloned()

M src/stats.rs => src/stats.rs +67 -79
@@ 1,4 1,4 @@
use generator::Renderer;
use generator::Output;
use std::collections::HashSet;
use typography::Space;



@@ 11,27 11,8 @@ pub struct Digest {
    pub characters: HashSet<String>,
}

fn join(set1: HashSet<String>, set2: HashSet<String>) -> HashSet<String> {
    if set2.is_empty() {
        set1
    } else if set1.is_empty() {
        set2
    } else {
        set1.union(&set2).cloned().collect()
    }
}

impl Renderer<Digest> for Stats {
    fn append(&self, d1: Digest, d2: Digest) -> Digest {
        Digest {
            words_count: d1.words_count + d2.words_count,
            signs_count: d1.signs_count + d2.signs_count,
            spaces_count: d1.spaces_count + d2.spaces_count,
            characters: join(d1.characters, d2.characters),
        }
    }

    fn empty(&self) -> Digest {
impl Output for Digest {
    fn empty(_: usize) -> Digest {
        Digest {
            words_count: 0,
            signs_count: 0,


@@ 40,91 21,98 @@ impl Renderer<Digest> for Stats {
        }
    }

    fn render_space(&self, space: Space) -> Digest {
        Digest {
            words_count: 0,
            signs_count: 0,
            spaces_count: match space {
                Space::None => 0,
                _ => 1,
            },
            characters: HashSet::new(),
    fn render_space(&mut self, space: Space) -> () {
        match space {
            Space::None => (),
            _ => {
                self.spaces_count += 1;
            }
        }
    }

    fn render_word(&self, _word: &str) -> Digest {
        Digest {
            words_count: 1,
            signs_count: 0,
            spaces_count: 0,
            characters: HashSet::new(),
        }
    fn render_word(&mut self, _word: &str) -> () {
        self.words_count += 1;
    }

    fn render_mark(&self, _mark: &str) -> Digest {
        Digest {
            words_count: 0,
            signs_count: 1,
            spaces_count: 0,
            characters: HashSet::new(),
        }
    fn render_mark(&mut self, _mark: &str) -> () {
        self.signs_count += 1;
    }

    fn render_illformed(&self, _err: &str) -> Digest {
        self.empty()
    }
    fn render_illformed(&mut self, _err: &str) -> () {}

    fn emph_template(&self, format: Digest) -> Digest {
        format
    fn emph_template<F>(&mut self, format: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        format(self)
    }

    fn strong_emph_template(&self, format: Digest) -> Digest {
        format
    fn strong_emph_template<F>(&mut self, format: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        format(self)
    }

    fn reply_template(&self, reply: Digest, _author: &Option<&str>) -> Digest {
        reply
    fn reply_template<F>(&mut self, reply: F, _author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        reply(self)
    }

    fn thought_template(&self, reply: Digest, author: &Option<&str>) -> Digest {
        let mut reply = reply;
        if let Some(author) = author {
            reply.characters.insert(author.to_string());
        }
    fn thought_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        // TODO: allocate only if necessary
        author.map(|a| self.characters.insert(a.to_string()));

        reply
        reply(self);
    }

    fn dialogue_template(&self, reply: Digest, author: &Option<&str>) -> Digest {
        let mut reply = reply;
        if let Some(author) = author {
            reply.characters.insert(author.to_string());
        }
    fn dialogue_template<F>(&mut self, reply: F, author: &Option<&str>) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        // TODO: allocate only if necessary
        author.map(|a| self.characters.insert(a.to_string()));

        reply
        reply(self);
    }

    fn between_dialogue(&self) -> Digest {
        self.empty()
    }
    fn between_dialogue(&mut self) -> () {}

    fn illformed_inline_template(&self, err: Digest) -> Digest {
        err
    fn illformed_inline_template<F>(&mut self, _err: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
    }

    fn paragraph_template(&self, para: Digest) -> Digest {
        para
    fn paragraph_template<F>(&mut self, para: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        para(self);
    }

    fn illformed_block_template(&self, err: Digest) -> Digest {
        err
    fn illformed_block_template<F>(&mut self, _err: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
    }

    fn story_template(&self, err: Digest) -> Digest {
        err
    fn story_template<F>(&mut self, story: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        story(self);
    }

    fn aside_template(&self, _cls: &Option<&str>, err: Digest) -> Digest {
        err
    fn aside_template<F>(&mut self, _cls: &Option<&str>, aside: F) -> ()
    where
        F: FnOnce(&mut Digest) -> (),
    {
        aside(self)
    }
}