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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
use std::rc::Rc;
use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code.
#[derive(Debug, Clone, PartialEq)]
pub enum Node {
/// Plain text.
Text(String),
/// Whitespace containing less than two newlines.
Space,
/// A forced line break: `\`.
Linebreak(Span),
/// A paragraph break: Two or more newlines.
Parbreak(Span),
/// Strong text was enabled / disabled: `*`.
Strong(Span),
/// Emphasized text was enabled / disabled: `_`.
Emph(Span),
/// A section heading: `= Introduction`.
Heading(HeadingNode),
/// A raw block with optional syntax highlighting: `` `...` ``.
Raw(RawNode),
/// An expression.
Expr(Expr),
}
impl Node {
// The names of the corresponding library functions.
pub const LINEBREAK: &'static str = "linebreak";
pub const PARBREAK: &'static str = "parbreak";
pub const STRONG: &'static str = "strong";
pub const EMPH: &'static str = "emph";
pub const HEADING: &'static str = "heading";
pub const RAW: &'static str = "raw";
/// Desugar markup into a function call.
pub fn desugar(&self) -> Option<CallExpr> {
match *self {
Self::Text(_) => None,
Self::Space => None,
Self::Linebreak(span) => Some(call(span, Self::LINEBREAK)),
Self::Parbreak(span) => Some(call(span, Self::PARBREAK)),
Self::Strong(span) => Some(call(span, Self::STRONG)),
Self::Emph(span) => Some(call(span, Self::EMPH)),
Self::Heading(ref heading) => Some(heading.desugar()),
Self::Raw(ref raw) => Some(raw.desugar()),
Self::Expr(_) => None,
}
}
}
/// A section heading: `= Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct HeadingNode {
/// The source code location.
pub span: Span,
/// The section depth (numer of equals signs).
pub level: usize,
/// The contents of the heading.
pub contents: Rc<Tree>,
}
impl HeadingNode {
pub const LEVEL: &'static str = "level";
pub const BODY: &'static str = "body";
/// Desugar into a function call.
pub fn desugar(&self) -> CallExpr {
let Self { span, level, ref contents } = *self;
let mut call = call(span, Node::HEADING);
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::LEVEL),
expr: Expr::Int(span, level as i64),
}));
call.args.items.push(CallArg::Pos(Expr::Template(TemplateExpr {
span,
tree: Rc::clone(&contents),
})));
call
}
}
/// A raw block with optional syntax highlighting: `` `...` ``.
///
/// Raw blocks start with 1 or 3+ backticks and end with the same number of
/// backticks.
///
/// When using at least three backticks, an optional language tag may follow
/// directly after the backticks. This tag defines which language to
/// syntax-highlight the text in. Apart from the language tag and some
/// whitespace trimming discussed below, everything inside a raw block is
/// rendered verbatim, in particular, there are no escape sequences.
///
/// # Examples
/// - Raw text is surrounded by backticks.
/// ```typst
/// `raw`
/// ```
/// - An optional language tag may follow directly at the start when the block
/// is surrounded by at least three backticks.
/// ````typst
/// ```rust println!("hello!")```;
/// ````
/// - Blocks can span multiple lines.
/// ````typst
/// ```rust
/// loop {
/// find_yak().shave();
/// }
/// ```
/// ````
/// - Start with a space to omit the language tag (the space will be trimmed
/// from the output).
/// `````typst
/// ```` This has no leading space.````
/// `````
/// - Use more backticks to allow backticks in the raw text.
/// `````typst
/// ```` This contains ```backticks```.````
/// `````
///
/// # Trimming
/// If we would always render the raw text between the backticks exactly as
/// given, some things would become cumbersome/impossible to write:
/// - Typical multiline code blocks (like in the example above) would have an
/// additional newline before and after the code.
/// - Multi-line blocks would need to start with a space since a word would be
/// interpreted as a language tag.
/// - Text ending with a backtick would be impossible since the backtick would
/// be interpreted as belonging to the closing backticks.
///
/// To fix these problems, we sometimes trim a bit of space from blocks with 3+
/// backticks:
/// - At the start, we trim a single space or a sequence of whitespace followed
/// by a newline.
/// - At the end, we trim
/// - a single space if the raw text ends with a backtick followed only by
/// whitespace,
/// - a newline followed by a sequence of whitespace.
///
/// You can thus produce a single backtick without surrounding spaces with the
/// sequence ```` ``` ` ``` ````.
///
/// Note that with these rules you can always force leading or trailing
/// whitespace simply by adding more spaces.
#[derive(Debug, Clone, PartialEq)]
pub struct RawNode {
/// The source code location.
pub span: Span,
/// An optional identifier specifying the language to syntax-highlight in.
pub lang: Option<Ident>,
/// The raw text, determined as the raw string between the backticks trimmed
/// according to the above rules.
pub text: String,
/// Whether the element is block-level, that is, it has 3+ backticks
/// and contains at least one newline.
pub block: bool,
}
impl RawNode {
pub const LANG: &'static str = "lang";
pub const BLOCK: &'static str = "block";
pub const TEXT: &'static str = "text";
/// Desugar into a function call.
pub fn desugar(&self) -> CallExpr {
let Self { span, ref lang, ref text, block } = *self;
let mut call = call(span, Node::RAW);
if let Some(lang) = lang {
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::LANG),
expr: Expr::Str(span, lang.string.clone()),
}));
}
call.args.items.push(CallArg::Named(Named {
name: ident(span, Self::BLOCK),
expr: Expr::Bool(span, block),
}));
call.args.items.push(CallArg::Pos(Expr::Str(span, text.clone())));
call
}
}
fn call(span: Span, name: &str) -> CallExpr {
CallExpr {
span,
callee: Box::new(Expr::Ident(Ident { span, string: name.into() })),
args: CallArgs { span, items: vec![] },
}
}
fn ident(span: Span, string: &str) -> Ident {
Ident { span, string: string.into() }
}
|