Skip to content

Commit 9b79af0

Browse files
committed
Add compiler-macros crate for parser development macros
Introduces the new `compiler-macros` crate to the workspace. This crate provides procedural macros designed to simplify and reduce boilerplate for common patterns used throughout the new parser implementation. By abstracting away repetitive logic, this macros make the parser's components cleaner and easier to maintain.
1 parent ac59281 commit 9b79af0

File tree

6 files changed

+367
-0
lines changed

6 files changed

+367
-0
lines changed

Cargo.lock

Lines changed: 47 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ version = "0.1.0"
44
edition = "2024"
55

66
[dependencies]
7+
compiler-macros = { path = "./compiler_macros" }

compiler_macros/Cargo.lock

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

compiler_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 = "compiler-macros"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
proc-macro2 = "1.0"
11+
quote = "1.0"
12+
syn = { version = "2.0", features = ["full"] }

compiler_macros/prd.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Simple Builder Proc-Macro PRD
2+
3+
## 1. Objective
4+
5+
To provide a simple and ergonomic solution for creating builder patterns in Rust. This proc-macro will automate the generation of a basic builder, reducing boilerplate code for simple use cases.
6+
7+
## 2. Background
8+
9+
The builder pattern is a useful way to construct complex objects. However, writing the builder struct and its methods can be tedious. This proc-macro automates the creation of a simple builder.
10+
11+
## 3. User Persona
12+
13+
This tool is for Rust developers who want a quick and easy way to create a builder for their structs without the complexity of a full type-state implementation.
14+
15+
## 4. API Definition
16+
17+
The public API will consist of a single derive macro:
18+
19+
```rust
20+
use simple_builder::SimpleBuilder;
21+
22+
#[derive(SimpleBuilder, Default)]
23+
#[builder_name = "MyCustomBuilderName"] // Optional: to rename the builder struct
24+
pub struct MyStruct {
25+
// ... fields
26+
}
27+
```
28+
29+
- `#[derive(SimpleBuilder)]`: The main entry point. It will trigger the builder generation.
30+
- `#[builder_name = "..."]` (optional attribute): Allows the user to specify a custom name for the generated builder struct. If not provided, it will default to `[StructName]Builder`.
31+
32+
### Constraints
33+
34+
- The target struct **must** implement the `Default` trait.
35+
36+
## 5. Requirements & Features
37+
38+
### 5.1. "With Defaults" Builder Logic
39+
40+
The builder will follow a "with defaults" strategy. This means:
41+
42+
- The `build()` method can be called at any time.
43+
- If a field has been set using its `with_...` method, the builder will use that value.
44+
- If a field has not been set, the builder will use the value from the struct's `Default` implementation.
45+
46+
### 5.2. Generated Code
47+
48+
The macro will generate:
49+
50+
1. A public `...Builder` struct with `Option` fields.
51+
2. A `new()` method to create an empty builder.
52+
3. A `with_<field_name>()` method for each field in the target struct.
53+
4. A `build()` method that constructs the target struct, intelligently combining user-provided values with default values.
54+
55+
## 6. Acceptance Criteria
56+
57+
To consider this feature complete and correct, the following criteria must be met:
58+
59+
1. **Compilation:** A struct decorated with `#[derive(SimpleBuilder, Default)]` must compile successfully.
60+
2. **Builder Generation:** The corresponding `...Builder` struct must be generated and be publicly accessible.
61+
3. **`new()` Method:** The `...Builder::new()` method must exist and return a new builder instance.
62+
4. **`with_...` Methods:** Each field in the target struct must have a corresponding `with_<field_name>()` method on the builder.
63+
5. **`build()` Method:** The `build()` method must be callable on the builder at any time.
64+
6. **Correctness (Set Fields):** When a field is set via its `with_...` method, the `build()` method must produce a struct with that exact value for that field.
65+
7. **Correctness (Unset Fields):** When a field is not set, the `build()` method must produce a struct where that field has the value from the `Default` implementation.
66+
67+
## 7. Out of Scope (Future Work)
68+
69+
- **Type-State Builder:** A more advanced builder that uses the type-state pattern to enforce compile-time correctness.
70+
- **Generic Structs:** Support for structs with generic parameters.
71+
- **Required Fields:** An attribute to mark certain fields as required.
72+
- **Validation:** Adding validation logic to the `build()` method.

