1use std::collections::{HashMap, HashSet};
2
3use nom::{branch::alt, bytes::complete::tag, character::complete::satisfy, combinator::{eof, map, map_opt, peek, value}, multi::{many0, many_till}, sequence::{delimited, preceded, tuple}};
4use serde::{Deserialize, Serialize};
5
6use crate::{annotations::Annotation, context::NessaContext, parser::{empty0, identifier_parser, Location, PResult, Span}, patterns::Pattern};
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum NessaMacroType {
10 Function, Expression, Block, Ndl
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum NdlMacro {
15 Text(String),
16 Var(String),
17 IndexedVar(String, String),
18 Loop(String, String, Box<NdlMacro>),
19 Seq(Vec<NdlMacro>),
20 Code(Box<NdlMacro>)
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub struct NessaMacro {
25 pub location: Location,
26 pub annotations: Vec<Annotation>,
27 pub name: String,
28 pub m_type: NessaMacroType,
29 pub pattern: Pattern,
30 pub generator: NdlMacro
31}
32
33pub fn text_pattern_char(input: Span<'_>) -> PResult<char> {
34 alt((
35 preceded(
36 tag("\\"),
37 map_opt(
38 satisfy(|_| true),
39 |c| match c {
40 '\\' => Some('\\'),
41 'n' => Some('\n'),
42 't' => Some('\t'),
43 '{' => Some('{'),
44 '}' => Some('}'),
45 '$' => Some('$'),
46 '@' => Some('@'),
47 _ => None
48 }
49 )
50 ),
51 satisfy(|_| true),
52 ))(input)
53}
54
55pub fn text_pattern_end(input: Span<'_>) -> PResult<()> {
56 alt((
57 value((), tuple((tag("$"), identifier_parser))),
58 value((), tuple((tag("@"), identifier_parser))),
59 value((), tag("{|")),
60 value((), tag("|}")),
61 value((), tag("}")),
62 value((), eof),
63 ))(input)
64}
65
66pub fn parse_text(input: Span<'_>) -> PResult<NdlMacro> {
67 map_opt(
68 many_till(text_pattern_char, peek(text_pattern_end)),
69 |(i, _)| if !i.is_empty() { Some(NdlMacro::Text(i.iter().collect())) } else { None }
70 )(input)
71}
72
73pub fn parse_var(input: Span<'_>) -> PResult<NdlMacro> {
74 map(
75 preceded(
76 tag("$"),
77 identifier_parser
78 ),
79 NdlMacro::Var
80 )(input)
81}
82
83pub fn parse_index(input: Span<'_>) -> PResult<NdlMacro> {
84 map(
85 tuple((
86 tag("$"),
87 identifier_parser,
88 tag("."),
89 identifier_parser
90 )),
91 |(_, c, _, i)| NdlMacro::IndexedVar(c, i)
92 )(input)
93}
94
95pub fn parse_loop_header(input: Span<'_>) -> PResult<(String, String)> {
96 map(
97 tuple((
98 tag("@"),
99 identifier_parser,
100 tag("."),
101 identifier_parser
102 )),
103 |(_, c, _, i)| (c, i)
104 )(input)
105}
106
107pub fn parse_loop(input: Span<'_>) -> PResult<NdlMacro> {
108 map(
109 tuple((
110 parse_loop_header,
111 empty0,
112 delimited(
113 tag("{"),
114 parse_nessa_macro,
115 tag("}")
116 )
117 )),
118 |(h, _, b)| NdlMacro::Loop(h.0, h.1, Box::new(b))
119 )(input)
120}
121
122pub fn parse_code(input: Span<'_>) -> PResult<NdlMacro> {
123 map(
124 delimited(
125 tag("{|"),
126 parse_nessa_macro,
127 tag("|}")
128 ),
129 |i| NdlMacro::Code(Box::new(i))
130 )(input)
131}
132
133pub fn parse_nessa_macro_line(input: Span<'_>) -> PResult<NdlMacro> {
134 alt((
135 parse_index,
136 parse_var,
137 parse_loop,
138 parse_code,
139 parse_text
140 ))(input)
141}
142
143pub fn parse_nessa_macro(input: Span<'_>) -> PResult<NdlMacro> {
144 map(
145 many0(parse_nessa_macro_line),
146 NdlMacro::Seq
147 )(input)
148}
149
150impl NdlMacro {
151 pub fn get_markers(&self) -> HashSet<(bool, String)> {
152 return match self {
153 NdlMacro::Loop(v, i, b) => {
154 let mut res = b.get_markers().into_iter().chain(vec!((false, v.clone()), (true, i.clone()))).collect::<HashSet<_>>();
155 let repeated = res.iter().filter(|(it, _)| *it).cloned().collect::<Vec<_>>();
156
157 for (_, s) in repeated {
159 res.remove(&(false, s.clone()));
160 }
161
162 res
163 },
164
165 NdlMacro::Seq(b) => b.iter().flat_map(NdlMacro::get_markers).collect(),
166 NdlMacro::Var(v) => vec!((false, v.clone())).into_iter().collect(),
167 NdlMacro::IndexedVar(v, i) => vec!((false, v.clone()), (true, i.clone())).into_iter().collect(),
168
169 NdlMacro::Code(c) => c.get_markers(),
170
171 _ => HashSet::new(),
172 };
173 }
174
175 pub fn expand(&self, args: &HashMap<String, Vec<String>>, ctx: &NessaContext) -> Result<String, String> {
176 macro_rules! extract_var {
177 ($n: expr) => {
178 match args.get($n) {
179 Some(inner) => Ok(inner),
180 _ => Err(format!("Did not extract variable with name '{}'", $n))
181 }
182 };
183 }
184
185 macro_rules! assert_single {
186 ($args: expr, $n: expr) => {
187 if $args.len() != 1 {
188 return Err(format!("Extracted {} arguments with name {} instead of 1", $args.len(), $n))
189 }
190 };
191 }
192
193 match self {
194 NdlMacro::Text(s) => Ok(s.clone()),
195
196 NdlMacro::Var(v) => {
197 let var = extract_var!(v)?;
198 assert_single!(var, v);
199
200 Ok(var[0].clone())
201 },
202
203 NdlMacro::IndexedVar(c, i) => {
204 let cs = extract_var!(c)?;
205 let idx = extract_var!(i)?;
206 assert_single!(idx, i);
207
208 match idx[0].parse::<usize>() {
209 Ok(idx_usize) => Ok(cs[idx_usize].clone()),
210 Err(_) => Err(format!("Unable to parse '{}' as an index", idx[0])),
211 }
212 },
213
214 NdlMacro::Loop(c, i, b) => {
215 let cont = extract_var!(c)?;
216
217 let mut args_cpy = args.clone();
218
219 let iters = (0..cont.len()).map(|iv| {
220 *args_cpy.entry(i.clone()).or_default() = vec!(iv.to_string());
221
222 b.expand(&args_cpy, ctx)
223
224 }).collect::<Result<Vec<_>, _>>()?;
225
226 Ok(iters.join(""))
227 },
228
229 NdlMacro::Seq(b) => {
230 Ok(b.iter().map(|i| i.expand(args, ctx)).collect::<Result<Vec<_>, _>>()?.join(""))
231 },
232
233 NdlMacro::Code(p) => {
234 let sub_code = p.expand(args, ctx)?;
235
236 let ex = NessaContext::parse_and_execute_nessa_project_inner::<false>(
237 ctx.module_path.clone(),
238 Some(sub_code),
239 true,
240 ctx.optimize,
241 false,
242 &[]
243 ).unwrap();
244
245 Ok(ex.captured_output)
246 }
247 }
248 }
249}
250
251mod tests {
252 #[allow(unused)]
253 use std::collections::HashMap;
254 #[allow(unused)]
255 use crate::context::standard_ctx;
256 #[allow(unused)]
257 use crate::macros::NdlMacro;
258 #[allow(unused)]
259 use super::parse_nessa_macro;
260
261 #[test]
262 fn macro_parsing() {
263 let example_1 = "let test = arr<$type>();";
264 let example_2 = "let \\$var = arr<$type>();";
265 let example_3 = "if $cond { $expr \\}";
266 let example_4 = "@list.i { let i = $list.i; }";
267 let example_5 = "let res = {|$expr|};";
268
269 let example_1_macro = parse_nessa_macro(example_1.into()).unwrap().1;
270 let example_2_macro = parse_nessa_macro(example_2.into()).unwrap().1;
271 let example_3_macro = parse_nessa_macro(example_3.into()).unwrap().1;
272 let example_4_macro = parse_nessa_macro(example_4.into()).unwrap().1;
273 let example_5_macro = parse_nessa_macro(example_5.into()).unwrap().1;
274
275 assert_eq!(example_1_macro, NdlMacro::Seq(vec!(
276 NdlMacro::Text("let test = arr<".into()),
277 NdlMacro::Var("type".into()),
278 NdlMacro::Text(">();".into())
279 )));
280
281 assert_eq!(example_2_macro, NdlMacro::Seq(vec!(
282 NdlMacro::Text("let $var = arr<".into()),
283 NdlMacro::Var("type".into()),
284 NdlMacro::Text(">();".into())
285 )));
286
287 assert_eq!(example_3_macro, NdlMacro::Seq(vec!(
288 NdlMacro::Text("if ".into()),
289 NdlMacro::Var("cond".into()),
290 NdlMacro::Text(" { ".into()),
291 NdlMacro::Var("expr".into()),
292 NdlMacro::Text(" }".into()),
293 )));
294
295 assert_eq!(example_4_macro, NdlMacro::Seq(vec!(
296 NdlMacro::Loop("list".into(), "i".into(), Box::new(NdlMacro::Seq(vec!(
297 NdlMacro::Text(" let i = ".into()),
298 NdlMacro::IndexedVar("list".into(), "i".into()),
299 NdlMacro::Text("; ".into())
300 ))))
301 )));
302
303 assert_eq!(example_5_macro, NdlMacro::Seq(vec!(
304 NdlMacro::Text("let res = ".into()),
305 NdlMacro::Code(Box::new(NdlMacro::Seq(vec!(NdlMacro::Var("expr".into()))))),
306 NdlMacro::Text(";".into()),
307 )));
308 }
309
310 #[test]
311 fn macro_expansion() {
312 let ctx = standard_ctx();
313
314 let macro_ex_str = "let res = arr<$type>(); @elems.i {res.push($elems.i);} return move(res);";
316
317 let macro_ex = parse_nessa_macro(macro_ex_str.into()).unwrap().1;
318
319 let expanded_code_ex = macro_ex.expand(&[
320 ("type".into(), vec!("Int".into())),
321 ("elems".into(), vec!("1".into(), "7".into(), "10".into()))
322 ].iter().cloned().collect(), &ctx).unwrap();
323
324 assert_eq!(
325 expanded_code_ex,
326 "let res = arr<Int>(); res.push(1);res.push(7);res.push(10); return move(res);"
327 );
328
329 let macro_ex_str = "let res = hashmap<$ktype, $vtype>(); @keys.i {res.add($keys.i, $values.i);} return move(res);";
331
332 let macro_ex = parse_nessa_macro(macro_ex_str.into()).unwrap().1;
333
334 let expanded_code_ex = macro_ex.expand(&[
335 ("ktype".into(), vec!("Int".into())),
336 ("vtype".into(), vec!("String".into())),
337 ("keys".into(), vec!("1".into(), "7".into(), "10".into())),
338 ("values".into(), vec!("\"Test 1\"".into(), "\"Test 2\"".into(), "\"Test 3\"".into()))
339 ].iter().cloned().collect(), &ctx).unwrap();
340
341 assert_eq!(
342 expanded_code_ex,
343 "let res = hashmap<Int, String>(); res.add(1, \"Test 1\");res.add(7, \"Test 2\");res.add(10, \"Test 3\"); return move(res);"
344 );
345 }
346}