Skip to content

Commit ac4bb8b

Browse files
committed
Add impl for switch and goto_block macros
1 parent 5455888 commit ac4bb8b

8 files changed

Lines changed: 586 additions & 0 deletions

File tree

libcc2rs-macros/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "libcc2rs-macros"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
proc-macro2 = "1"
11+
quote = "1"
12+
syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] }

libcc2rs-macros/src/goto.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2022-present INESC-ID.
2+
// Distributed under the MIT license that can be found in the LICENSE file.
3+
4+
// goto_block! {
5+
// '<label> => <body>,
6+
// '<label> => <body>,
7+
// ...
8+
// };
9+
10+
use proc_macro::TokenStream;
11+
use syn::parse::{Parse, ParseStream};
12+
use syn::{parse_macro_input, Expr, Lifetime, Token};
13+
14+
use crate::state_machine::{emit_state_machine, Arm, ArmEntry};
15+
16+
pub fn expand(input: TokenStream) -> TokenStream {
17+
let GotoBlockInput { arms } = parse_macro_input!(input as GotoBlockInput);
18+
emit_state_machine(
19+
None,
20+
arms.into_iter()
21+
.map(|a| Arm {
22+
label: a.label.ident.to_string(),
23+
entry: ArmEntry::LabelOnly,
24+
body: a.body,
25+
})
26+
.collect(),
27+
)
28+
.into()
29+
}
30+
31+
struct GotoBlockInput {
32+
arms: Vec<GotoArm>,
33+
}
34+
35+
struct GotoArm {
36+
label: Lifetime,
37+
body: Expr,
38+
}
39+
40+
impl Parse for GotoBlockInput {
41+
fn parse(input: ParseStream) -> syn::Result<Self> {
42+
let mut arms = Vec::new();
43+
while !input.is_empty() {
44+
let label: Lifetime = input.parse()?;
45+
input.parse::<Token![=>]>()?;
46+
let body: Expr = input.parse()?;
47+
arms.push(GotoArm { label, body });
48+
if input.peek(Token![,]) {
49+
input.parse::<Token![,]>()?;
50+
}
51+
}
52+
Ok(Self { arms })
53+
}
54+
}

