Skip to main content

reoxide_proc/
lib.rs

1use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
2
3macro_rules! ident {
4    ($ident:expr, $span:expr) => {
5        TokenTree::Ident(Ident::new($ident, $span))
6    };
7    ($ident:expr) => {
8        TokenTree::Ident(Ident::new($ident, Span::call_site()))
9    };
10}
11
12macro_rules! punct {
13    ($ch:literal, $span:expr) => {{
14        let mut punct = Punct::new($ch, Spacing::Alone);
15        punct.set_span($span);
16        TokenTree::Punct(punct)
17    }};
18    ($ch:literal) => {
19        TokenTree::Punct(Punct::new($ch, Spacing::Alone))
20    };
21}
22
23macro_rules! jpunct {
24    ($ch:literal, $span:expr) => {{
25        let mut punct = Punct::new($ch, Spacing::Joint);
26        punct.set_span($span);
27        TokenTree::Punct(punct)
28    }};
29    ($ch:literal) => {
30        TokenTree::Punct(Punct::new($ch, Spacing::Joint))
31    };
32}
33
34macro_rules! literal {
35    ($lit:expr, $span:expr) => {{
36        let mut literal = Literal::string($lit);
37        literal.set_span($span);
38        TokenTree::Literal(literal)
39    }};
40    ($lit:expr) => {
41        TokenTree::Literal(Literal::string($lit))
42    };
43}
44
45macro_rules! group {
46    ($delimiter:expr, $array: expr, $span:expr) => {{
47        let mut group = Group::new($delimiter, TokenStream::from_iter($array));
48        group.set_span($span);
49        TokenTree::Group(group)
50    }};
51    ($delimiter:expr, $array: expr) => {
52        TokenTree::Group(Group::new($delimiter, TokenStream::from_iter($array)))
53    };
54}
55
56macro_rules! braces {
57    ($delimiter:expr, $span:expr) => {{
58        let mut group = Group::new($delimiter, TokenStream::new());
59        group.set_span($span);
60        TokenTree::Group(group)
61    }};
62    ($delimiter:expr) => {
63        TokenTree::Group(Group::new($delimiter, TokenStream::new()))
64    };
65}
66
67struct Error {
68    message: String,
69    span: Span,
70}
71
72impl Error {
73    fn into_token_stream(self) -> TokenStream {
74        let span = self.span;
75        let message = self.message;
76
77        TokenStream::from_iter([
78            jpunct!(':', span),
79            punct!(':', span),
80            ident!("core", span),
81            jpunct!(':', span),
82            punct!(':', span),
83            ident!("compile_error", span),
84            punct!('!', span),
85            group!(Delimiter::Brace, [literal!(&message, span)], span),
86        ])
87    }
88
89    fn into_combined_token_stream(self, other: TokenStream) -> TokenStream {
90        let mut stream = self.into_token_stream();
91        stream.extend(other);
92        stream
93    }
94}
95
96type Result<T> = std::result::Result<T, Error>;
97
98macro_rules! error {
99    ($msg:expr, $span:expr) => {
100        Err(Error {
101            message: $msg,
102            span: $span,
103        })
104    };
105}
106
107fn parse_ident<T>(iter: &mut T, span: Span, ident: &str) -> Result<Span>
108where
109    T: Iterator<Item = TokenTree>,
110{
111    match iter.next() {
112        Some(TokenTree::Ident(l)) if l.to_string() == ident => Ok(l.span().end()),
113        Some(t) => error!(format!(r#"Expected "{}", got "{}""#, ident, t), t.span()),
114        None => error!(format!(r#"Expected "{}""#, ident), span),
115    }
116}
117
118fn parse_ident_name<T>(iter: &mut T, span: Span) -> Result<Ident>
119where
120    T: Iterator<Item = TokenTree>,
121{
122    match iter.next() {
123        Some(TokenTree::Ident(ident)) => Ok(ident),
124        Some(t) => error!(format!(r#"Expected identifier got "{}""#, t), t.span()),
125        None => error!("Expected identifier".into(), span),
126    }
127}
128
129fn parse_punct<T>(iter: &mut T, span: Span, ch: char) -> Result<Span>
130where
131    T: Iterator<Item = TokenTree>,
132{
133    match iter.next() {
134        Some(TokenTree::Punct(p)) if p.as_char() == ch => Ok(p.span().end()),
135        Some(t) => error!(format!(r#"Expected "{}", got "{}""#, ch, t), t.span()),
136        None => error!(format!(r#"Expected "{}""#, ch), span),
137    }
138}
139
140fn parse_literal<T>(iter: &mut T, span: Span) -> Result<Literal>
141where
142    T: Iterator<Item = TokenTree>,
143{
144    match iter.next() {
145        Some(TokenTree::Literal(l)) => Ok(l),
146        Some(t) => error!("Expected string literal in argument".to_owned(), t.span()),
147        None => error!("Expected string literal in argument".to_owned(), span),
148    }
149}
150
151fn parse_name(literal: &Literal) -> Result<String> {
152    let name_string = literal.to_string();
153    if name_string.len() <= 2 || !name_string.starts_with('"') || !name_string.ends_with('"') {
154        return error!(
155            "Provided name is not a string literal or is empty".into(),
156            literal.span()
157        );
158    }
159
160    let substr = &name_string[1..name_string.len() - 1];
161    if let Some(_) = substr.find('\0') {
162        error!(
163            "Provided name is not a valid C string, because it contains internal null bytes".into(),
164            literal.span()
165        )
166    } else {
167        Ok(substr.into())
168    }
169}
170
171fn parse_list_group<T>(iter: &mut T, span: Span) -> Result<Group>
172where
173    T: Iterator<Item = TokenTree>,
174{
175    match iter.next() {
176        Some(TokenTree::Group(g)) if matches!(g.delimiter(), Delimiter::Bracket) => Ok(g),
177        Some(t) => error!("Expected list of identifiers".into(), t.span()),
178        _ => error!(
179            "Expected list of identifiers, got nothing".into(),
180            span.end()
181        ),
182    }
183}
184
185fn parse_ident_list<T>(iter: &mut T, span: Span) -> Result<(Span, Vec<String>)>
186where
187    T: Iterator<Item = TokenTree>,
188{
189    let list_group = parse_list_group(iter, span)?;
190    let mut span = list_group.span().start();
191
192    let mut list_iter = list_group.stream().into_iter().peekable();
193    let mut ident_list = Vec::new();
194
195    while let Some(_) = list_iter.peek() {
196        let ident_name = parse_ident_name(&mut list_iter, span)?;
197        span = ident_name.span().end();
198        ident_list.push(ident_name.to_string());
199
200        if let Some(_) = list_iter.peek() {
201            span = parse_punct(&mut list_iter, span, ',')?;
202        }
203    }
204
205    Ok((span, ident_list))
206}
207
208/// Skip outer attributes, these should start with '#' according to
209/// https://doc.rust-lang.org/reference/items.html#items
210fn skip_outer_attributes<T>(item: &mut std::iter::Peekable<T>, mut span: Span) -> Result<Span>
211where
212    T: Iterator<Item = TokenTree>,
213{
214    while let Some(TokenTree::Punct(p)) = item.peek() {
215        span = p.span();
216        if p.as_char() != '#' {
217            return error!("Unexpected token".into(), span);
218        }
219
220        item.next();
221
222        // The '#' should be followed by a bracket group
223        if let Some(TokenTree::Group(_)) = item.peek() {
224            item.next();
225        } else {
226            return error!("Unexpected token".into(), span.end());
227        }
228    }
229
230    Ok(span.end())
231}
232
233fn skip_visibility_modifiers<T>(item: &mut std::iter::Peekable<T>, mut span: Span) -> Span
234where
235    T: Iterator<Item = TokenTree>,
236{
237    // Skip visibility modifiers
238    if let Some(TokenTree::Ident(ident)) = item.peek() {
239        if ident.to_string() == "pub" {
240            span = ident.span();
241            item.next();
242
243            // If we have modifier on the visibility, also consume it
244            if let Some(TokenTree::Group(g)) = item.peek() {
245                span = g.span();
246                item.next();
247            }
248        }
249    }
250
251    span.end()
252}
253
254fn check_struct_specifier<T>(item: &mut std::iter::Peekable<T>, span: Span) -> Result<Span>
255where
256    T: Iterator<Item = TokenTree>,
257{
258    match item.next() {
259        Some(TokenTree::Ident(ident)) if ident.to_string() == "struct" => Ok(ident.span()),
260        Some(TokenTree::Ident(ident)) => {
261            error!("Only \"struct\" item supported".into(), ident.span())
262        }
263        Some(t) => error!("Unexpected token as item specifier".into(), t.span()),
264        _ => error!("Missing item specifier".into(), span),
265    }
266}
267
268fn parse_name_from_args<T>(args: &mut T, mut span: Span) -> Result<(String, Span)>
269where
270    T: Iterator<Item = TokenTree>,
271{
272    span = parse_ident(args, span, "name")?;
273    span = parse_punct(args, span, '=')?;
274    let name_literal = parse_literal(args, span)?;
275    Ok((parse_name(&name_literal)?, name_literal.span().end()))
276}
277
278fn parse_item_name<T>(item: &mut std::iter::Peekable<T>, mut span: Span) -> Result<(String, Span)>
279where
280    T: Iterator<Item = TokenTree>,
281{
282    span = skip_outer_attributes(item, span)?;
283    span = skip_visibility_modifiers(item, span);
284    span = check_struct_specifier(item, span)?;
285    let item_name = parse_ident_name(item, span)?;
286    Ok((item_name.to_string(), item_name.span()))
287}
288
289fn rule_impl(args: TokenStream, item: TokenStream) -> Result<TokenStream> {
290    let span = Span::call_site();
291    let mut args_iter = args.into_iter();
292    let (name, span) = parse_name_from_args(&mut args_iter, span)?;
293    let mut item_iter = item.clone().into_iter().peekable();
294    let (item_name, _) = parse_item_name(&mut item_iter, span)?;
295    let name_chars: Vec<i8> = name
296        .bytes()
297        .map(|b| b as i8)
298        .chain(std::iter::once(0i8))
299        .collect();
300
301    let mut stream = format!(
302        r#"
303        #[unsafe(no_mangle)]
304        pub static reoxide_rule_name_{item_name}: [i8; {rule_name_chars_len}] = {rule_name_chars:?};
305
306        #[unsafe(no_mangle)]
307        pub extern "C-unwind" fn reoxide_rule_new_{item_name}(
308            context: *const ::reoxide::plugin::Context,
309            plugin: *mut ::reoxide::plugin::OpaquePlugin,
310            args: *const ::reoxide::plugin::ExtraArgs,
311        ) -> *mut ::reoxide::plugin::OpaqueRule {{
312            unsafe {{
313                let args_ref = &*args;
314                let group_name_cstr = ::core::ffi::CStr::from_ptr(args_ref.group_name);
315                let group_name = match group_name_cstr.to_str() {{
316                    Ok(s) => s,
317                    Err(_) => return ::core::ptr::null_mut(),
318                }};
319                let context_ref = &*context;
320                let arch = &mut *context_ref.arch;
321                let stack_space = &mut *context_ref.stackspace;
322                let reoxide = &mut *context_ref.reoxide;
323                let plugin_ref = &mut *plugin.cast();
324                Box::into_raw(
325                        Box::new(
326                            {item_name}::new(::reoxide::plugin::CreationContext {{
327                                arch,
328                                stack_space,
329                                reoxide,
330                                plugin: plugin_ref,
331                                group_name,
332                                extra_args: args_ref.extra_arg
333                            }}),
334                        ),
335                    )
336                    .cast()
337            }}
338        }}
339
340        #[unsafe(no_mangle)]
341        pub extern "C-unwind" fn reoxide_rule_delete_{item_name}(rule: *mut ::reoxide::plugin::OpaqueRule) {{
342            if !rule.is_null() {{
343                let _ = unsafe {{
344                    let concrete_rule: *mut {item_name} = rule.cast();
345                    Box::from_raw(concrete_rule)
346                }};
347            }}
348        }}
349
350        #[unsafe(no_mangle)]
351        pub extern "C-unwind" fn reoxide_rule_oplist_{item_name}(
352            this: *const ::reoxide::plugin::OpaqueRule,
353            oplist: *mut ::reoxide::cpp::StdVector<::reoxide::plugin::OpCode>,
354        ) {{
355            let rule = unsafe {{ &*this.cast::<{item_name}>() }};
356            let oplist: &mut ::reoxide::cpp::StdVector<::reoxide::plugin::OpCode> = unsafe {{ &mut *oplist }};
357            oplist.extend(&rule.op_list());
358        }}
359
360
361        #[unsafe(no_mangle)]
362        pub extern "C-unwind" fn reoxide_rule_apply_{item_name}(
363            this: *mut ::reoxide::plugin::OpaqueRule,
364            op: *mut ::reoxide::plugin::PcodeOp,
365            data: *mut ::reoxide::plugin::Funcdata,
366        ) -> i32 {{
367            unsafe {{
368                let rule = &mut *this.cast::<{item_name}>();
369                let op_ref = &mut *op;
370                let data_ref = &mut *data;
371                rule.apply(op_ref, data_ref) as i32
372            }}
373        }}
374        "#,
375        item_name = item_name,
376        rule_name_chars = name_chars,
377        rule_name_chars_len = name_chars.len()
378    )
379    .parse::<TokenStream>()
380    .expect("Proc macro failed to produce output stream");
381    stream.extend(item);
382    Ok(stream)
383}
384
385fn action_impl(args: TokenStream, item: TokenStream) -> Result<TokenStream> {
386    let span = Span::call_site();
387    let mut args_iter = args.into_iter();
388    let (name, span) = parse_name_from_args(&mut args_iter, span)?;
389    let mut item_iter = item.clone().into_iter().peekable();
390    let (item_name, _) = parse_item_name(&mut item_iter, span)?;
391    let name_chars: Vec<i8> = name
392        .bytes()
393        .map(|b| b as i8)
394        .chain(std::iter::once(0i8))
395        .collect();
396
397    let mut stream = format!(
398        r#"
399        #[unsafe(no_mangle)]
400        pub static reoxide_action_name_{item_name}: [i8; {action_name_chars_len}] = {action_name_chars:?};
401
402        #[unsafe(no_mangle)]
403        pub extern "C-unwind" fn reoxide_action_new_{item_name}(
404            context: *const ::reoxide::plugin::Context,
405            plugin: *mut ::reoxide::plugin::OpaquePlugin,
406            args: *const ::reoxide::plugin::ExtraArgs,
407        ) -> *mut ::reoxide::plugin::OpaqueAction {{
408            unsafe {{
409                let args_ref = &*args;
410                let group_name_cstr = ::core::ffi::CStr::from_ptr(args_ref.group_name);
411                let group_name = match group_name_cstr.to_str() {{
412                    Ok(s) => s,
413                    Err(_) => return ::core::ptr::null_mut(),
414                }};
415                let context_ref = &*context;
416                let arch = &mut *context_ref.arch;
417                let stack_space = &mut *context_ref.stackspace;
418                let reoxide = &mut *context_ref.reoxide;
419                let plugin_ref = &mut *plugin.cast();
420                Box::into_raw(
421                        Box::new(
422                            {item_name}::new(::reoxide::plugin::CreationContext {{
423                                arch,
424                                stack_space,
425                                reoxide,
426                                plugin: plugin_ref,
427                                group_name,
428                                extra_args: args_ref.extra_arg
429                            }}),
430                        ),
431                    )
432                    .cast()
433            }}
434        }}
435
436        #[unsafe(no_mangle)]
437        pub extern "C-unwind" fn reoxide_action_delete_{item_name}(action: *mut ::reoxide::plugin::OpaqueAction) {{
438            if !action.is_null() {{
439                let _ = unsafe {{
440                    let concrete_action: *mut {item_name} = action.cast();
441                    Box::from_raw(concrete_action)
442                }};
443            }}
444        }}
445
446        #[unsafe(no_mangle)]
447        pub extern "C-unwind" fn reoxide_action_apply_{item_name}(
448            this: *mut ::reoxide::plugin::OpaqueAction,
449            data: *mut ::reoxide::plugin::Funcdata,
450        ) -> i32 {{
451            unsafe {{
452                let action = &mut *this.cast::<{item_name}>();
453                let data_ref = &mut *data;
454                action.apply(data_ref) as i32
455            }}
456        }}"#,
457        item_name = item_name,
458        action_name_chars = name_chars,
459        action_name_chars_len = name_chars.len()
460    )
461    .parse::<TokenStream>()
462    .expect("Proc macro failed to produce output stream");
463    stream.extend(item);
464    Ok(stream)
465}
466
467#[proc_macro_attribute]
468pub fn rule(args: TokenStream, item: TokenStream) -> TokenStream {
469    match rule_impl(args, item.clone()) {
470        Ok(s) => s,
471        Err(e) => e.into_combined_token_stream(item),
472    }
473}
474
475#[proc_macro_attribute]
476pub fn action(args: TokenStream, item: TokenStream) -> TokenStream {
477    match action_impl(args, item.clone()) {
478        Ok(s) => s,
479        Err(e) => e.into_combined_token_stream(item),
480    }
481}
482
483fn rule_definition_to_stream(ident: &str, span: Span) -> TokenStream {
484    let definition = [
485        ident!("name", span),
486        punct!(':', span),
487        ident!(&format!("reoxide_rule_name_{}", ident), span),
488        punct!('.', span),
489        ident!("as_ptr", span),
490        braces!(Delimiter::Parenthesis, span),
491        punct!(',', span),
492        ident!("constructor", span),
493        punct!(':', span),
494        ident!(&format!("reoxide_rule_new_{}", ident), span),
495        punct!(',', span),
496        ident!("destructor", span),
497        punct!(':', span),
498        ident!(&format!("reoxide_rule_delete_{}", ident), span),
499        punct!(',', span),
500        ident!("oplist", span),
501        punct!(':', span),
502        ident!(&format!("reoxide_rule_oplist_{}", ident), span),
503        punct!(',', span),
504        ident!("apply", span),
505        punct!(':', span),
506        ident!(&format!("reoxide_rule_apply_{}", ident), span),
507    ];
508
509    TokenStream::from_iter([
510        jpunct!(':', span),
511        punct!(':', span),
512        ident!("reoxide", span),
513        jpunct!(':', span),
514        punct!(':', span),
515        ident!("plugin", span),
516        jpunct!(':', span),
517        punct!(':', span),
518        ident!("RuleDefinition", span),
519        group!(Delimiter::Brace, definition, span),
520        punct!(',', span),
521    ])
522}
523
524fn action_definition_to_stream(ident: &str, span: Span) -> TokenStream {
525    let definition = [
526        ident!("name", span),
527        punct!(':', span),
528        ident!(&format!("reoxide_action_name_{}", ident), span),
529        punct!('.', span),
530        ident!("as_ptr", span),
531        braces!(Delimiter::Parenthesis, span),
532        punct!(',', span),
533        ident!("constructor", span),
534        punct!(':', span),
535        ident!(&format!("reoxide_action_new_{}", ident), span),
536        punct!(',', span),
537        ident!("destructor", span),
538        punct!(':', span),
539        ident!(&format!("reoxide_action_delete_{}", ident), span),
540        punct!(',', span),
541        ident!("apply", span),
542        punct!(':', span),
543        ident!(&format!("reoxide_action_apply_{}", ident), span),
544    ];
545
546    TokenStream::from_iter([
547        jpunct!(':', span),
548        punct!(':', span),
549        ident!("reoxide", span),
550        jpunct!(':', span),
551        punct!(':', span),
552        ident!("plugin", span),
553        jpunct!(':', span),
554        punct!(':', span),
555        ident!("ActionDefinition", span),
556        group!(Delimiter::Brace, definition, span),
557        punct!(',', span),
558    ])
559}
560
561fn plugin_impl(input: TokenStream) -> Result<TokenStream> {
562    let mut span = Span::call_site();
563    let mut args_iter = input.into_iter();
564
565    span = parse_ident(&mut args_iter, span, "context")?;
566    span = parse_punct(&mut args_iter, span, '=')?;
567    let plugin_ident = parse_ident_name(&mut args_iter, span)?;
568    span = parse_punct(&mut args_iter, span, ',')?;
569    span = parse_ident(&mut args_iter, span, "rules")?;
570    span = parse_punct(&mut args_iter, span, '=')?;
571    let (mut span, rules) = parse_ident_list(&mut args_iter, span)?;
572    span = parse_punct(&mut args_iter, span, ',')?;
573    span = parse_ident(&mut args_iter, span, "actions")?;
574    span = parse_punct(&mut args_iter, span, '=')?;
575    let (_, actions) = parse_ident_list(&mut args_iter, span)?;
576
577    let mut out_stream = TokenStream::new();
578
579    out_stream.extend(
580        format!(
581            r#"
582        #[unsafe(no_mangle)]
583        pub static reoxide_c_abi: usize = 1;
584
585        #[unsafe(no_mangle)]
586        pub extern "C-unwind" fn reoxide_plugin_new() -> *mut ::reoxide::plugin::OpaquePlugin {{
587            ::std::panic::set_hook(Box::new(reoxide::plugin::panic_hook));
588            Box::into_raw(Box::new({plugin_name} {{}})).cast()
589        }}
590        
591        #[unsafe(no_mangle)]
592        pub extern "C-unwind" fn reoxide_plugin_delete(plugin: *mut {plugin_name}) {{
593            if !plugin.is_null() {{
594                let _ = unsafe {{ Box::from_raw(plugin) }};
595            }}
596        }}
597        "#,
598            plugin_name = plugin_ident.to_string()
599        )
600        .parse::<TokenStream>()
601        .expect("Proc macro failed to produce output stream"),
602    );
603
604    out_stream.extend(
605        format!(
606            r#"
607            #[unsafe(no_mangle)]
608            pub static reoxide_rule_count: usize = {rule_count};
609
610            #[unsafe(no_mangle)]
611            pub static reoxide_rule_defs: ::reoxide::plugin::DefinitionTable<
612                [::reoxide::plugin::RuleDefinition; {rule_count}]
613            > = ::reoxide::plugin::DefinitionTable::<
614                [::reoxide::plugin::RuleDefinition; {rule_count}]
615            >"#,
616            rule_count = rules.len()
617        )
618        .parse::<TokenStream>()
619        .expect("Proc macro failed to produce output stream"),
620    );
621
622    let mut rule_stream = TokenStream::new();
623    rules
624        .into_iter()
625        .for_each(|ident| rule_stream.extend(rule_definition_to_stream(&ident, Span::call_site())));
626
627    out_stream.extend([
628        group!(
629            Delimiter::Brace,
630            [
631                ident!("table"),
632                punct!(':'),
633                group!(Delimiter::Bracket, rule_stream),
634            ]
635        ),
636        punct!(';'),
637    ]);
638
639    out_stream.extend(
640        format!(
641            r#"
642            #[unsafe(no_mangle)]
643            pub static reoxide_action_count: usize = {action_count};
644
645            #[unsafe(no_mangle)]
646            pub static reoxide_action_defs: ::reoxide::plugin::DefinitionTable<
647                [::reoxide::plugin::ActionDefinition; {action_count}]
648            > = ::reoxide::plugin::DefinitionTable::<
649                [::reoxide::plugin::ActionDefinition; {action_count}]
650            >"#,
651            action_count = actions.len()
652        )
653        .parse::<TokenStream>()
654        .expect("Proc macro failed to produce output stream"),
655    );
656
657    let mut action_stream = TokenStream::new();
658    actions.into_iter().for_each(|ident| {
659        action_stream.extend(action_definition_to_stream(&ident, Span::call_site()))
660    });
661
662    out_stream.extend([
663        group!(
664            Delimiter::Brace,
665            [
666                ident!("table"),
667                punct!(':'),
668                group!(Delimiter::Bracket, action_stream),
669            ]
670        ),
671        punct!(';'),
672    ]);
673
674    Ok(out_stream)
675}
676
677#[proc_macro]
678pub fn plugin(input: TokenStream) -> TokenStream {
679    match plugin_impl(input) {
680        Ok(s) => s,
681        Err(e) => e.into_token_stream(),
682    }
683}