Skip to content

Commit 0fd4edf

Browse files
authored
Merge pull request #2 from Tsukuba-Programming-Lab/#1_SetupProject
#1 基礎を整えた
2 parents d7696a8 + e531218 commit 0fd4edf

File tree

15 files changed

+403
-1
lines changed

15 files changed

+403
-1
lines changed

Cargo.lock

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
[package]
22
name = "mymodifer"
3-
version = "0.1.0"
43
edition = "2024"
4+
version.workspace = true
55

66
[dependencies]
7+
# TODO
8+
9+
[workspace]
10+
resovler = "2"
11+
members = [
12+
# crates
13+
"crates/core",
14+
"crates/macros",
15+
16+
# examples
17+
"examples/simple",
18+
]
19+
exclude = []
20+
21+
[workspace.package]
22+
version = "0.1.0"
23+
24+
[workspace.dependencies]
25+
anyhow = "1.0.96"
26+
thiserror = "2.0.11"

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# My Modifier
2+
3+
```rust
4+
#[modifier]
5+
trait Dev {}
6+
7+
#[modifier_callee]
8+
fn for_developper() {
9+
// ...
10+
}
11+
12+
fn main() {
13+
// Ok
14+
dev! {{
15+
for_developper();
16+
}}
17+
18+
// Compile error
19+
for_developper();
20+
}
21+
```
22+
23+
## Examples
24+
25+
- [simple](examples/simple)
26+
27+
```
28+
$ cargo run -p example_simple
29+
Hello, World!
30+
```

crates/core/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "mymodifier_core"
3+
edition = "2024"
4+
version.workspace = true
5+
6+
[dependencies]
7+
anyhow = { workspace = true }
8+
thiserror = { workspace = true }
9+
mymodifier_macros = { path = "../macros" }

crates/core/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub use mymodifier_macros::{
2+
modifier,
3+
modifier_caller,
4+
modifier_callee,
5+
};

crates/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 = "mymodifier_macros"
3+
edition = "2024"
4+
version.workspace = true
5+
6+
[dependencies]
7+
proc-macro2 = "1.0"
8+
quote = "1.0"
9+
syn = { version = "2.0", features = ["full", "extra-traits"] }
10+
11+
[lib]
12+
proc-macro = true

crates/macros/src/impl.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod modifier;
2+
pub mod modifier_caller;
3+
pub mod modifier_callee;

