summaryrefslogtreecommitdiff
path: root/src/eval/walk.rs
blob: cd9809a29be9876e03c820fcfe780ffd35fd096f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::rc::Rc;

use super::{Eval, EvalContext, Str, Template, Value};
use crate::diag::TypResult;
use crate::geom::Align;
use crate::layout::{BlockLevel, ParChild, ParNode, Spacing, StackChild, StackNode};
use crate::syntax::*;
use crate::util::BoolExt;

/// Walk markup, filling the currently built template.
pub trait Walk {
    /// Walk the node.
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>;
}

impl Walk for Markup {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        for node in self.iter() {
            node.walk(ctx)?;
        }
        Ok(())
    }
}

impl Walk for MarkupNode {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        match self {
            Self::Space => ctx.template.space(),
            Self::Linebreak(_) => ctx.template.linebreak(),
            Self::Parbreak(_) => ctx.template.parbreak(),
            Self::Strong(_) => ctx.template.modify(|s| s.text_mut().strong.flip()),
            Self::Emph(_) => ctx.template.modify(|s| s.text_mut().emph.flip()),
            Self::Text(text) => ctx.template.text(text),
            Self::Raw(raw) => raw.walk(ctx)?,
            Self::Heading(heading) => heading.walk(ctx)?,
            Self::List(list) => list.walk(ctx)?,
            Self::Enum(enum_) => enum_.walk(ctx)?,
            Self::Expr(expr) => match expr.eval(ctx)? {
                Value::None => {}
                Value::Int(v) => ctx.template.text(format_str!("{}", v)),
                Value::Float(v) => ctx.template.text(format_str!("{}", v)),
                Value::Str(v) => ctx.template.text(v),
                Value::Template(v) => ctx.template += v,
                // For values which can't be shown "naturally", we print the
                // representation in monospace.
                other => ctx.template.monospace(other.repr()),
            },
        }
        Ok(())
    }
}

impl Walk for RawNode {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        if self.block {
            ctx.template.parbreak();
        }

        ctx.template.monospace(&self.text);

        if self.block {
            ctx.template.parbreak();
        }

        Ok(())
    }
}

impl Walk for HeadingNode {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        let level = self.level;
        let body = self.body.eval(ctx)?;

        ctx.template.parbreak();
        ctx.template.save();
        ctx.template.modify(move |style| {
            let text = style.text_mut();
            let upscale = 1.6 - 0.1 * level as f64;
            text.size *= upscale;
            text.strong = true;
        });
        ctx.template += body;
        ctx.template.restore();
        ctx.template.parbreak();

        Ok(())
    }
}

impl Walk for ListNode {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        let body = self.body.eval(ctx)?;
        walk_item(ctx, Str::from('•'), body);
        Ok(())
    }
}

impl Walk for EnumNode {
    fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
        let body = self.body.eval(ctx)?;
        let label = format_str!("{}.", self.number.unwrap_or(1));
        walk_item(ctx, label, body);
        Ok(())
    }
}

fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
    ctx.template += Template::from_block(move |style| {
        let label = ParNode {
            dir: style.dir,
            leading: style.leading(),
            children: vec![ParChild::Text(
                (&label).into(),
                style.aligns.inline,
                Rc::clone(&style.text),
            )],
        };
        StackNode {
            dir: style.dir,
            children: vec![
                StackChild::Node(label.pack(), Align::Start),
                StackChild::Spacing(Spacing::Linear((style.text.size / 2.0).into())),
                StackChild::Node(body.to_stack(&style).pack(), Align::Start),
            ],
        }
    });
}