1use std::borrow::Cow;
2
3use crate::constants::{BACKSLASH, COMMA, HYPHEN, LPAREN, NEWLINE, RBRACE, RPAREN, UNDERSCORE};
4use crate::node_pool::NodeID;
5use crate::parse::parse_element;
6use crate::types::{Cursor, MatchError, ParseOpts, Parseable, Parser, Result};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct MacroCall<'a> {
10 pub name: &'a str,
11 pub args: Vec<Cow<'a, str>>,
12}
13
14impl<'a> Parseable<'a> for MacroCall<'a> {
15 fn parse(
16 parser: &mut Parser<'a>,
17 mut cursor: Cursor<'a>,
18 parent: Option<NodeID>,
19 mut parse_opts: ParseOpts,
20 ) -> Result<NodeID> {
21 let start = cursor.index;
22
23 cursor.word("{{{")?;
24
25 if !cursor.curr().is_ascii_alphabetic() {
26 return Err(MatchError::InvalidLogic);
27 }
28
29 let name_match = cursor.fn_while(|chr: u8| {
30 chr.is_ascii_alphanumeric()
32 || chr == HYPHEN
33 || chr == UNDERSCORE
34 && (chr != LPAREN
36 || chr != RBRACE)
38 })?;
39
40 cursor.index = name_match.end;
43
44 match cursor.curr() {
45 LPAREN => {
46 cursor.next();
48 let mut arg_vec: Vec<Cow<str>> = Vec::new();
49 let mut prev_ind = cursor.index;
50 let mut join_prev = false;
54
55 loop {
56 match cursor.try_curr()? {
58 NEWLINE => {
59 parse_opts.from_paragraph = true;
60 parse_opts.list_line = false;
61 parse_opts.from_object = false;
62 match parse_element(parser, cursor.adv_copy(1), parent, parse_opts) {
63 Ok(_) => return Err(MatchError::InvalidLogic),
64 Err(MatchError::InvalidLogic) => {}
65 ret @ Err(_) => return ret,
66 }
67 }
68 RBRACE => {
69 if cursor.word("}}}").is_ok() {
70 return Err(MatchError::InvalidLogic);
71 }
72 }
73 RPAREN => {
74 let arg = cursor.clamp_backwards(prev_ind);
76
77 if !cursor.word(")}}}").is_ok() {
79 cursor.next();
80 continue;
81 }
82
83 if join_prev {
84 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
85 a.push_str(arg);
86 }
87 } else {
88 arg_vec.push(Cow::Borrowed(arg));
89 }
90
91 return Ok(parser.alloc(
92 MacroCall {
93 name: name_match.obj,
94 args: arg_vec,
95 },
96 start,
97 cursor.index,
98 parent,
99 ));
100 }
101 COMMA => {
102 if cursor.peek_rev(1)? != BACKSLASH {
103 if join_prev {
104 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
105 a.push_str(cursor.clamp_backwards(prev_ind));
106 }
107 join_prev = false;
108 } else {
109 arg_vec.push(cursor.clamp_backwards(prev_ind).into());
110 }
111 } else {
112 let mut pushee =
114 cursor.clamp(prev_ind, cursor.index - 1).to_owned();
115 pushee.push(COMMA as char);
116
117 if join_prev {
118 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
119 a.push_str(&pushee);
120 }
121 } else {
122 arg_vec.push(pushee.into());
123 }
124 join_prev = true;
125 }
126
127 prev_ind = cursor.index + 1;
128 }
129 _ => {}
130 }
131 cursor.next();
132 }
133 }
134 RBRACE => {
135 cursor.word("}}}")?;
136 Ok(parser.alloc(
137 MacroCall {
138 name: name_match.obj,
139 args: Vec::new(),
140 },
141 start,
142 cursor.index,
143 parent,
144 ))
145 }
146 _ => Err(MatchError::InvalidLogic),
147 }
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use std::borrow::Cow;
154
155 use pretty_assertions::assert_eq;
156
157 use crate::{
158 element::{ArgNumOrText, MacroDef},
159 expr_in_pool,
160 object::MacroCall,
161 parse_org,
162 types::Expr,
163 };
164
165 #[test]
166 fn basic_macro() {
167 let input = r"{{{abc}}}";
168 let parsed = parse_org(input);
169 let l = expr_in_pool!(parsed, Macro).unwrap();
170 assert_eq!(
171 l,
172 &MacroCall {
173 name: "abc",
174 args: Vec::new()
175 }
176 )
177 }
178
179 #[test]
180 fn macro_with_args() {
181 let input = r"{{{poem(cool, three)}}}";
182 let parsed = parse_org(input);
183 let l = expr_in_pool!(parsed, Macro).unwrap();
184 assert_eq!(
185 l,
186 &MacroCall {
187 name: "poem",
188 args: vec!["cool".into(), " three".into()]
189 }
190 )
191 }
192
193 #[test]
194 fn basic_macro_def() {
195 let input = r"#+macro: poem hiii $1 $2 text
196";
197 let parsed = parse_org(input);
198 let l = expr_in_pool!(parsed, MacroDef).unwrap();
199 assert_eq!(
200 l,
201 &MacroDef {
202 num_args: 2,
203 input: vec![
204 ArgNumOrText::Text("hiii "),
205 ArgNumOrText::ArgNum(1),
206 ArgNumOrText::Text(" "),
207 ArgNumOrText::ArgNum(2),
208 ArgNumOrText::Text(" text")
209 ],
210 name: "poem"
211 }
212 )
213 }
214
215 #[test]
216 fn repeated_macro_def() {
217 let input = r"#+macro: poem $1 $1 text
218";
219 let parsed = parse_org(input);
220 let l = expr_in_pool!(parsed, MacroDef).unwrap();
221 assert_eq!(
222 l,
223 &MacroDef {
224 num_args: 1,
225 input: vec![
226 ArgNumOrText::Text(""),
227 ArgNumOrText::ArgNum(1),
228 ArgNumOrText::Text(" "),
229 ArgNumOrText::ArgNum(1),
230 ArgNumOrText::Text(" text")
231 ],
232 name: "poem"
233 }
234 )
235 }
236
237 #[test]
238 fn combined_macros() {
239 let input = r"#+macro: poem hiii $1 $2 text
240
241{{{poem(cool, three)}}}
242";
243 let pool = parse_org(input);
244 pool.print_tree();
245 }
246
247 #[test]
248 fn macro_escape() {
249 let input = r"{{{poem(cool\, three)}}}";
250 let parsed = parse_org(input);
251 let l = expr_in_pool!(parsed, Macro).unwrap();
252 assert_eq!(
253 l,
254 &MacroCall {
255 name: "poem",
256 args: vec![Cow::Borrowed("cool, three"),]
257 }
258 )
259 }
260
261 #[test]
262 fn macro_multiple_escape() {
263 let input = r"{{{poem(cool\, \, \, \, three)}}}";
264 let parsed = parse_org(input);
265 let l = expr_in_pool!(parsed, Macro).unwrap();
266 assert_eq!(
267 l,
268 &MacroCall {
269 name: "poem",
270 args: vec![Cow::Borrowed("cool, , , , three"),]
271 }
272 )
273 }
274
275 #[test]
276 fn macro_nested_params() {
277 let input = r"{{{i(things (inside things) other things)}}}";
278 let parsed = parse_org(input);
279 let l = expr_in_pool!(parsed, Macro).unwrap();
280 assert_eq!(
281 l,
282 &MacroCall {
283 name: "i",
284 args: vec![Cow::Borrowed("things (inside things) other things"),]
285 }
286 )
287 }
288
289 #[test]
290 fn macro_almost_nested_params() {
291 let input = r"{{{i(things (inside things other things)}}}";
292 let parsed = parse_org(input);
293 let l = expr_in_pool!(parsed, Macro).unwrap();
294 assert_eq!(
295 l,
296 &MacroCall {
297 name: "i",
298 args: vec![Cow::Borrowed("things (inside things other things"),]
299 }
300 )
301 }
302}