crates/macros/src/impl/modifier.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use proc_macro2::{TokenStream, Ident};
2+
use quote::quote;
3+
use syn::ItemTrait;
4+
5+
/// 対象: トレイト
6+
/// 動作: 文脈導入用の宣言型マクロを生成
7+
pub fn proc_macro_impl(_args: TokenStream, ast: ItemTrait) -> TokenStream {
8+
let trait_name = &ast.ident;
9+
let macro_ident = as_macro_ident(trait_name);
10+
11+
quote! {
12+
// トレイト本体
13+
#ast
14+
15+
// 文脈マクロ
16+
#[macro_export]
17+
macro_rules! #macro_ident {
18+
({ $($body:tt)* }) => {
19+
// 文脈を用意
20+
struct Ctx;
21+
22+
// 文脈付き呼び出しへの変換 (関数定義ではなくブロックに変換される)
23+
#[modifier_caller(#trait_name)]
24+
fn __mymodifier_caller() {
25+
$($body)*
26+
}
27+
};
28+
29+
($($body:tt)*) => {{
30+
// 文脈付き呼び出しへの変換 (関数定義ではなくブロックに変換される)
31+
#[modifier_caller(#trait_name)]
32+
fn __mymodifier_caller() {
33+
$($body)*
34+
}
35+
}};
36+
}
37+
}
38+
}
39+
40+
fn as_macro_ident(ident: &Ident) -> TokenStream {
41+
let mut result = String::new();
42+
for c in ident.to_string().chars() {
43+
if c.is_uppercase() {
44+
result.push('_');
45+
result.push(c.to_ascii_lowercase());
46+
} else {
47+
result.push(c);
48+
}
49+
}
50+
51+
let result = if result.starts_with('_') {
52+
&result[1..]
53+
} else {
54+
&result
55+
};
56+
57+
result.parse().unwrap()
58+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
use syn::{FnArg, ItemFn};
4+
5+
/// 対象: 関数
6+
/// 動作: 文脈への制約を追加
7+
pub fn proc_macro_impl(args: TokenStream, ast: ItemFn) -> TokenStream {
8+
let target_trait = args;
9+
10+
let fn_visibility = ast.vis;
11+
let fn_ident = ast.sig.ident;
12+
let fn_ret_type = ast.sig.output;
13+
let fn_body = ast.block;
14+
15+
let fn_args = ast.sig.inputs;
16+
let fn_args = if let Some(FnArg::Receiver(..)) = fn_args.first() {
17+
let receiver = &fn_args[0];
18+
let remains = fn_args.iter().skip(1).collect::<Vec<_>>();
19+
quote! { #receiver, ctx: Ctx, #(#remains),*}
20+
} else {
21+
quote! { ctx: Ctx, #fn_args }
22+
};
23+
24+
quote! {
25+
// マクロ適用関数
26+
#fn_visibility fn #fn_ident <Ctx> (#fn_args) #fn_ret_type
27+
where
28+
// 制約
29+
Ctx: #target_trait,
30+
{
31+
// 関数本体
32+
#fn_body
33+
}
34+
}
35+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use proc_macro2::TokenStream;
2+
use quote::{quote, ToTokens};
3+
use syn::{Expr, ItemFn, Stmt};
4+
5+
/// 対象: 関数
6+
/// 動作: 文脈付き呼び出しを行うための前準備を生成 & 文脈付き呼び出しへの変換
7+
pub fn proc_macro_impl(args: TokenStream, ast: ItemFn) -> TokenStream {
8+
let target_trait = args;
9+
10+
let stmts = &ast
11+
.block
12+
.stmts
13+
.iter()
14+
.map(append_ctx_if_calling)
15+
.collect::<Vec<_>>();
16+
17+
quote! {
18+
// 前準備
19+
#[allow(non_local_definitions)]
20+
impl #target_trait for Ctx {}
21+
22+
// 関数本体
23+
#(#stmts);*
24+
}
25+
}
26+
27+
fn append_ctx_if_calling(stmt: &Stmt) -> TokenStream {
28+
let append_ctx = |expr: &Expr| {
29+
if let Expr::Call(call) = expr {
30+
let func = call.func.to_token_stream();
31+
let args = call.args.to_token_stream();
32+
quote! { #func (Ctx, #args) }
33+
} else if let Expr::MethodCall(call) = expr {
34+
let receiver = call.receiver.to_token_stream();
35+
let method = call.method.to_token_stream();
36+
let args = call.args.to_token_stream();
37+
quote! { #receiver . #method (Ctx, #args) }
38+
} else {
39+
stmt.to_token_stream()
40+
}
41+
};
42+
43+
match stmt {
44+
Stmt::Expr(expr, _) => append_ctx(expr),
45+
_ => stmt.to_token_stream(),
46+
}
47+
}

crates/macros/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
mod r#impl;
2+
3+
use proc_macro2::TokenStream;
4+
use syn::{parse_macro_input, ItemFn, ItemTrait};
5+
6+
#[proc_macro_attribute]
7+
pub fn modifier(
8+
attr: proc_macro::TokenStream,
9+
item: proc_macro::TokenStream,
10+
) -> proc_macro::TokenStream {
11+
let args: TokenStream = attr.into();
12+
let ast = parse_macro_input!(item as ItemTrait);
13+
r#impl::modifier::proc_macro_impl(args, ast).into()
14+
}
15+
16+
#[proc_macro_attribute]
17+
pub fn modifier_caller(
18+
attr: proc_macro::TokenStream,
19+
item: proc_macro::TokenStream,
20+
) -> proc_macro::TokenStream {
21+
let args: TokenStream = attr.into();
22+
let ast = parse_macro_input!(item as ItemFn);
23+
r#impl::modifier_caller::proc_macro_impl(args, ast).into()
24+
}
25+
26+
#[proc_macro_attribute]
27+
pub fn modifier_callee(
28+
attr: proc_macro::TokenStream,
29+
item: proc_macro::TokenStream,
30+
) -> proc_macro::TokenStream {
31+
let args: TokenStream = attr.into();
32+
let ast = parse_macro_input!(item as ItemFn);
33+
r#impl::modifier_callee::proc_macro_impl(args, ast).into()
34+
}

crates/macros/tests/modifier.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use mymodifier_macros::*;
2+
3+
#[modifier]
4+
pub trait A {}
5+
6+
#[modifier]
7+
pub trait B {}
8+
9+
#[modifier]
10+
pub trait C {}
11+
12+
#[test]
13+
fn check_compile() {}

0 commit comments

Comments
 (0)