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::RynaContext, parser::{empty0, identifier_parser, Location, PResult, Span}, patterns::Pattern};
7
8#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum RynaMacroType {
10 Function, Expression, Block, Rdl
11}
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14pub enum RdlMacro {
15 Text(String),
16 Var(String),
17 IndexedVar(String, String),
18 Loop(String, String, Box<RdlMacro>),
19 Seq(Vec<RdlMacro>),
20 Code(Box<RdlMacro>)
21}
22
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub struct RynaMacro {
25 pub location: Location,
26 pub annotations: Vec<Annotation>,
27 pub name: String,
28 pub m_type: RynaMacroType,
29 pub pattern: Pattern,
30 pub generator: RdlMacro
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<RdlMacro> {
67 map_opt(
68 many_till(text_pattern_char, peek(text_pattern_end)),
69 |(i, _)| if !i.is_empty() { Some(RdlMacro::Text(i.iter().collect())) } else { None }
70 )(input)
71}
72
73pub fn parse_var(input: Span<'_>) -> PResult<RdlMacro> {
74 map(
75 preceded(
76 tag("$"),
77 identifier_parser
78 ),
79 RdlMacro::Var
80 )(input)
81}
82
83pub fn parse_index(input: Span<'_>) -> PResult<RdlMacro> {
84 map(
85 tuple((
86 tag("$"),
87 identifier_parser,
88 tag("."),
89 identifier_parser
90 )),
91 |(_, c, _, i)| RdlMacro::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<RdlMacro> {
108 map(
109 tuple((
110 parse_loop_header,
111 empty0,
112 delimited(
113 tag("{"),
114 parse_ryna_macro,
115 tag("}")
116 )
117 )),
118 |(h, _, b)| RdlMacro::Loop(h.0, h.1, Box::new(b))
119 )(input)
120}
121
122pub fn parse_code(input: Span<'_>) -> PResult<RdlMacro> {
123 map(
124 delimited(
125 tag("{|"),
126 parse_ryna_macro,
127 tag("|}")
128 ),
129 |i| RdlMacro::Code(Box::new(i))
130 )(input)
131}
132
133pub fn parse_ryna_macro_line(input: Span<'_>) -> PResult<RdlMacro> {
134 alt((
135 parse_index,
136 parse_var,
137 parse_loop,
138 parse_code,
139 parse_text
140 ))(input)
141}
142
143pub fn parse_ryna_macro(input: Span<'_>) -> PResult<RdlMacro> {
144 map(
145 many0(parse_ryna_macro_line),
146 RdlMacro::Seq
147 )(input)
148}
149
150impl RdlMacro {
151 pub fn get_markers(&self) -> HashSet<(bool, String)> {
152 return match self {
153 RdlMacro::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 RdlMacro::Seq(b) => b.iter().flat_map(RdlMacro::get_markers).collect(),
166 RdlMacro::Var(v) => vec!((false, v.clone())).into_iter().collect(),
167 RdlMacro::IndexedVar(v, i) => vec!((false, v.clone()), (true, i.clone())).into_iter().collect(),
168
169 RdlMacro::Code(c) => c.get_markers(),
170
171 _ => HashSet::new(),
172 };
173 }
174
175 pub fn expand(&self, args: &HashMap<String, Vec<String>>, ctx: &RynaContext) -> 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 RdlMacro::Text(s) => Ok(s.clone()),
195
196 RdlMacro::Var(v) => {
197 let var = extract_var!(v)?;
198 assert_single!(var, v);
199
200 Ok(var[0].clone())
201 },
202
203 RdlMacro::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 RdlMacro::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 RdlMacro::Seq(b) => {
230 Ok(b.iter().map(|i| i.expand(args, ctx)).collect::<Result<Vec<_>, _>>()?.join(""))
231 },
232
233 RdlMacro::Code(p) => {
234 let sub_code = p.expand(args, ctx)?;
235
236 let ex = RynaContext::parse_and_execute_ryna_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
251fn escape_string(string: &str) -> String {
253 string.replace("\\", "\\\\")
254 .replace("\"", "\\\"")
255}
256
257pub fn define_module_path_macro(ctx: &mut RynaContext) {
258 ctx.macros.push(RynaMacro {
259 location: Location::none(),
260 annotations: vec!(),
261 name: "module_path".into(),
262 m_type: RynaMacroType::Expression,
263 pattern: Pattern::Str("$MODULE_PATH".into()),
264 generator: RdlMacro::Text(format!("\"{}\"", escape_string(&ctx.module_path))),
265 });
266}
267
268mod tests {
269 #[allow(unused)]
270 use std::collections::HashMap;
271 #[allow(unused)]
272 use crate::context::standard_ctx;
273 #[allow(unused)]
274 use crate::macros::RdlMacro;
275 #[allow(unused)]
276 use super::parse_ryna_macro;
277
278 #[test]
279 fn macro_parsing() {
280 let example_1 = "let test = arr<$type>();";
281 let example_2 = "let \\$var = arr<$type>();";
282 let example_3 = "if $cond { $expr \\}";
283 let example_4 = "@list.i { let i = $list.i; }";
284 let example_5 = "let res = {|$expr|};";
285
286 let example_1_macro = parse_ryna_macro(example_1.into()).unwrap().1;
287 let example_2_macro = parse_ryna_macro(example_2.into()).unwrap().1;
288 let example_3_macro = parse_ryna_macro(example_3.into()).unwrap().1;
289 let example_4_macro = parse_ryna_macro(example_4.into()).unwrap().1;
290 let example_5_macro = parse_ryna_macro(example_5.into()).unwrap().1;
291
292 assert_eq!(example_1_macro, RdlMacro::Seq(vec!(
293 RdlMacro::Text("let test = arr<".into()),
294 RdlMacro::Var("type".into()),
295 RdlMacro::Text(">();".into())
296 )));
297
298 assert_eq!(example_2_macro, RdlMacro::Seq(vec!(
299 RdlMacro::Text("let $var = arr<".into()),
300 RdlMacro::Var("type".into()),
301 RdlMacro::Text(">();".into())
302 )));
303
304 assert_eq!(example_3_macro, RdlMacro::Seq(vec!(
305 RdlMacro::Text("if ".into()),
306 RdlMacro::Var("cond".into()),
307 RdlMacro::Text(" { ".into()),
308 RdlMacro::Var("expr".into()),
309 RdlMacro::Text(" }".into()),
310 )));
311
312 assert_eq!(example_4_macro, RdlMacro::Seq(vec!(
313 RdlMacro::Loop("list".into(), "i".into(), Box::new(RdlMacro::Seq(vec!(
314 RdlMacro::Text(" let i = ".into()),
315 RdlMacro::IndexedVar("list".into(), "i".into()),
316 RdlMacro::Text("; ".into())
317 ))))
318 )));
319
320 assert_eq!(example_5_macro, RdlMacro::Seq(vec!(
321 RdlMacro::Text("let res = ".into()),
322 RdlMacro::Code(Box::new(RdlMacro::Seq(vec!(RdlMacro::Var("expr".into()))))),
323 RdlMacro::Text(";".into()),
324 )));
325 }
326
327 #[test]
328 fn macro_expansion() {
329 let ctx = standard_ctx();
330
331 let macro_ex_str = "let res = arr<$type>(); @elems.i {res.push($elems.i);} return move(res);";
333
334 let macro_ex = parse_ryna_macro(macro_ex_str.into()).unwrap().1;
335
336 let expanded_code_ex = macro_ex.expand(&[
337 ("type".into(), vec!("Int".into())),
338 ("elems".into(), vec!("1".into(), "7".into(), "10".into()))
339 ].iter().cloned().collect(), &ctx).unwrap();
340
341 assert_eq!(
342 expanded_code_ex,
343 "let res = arr<Int>(); res.push(1);res.push(7);res.push(10); return move(res);"
344 );
345
346 let macro_ex_str = "let res = hashmap<$ktype, $vtype>(); @keys.i {res.add($keys.i, $values.i);} return move(res);";
348
349 let macro_ex = parse_ryna_macro(macro_ex_str.into()).unwrap().1;
350
351 let expanded_code_ex = macro_ex.expand(&[
352 ("ktype".into(), vec!("Int".into())),
353 ("vtype".into(), vec!("String".into())),
354 ("keys".into(), vec!("1".into(), "7".into(), "10".into())),
355 ("values".into(), vec!("\"Test 1\"".into(), "\"Test 2\"".into(), "\"Test 3\"".into()))
356 ].iter().cloned().collect(), &ctx).unwrap();
357
358 assert_eq!(
359 expanded_code_ex,
360 "let res = hashmap<Int, String>(); res.add(1, \"Test 1\");res.add(7, \"Test 2\");res.add(10, \"Test 3\"); return move(res);"
361 );
362 }
363}