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()? {
57 NEWLINE => {
58 parse_opts.from_paragraph = true;
59 parse_opts.list_line = false;
60 parse_opts.from_object = false;
61 match parse_element(parser, cursor.adv_copy(1), parent, parse_opts) {
62 Ok(_) => return Err(MatchError::InvalidLogic),
63 Err(MatchError::InvalidLogic) => {}
64 ret @ Err(_) => return ret,
65 }
66 }
67 RBRACE => {
68 if cursor.word("}}}").is_ok() {
69 return Err(MatchError::InvalidLogic);
70 }
71 }
72 RPAREN => {
73 if join_prev {
74 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
75 a.push_str(cursor.clamp_backwards(prev_ind));
76 }
77 } else {
78 arg_vec.push(cursor.clamp_backwards(prev_ind).into());
79 }
80
81 cursor.word(")}}}")?;
82 return Ok(parser.alloc(
83 MacroCall {
84 name: name_match.obj,
85 args: arg_vec,
86 },
87 start,
88 cursor.index,
89 parent,
90 ));
91 }
92 COMMA => {
93 if cursor.peek_rev(1)? != BACKSLASH {
94 if join_prev {
95 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
96 a.push_str(cursor.clamp_backwards(prev_ind));
97 }
98 join_prev = false;
99 } else {
100 arg_vec.push(cursor.clamp_backwards(prev_ind).into());
101 }
102 } else {
103 let mut pushee =
105 cursor.clamp(prev_ind, cursor.index - 1).to_owned();
106 pushee.push(COMMA as char);
107
108 if join_prev {
109 if let Cow::Owned(a) = arg_vec.last_mut().unwrap() {
110 a.push_str(&pushee);
111 }
112 } else {
113 arg_vec.push(pushee.into());
114 }
115 join_prev = true;
116 }
117
118 prev_ind = cursor.index + 1;
119 }
120 _ => {}
121 }
122 cursor.next();
123 }
124 }
125 RBRACE => {
126 cursor.word("}}}")?;
127 Ok(parser.alloc(
128 MacroCall {
129 name: name_match.obj,
130 args: Vec::new(),
131 },
132 start,
133 cursor.index,
134 parent,
135 ))
136 }
137 _ => Err(MatchError::InvalidLogic),
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use std::borrow::Cow;
145
146 use pretty_assertions::assert_eq;
147
148 use crate::{
149 element::{ArgNumOrText, MacroDef},
150 expr_in_pool,
151 object::MacroCall,
152 parse_org,
153 types::Expr,
154 };
155
156 #[test]
157 fn basic_macro() {
158 let input = r"{{{abc}}}";
159 let parsed = parse_org(input);
160 let l = expr_in_pool!(parsed, Macro).unwrap();
161 assert_eq!(
162 l,
163 &MacroCall {
164 name: "abc",
165 args: Vec::new()
166 }
167 )
168 }
169
170 #[test]
171 fn macro_with_args() {
172 let input = r"{{{poem(cool, three)}}}";
173 let parsed = parse_org(input);
174 let l = expr_in_pool!(parsed, Macro).unwrap();
175 assert_eq!(
176 l,
177 &MacroCall {
178 name: "poem",
179 args: vec!["cool".into(), " three".into()]
180 }
181 )
182 }
183
184 #[test]
185 fn basic_macro_def() {
186 let input = r"#+macro: poem hiii $1 $2 text
187";
188 let parsed = parse_org(input);
189 let l = expr_in_pool!(parsed, MacroDef).unwrap();
190 assert_eq!(
191 l,
192 &MacroDef {
193 num_args: 2,
194 input: vec![
195 ArgNumOrText::Text("hiii "),
196 ArgNumOrText::ArgNum(1),
197 ArgNumOrText::Text(" "),
198 ArgNumOrText::ArgNum(2),
199 ArgNumOrText::Text(" text")
200 ],
201 name: "poem"
202 }
203 )
204 }
205
206 #[test]
207 fn repeated_macro_def() {
208 let input = r"#+macro: poem $1 $1 text
209";
210 let parsed = parse_org(input);
211 let l = expr_in_pool!(parsed, MacroDef).unwrap();
212 assert_eq!(
213 l,
214 &MacroDef {
215 num_args: 1,
216 input: vec![
217 ArgNumOrText::Text(""),
218 ArgNumOrText::ArgNum(1),
219 ArgNumOrText::Text(" "),
220 ArgNumOrText::ArgNum(1),
221 ArgNumOrText::Text(" text")
222 ],
223 name: "poem"
224 }
225 )
226 }
227
228 #[test]
229 fn combined_macros() {
230 let input = r"#+macro: poem hiii $1 $2 text
231
232{{{poem(cool, three)}}}
233";
234 let pool = parse_org(input);
235 pool.print_tree();
236 }
237
238 #[test]
239 fn macro_escape() {
240 let input = r"{{{poem(cool\, three)}}}";
241 let parsed = parse_org(input);
242 let l = expr_in_pool!(parsed, Macro).unwrap();
243 assert_eq!(
244 l,
245 &MacroCall {
246 name: "poem",
247 args: vec![Cow::Borrowed("cool, three"), ]
248 }
249 )
250 }
251
252 #[test]
253 fn macro_multiple_escape() {
254 let input = r"{{{poem(cool\, \, \, \, three)}}}";
255 let parsed = parse_org(input);
256 let l = expr_in_pool!(parsed, Macro).unwrap();
257 assert_eq!(
258 l,
259 &MacroCall {
260 name: "poem",
261 args: vec![Cow::Borrowed("cool, , , , three"), ]
262 }
263 )
264 }
265}