compiler_macros/src/lib.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::{Data, DeriveInput, Fields, parse_macro_input};
4+
5+
/// Derive macro for implementing `AstNode` as a leaf node (non-container).
6+
///
7+
/// This automatically implements `HasSpan`, `HasNodeType`, and `AstNode` with
8+
/// `is_container() = false`.
9+
///
10+
/// Requires a `span` field of type `SymbolSpan`.
11+
///
12+
/// # Example
13+
///
14+
/// ```rust
15+
/// #[derive(AstLeafNode)]
16+
/// struct StringLit {
17+
/// pub value: String,
18+
/// pub span: SymbolSpan,
19+
/// }
20+
/// ```
21+
#[proc_macro_derive(AstLeafNode)]
22+
pub fn derive_ast_leaf_node(input: TokenStream) -> TokenStream {
23+
let input = parse_macro_input!(input as DeriveInput);
24+
let name = &input.ident;
25+
let generics = input.generics.clone();
26+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
27+
28+
// Verify the struct has a `span` field
29+
let has_span_field = match &input.data {
30+
Data::Struct(data_struct) => match &data_struct.fields {
31+
Fields::Named(fields) => fields.named.iter().any(|field| {
32+
field.ident.as_ref().is_some_and(|ident| ident == "span")
33+
}),
34+
_ => false,
35+
},
36+
_ => false,
37+
};
38+
39+
if !has_span_field {
40+
return syn::Error::new_spanned(
41+
&input,
42+
"AstLeafNode can only be derived for structs with a `span: SymbolSpan` field"
43+
).to_compile_error().into();
44+
}
45+
46+
let type_name = name.to_string();
47+
48+
let expanded = quote! {
49+
impl #impl_generics HasSpan for #name #ty_generics #where_clause {
50+
fn span(&self) -> &SymbolSpan {
51+
&self.span
52+
}
53+
}
54+
55+
impl #impl_generics HasNodeType for #name #ty_generics #where_clause {
56+
fn node_type(&self) -> &'static str {
57+
#type_name
58+
}
59+
}
60+
61+
impl #impl_generics AstNode for #name #ty_generics #where_clause {}
62+
63+
impl #impl_generics AstVisitable for #name #ty_generics #where_clause {}
64+
};
65+
66+
TokenStream::from(expanded)
67+
}
68+
69+
/// Derive macro for implementing `AstNode` as a container node.
70+
///
71+
/// This automatically implements `HasSpan`, `HasNodeType`, and `AstNode` with
72+
/// `is_container() = true`.
73+
///
74+
/// Requires a `span` field of type `SymbolSpan`.
75+
/// The `accept` method should be implemented manually for visiting children.
76+
///
77+
/// # Example
78+
///
79+
/// ```rust
80+
/// #[derive(AstContainerNode)]
81+
/// struct ModelDecl {
82+
/// pub name: Ident,
83+
/// pub members: Vec<ModelMember>,
84+
/// pub span: SymbolSpan,
85+
/// }
86+
/// ```
87+
#[proc_macro_derive(AstContainerNode)]
88+
pub fn derive_ast_container_node(input: TokenStream) -> TokenStream {
89+
let input = parse_macro_input!(input as DeriveInput);
90+
let name = &input.ident;
91+
let generics = input.generics.clone();
92+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
93+
94+
// Verify the struct has a `span` field
95+
let has_span_field = match &input.data {
96+
Data::Struct(data_struct) => match &data_struct.fields {
97+
Fields::Named(fields) => fields.named.iter().any(|field| {
98+
field.ident.as_ref().is_some_and(|ident| ident == "span")
99+
}),
100+
_ => false,
101+
},
102+
_ => false,
103+
};
104+
105+
if !has_span_field {
106+
return syn::Error::new_spanned(
107+
&input,
108+
"AstContainerNode can only be derived for structs with a `span: SymbolSpan` field"
109+
).to_compile_error().into();
110+
}
111+
112+
let type_name = name.to_string();
113+
114+
let expanded = quote! {
115+
impl #impl_generics HasSpan for #name #ty_generics #where_clause {
116+
fn span(&self) -> &SymbolSpan {
117+
&self.span
118+
}
119+
}
120+
121+
impl #impl_generics HasNodeType for #name #ty_generics #where_clause {
122+
fn node_type(&self) -> &'static str {
123+
#type_name
124+
}
125+
}
126+
127+
impl #impl_generics AstNode for #name #ty_generics #where_clause {
128+
fn is_container(&self) -> bool {
129+
true
130+
}
131+
}
132+
133+
impl #impl_generics AstVisitable for #name #ty_generics #where_clause {}
134+
};
135+
136+
TokenStream::from(expanded)
137+
}
138+
139+
/// Derive an inherent `name()` method for an enum that returns the variant name.
140+
///
141+
/// For unit variants, the match arm uses `Type::Variant`.
142+
/// For tuple variants, it uses `Type::Variant(..)`.
143+
/// For struct variants, it uses `Type::Variant { .. }`.
144+
///
145+
/// # Example
146+
///
147+
/// ```rust
148+
/// #[derive(EnumKindName)]
149+
/// enum K { Unit, Tuple(u8), Struct { x: u8 } }
150+
/// # impl K { /* name() generated */ }
151+
/// ```
152+
#[proc_macro_derive(EnumKindName)]
153+
pub fn derive_enum_kind_name(input: TokenStream) -> TokenStream {
154+
let input = parse_macro_input!(input as DeriveInput);
155+
let name = &input.ident;
156+
157+
let Data::Enum(data_enum) = &input.data else {
158+
return syn::Error::new_spanned(
159+
&input,
160+
"EnumKindName can only be derived for enums",
161+
)
162+
.to_compile_error()
163+
.into();
164+
};
165+
166+
let arms = data_enum.variants.iter().map(|v| {
167+
let v_ident = &v.ident;
168+
let v_name = v_ident.to_string();
169+
match &v.fields {
170+
Fields::Unit => quote! { #name::#v_ident => #v_name },
171+
Fields::Unnamed(_) => quote! { #name::#v_ident(..) => #v_name },
172+
Fields::Named(_) => quote! { #name::#v_ident { .. } => #v_name },
173+
}
174+
});
175+
176+
let expanded = quote! {
177+
impl #name {
178+
/// Return the enum variant name.
179+
pub fn name(&self) -> &'static str {
180+
match self {
181+
#( #arms, )*
182+
}
183+
}
184+
}
185+
};
186+
187+
TokenStream::from(expanded)
188+
}

0 commit comments

Comments
 (0)