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
208fn 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 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 if let Some(TokenTree::Ident(ident)) = item.peek() {
239 if ident.to_string() == "pub" {
240 span = ident.span();
241 item.next();
242
243 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}