libcc2rs-macros/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2022-present INESC-ID.
2+
// Distributed under the MIT license that can be found in the LICENSE file.
3+
4+
use proc_macro::TokenStream;
5+
6+
mod goto;
7+
mod state_machine;
8+
mod switch;
9+
10+
#[proc_macro]
11+
pub fn switch(input: TokenStream) -> TokenStream {
12+
switch::expand(input)
13+
}
14+
15+
#[proc_macro]
16+
pub fn goto_block(input: TokenStream) -> TokenStream {
17+
goto::expand(input)
18+
}
19+
20+
#[proc_macro]
21+
pub fn goto(_input: TokenStream) -> TokenStream {
22+
todo!();
23+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2022-present INESC-ID.
2+
// Distributed under the MIT license that can be found in the LICENSE file.
3+
4+
use proc_macro2::TokenStream as TokenStream2;
5+
use quote::{format_ident, quote};
6+
use syn::visit_mut::{self, VisitMut};
7+
use syn::{Expr, ExprBreak, Lifetime, Pat};
8+
9+
pub struct Arm {
10+
pub label: String,
11+
pub entry: ArmEntry,
12+
pub body: Expr,
13+
}
14+
15+
pub enum ArmEntry {
16+
Dispatch { pat: Pat, guard: Option<Expr> },
17+
LabelOnly,
18+
}
19+
20+
pub fn emit_state_machine(condition: Option<Expr>, arms: Vec<Arm>) -> TokenStream2 {
21+
let lbl = Lifetime::new("'__sm", proc_macro2::Span::call_site());
22+
let s = format_ident!("__s");
23+
24+
let base = if condition.is_some() { 1u32 } else { 0u32 };
25+
26+
let dispatch_arm = condition.map(|scrut| {
27+
let case_arms = arms
28+
.iter()
29+
.enumerate()
30+
.filter_map(|(i, arm)| match &arm.entry {
31+
ArmEntry::Dispatch { pat, guard } => {
32+
let idx = base + i as u32;
33+
let guard = guard.as_ref().map(|g| quote! { if #g });
34+
Some(quote! { #pat #guard => { #s = #idx; continue #lbl; } })
35+
}
36+
ArmEntry::LabelOnly => None,
37+
});
38+
quote! {
39+
0u32 => {
40+
#[allow(unreachable_patterns)]
41+
match #scrut {
42+
#(#case_arms,)*
43+
_ => break #lbl,
44+
}
45+
}
46+
}
47+
});
48+
49+
let n = arms.len();
50+
let body_arms = arms.iter().enumerate().map(|(i, arm)| {
51+
let idx = base + i as u32;
52+
let body = rewrite_body(&arm.body, &lbl);
53+
let tail = if i + 1 < n {
54+
let next = idx + 1;
55+
quote! { #s = #next; continue #lbl; }
56+
} else {
57+
quote! { break #lbl; }
58+
};
59+
quote! {
60+
#idx => {
61+
#[allow(unreachable_code)]
62+
{ #body; #tail }
63+
}
64+
}
65+
});
66+
67+
quote! {{
68+
let mut #s: u32 = 0;
69+
#[allow(unreachable_code, unused_labels)]
70+
#lbl: loop {
71+
match #s {
72+
#dispatch_arm
73+
#(#body_arms)*
74+
_ => break #lbl,
75+
}
76+
}
77+
}}
78+
}
79+
80+
fn rewrite_body(body: &Expr, label: &Lifetime) -> TokenStream2 {
81+
let mut body = body.clone();
82+
BreakRewriter {
83+
label: label.clone(),
84+
}
85+
.visit_expr_mut(&mut body);
86+
quote! { #body }
87+
}
88+
89+
struct BreakRewriter {
90+
label: Lifetime,
91+
}
92+
93+
impl VisitMut for BreakRewriter {
94+
fn visit_expr_break_mut(&mut self, node: &mut ExprBreak) {
95+
if node.label.is_none() {
96+
node.label = Some(self.label.clone());
97+
}
98+
}
99+
fn visit_expr_loop_mut(&mut self, _: &mut syn::ExprLoop) {}
100+
fn visit_expr_while_mut(&mut self, _: &mut syn::ExprWhile) {}
101+
fn visit_expr_for_loop_mut(&mut self, _: &mut syn::ExprForLoop) {}
102+
}

libcc2rs-macros/src/switch.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2022-present INESC-ID.
2+
// Distributed under the MIT license that can be found in the LICENSE file.
3+
4+
// switch!(match <condition> {
5+
// <pat> [if <guard>] => <body>,
6+
// <pat> [if <guard>] => <body>,
7+
// ...
8+
// _ => <body>,
9+
// });
10+
//
11+
// The input is a real `match` expression so rustfmt formats it normally.
12+
13+
use proc_macro::TokenStream;
14+
use syn::parse::{Parse, ParseStream};
15+
use syn::{parse_macro_input, Expr, Pat};
16+
17+
use crate::state_machine::{emit_state_machine, Arm, ArmEntry};
18+
19+
pub fn expand(input: TokenStream) -> TokenStream {
20+
let SwitchInput { condition, arms } = parse_macro_input!(input as SwitchInput);
21+
emit_state_machine(
22+
Some(condition),
23+
arms.into_iter()
24+
.enumerate()
25+
.map(|(i, a)| Arm {
26+
label: format!("__c{}", i),
27+
entry: ArmEntry::Dispatch {
28+
pat: a.pat,
29+
guard: a.guard,
30+
},
31+
body: a.body,
32+
})
33+
.collect(),
34+
)
35+
.into()
36+
}
37+
38+
struct SwitchInput {
39+
condition: Expr,
40+
arms: Vec<SwitchArm>,
41+
}
42+
43+
struct SwitchArm {
44+
pat: Pat,
45+
guard: Option<Expr>,
46+
body: Expr,
47+
}
48+
49+
impl Parse for SwitchInput {
50+
fn parse(input: ParseStream) -> syn::Result<Self> {
51+
let m: syn::ExprMatch = input.parse()?;
52+
Ok(Self {
53+
condition: *m.expr,
54+
arms: m
55+
.arms
56+
.into_iter()
57+
.map(|a| SwitchArm {
58+
pat: a.pat,
59+
guard: a.guard.map(|(_if, e)| *e),
60+
body: *a.body,
61+
})
62+
.collect(),
63+
})
64+
}
65+
}

0 commit comments

Comments
 (0)