ryna/
macros.rs

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                // Delete referenced iterator variables
158                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
251// Standard context
252fn 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        // Array syntax
332        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        // Hashmap syntax
347        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}