Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-macros/src/lib.rs
1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{
4 braced, bracketed,
5 parse::{Parse, ParseStream},
6 parse_macro_input,
7 punctuated::Punctuated,
8 Expr, Ident, Lit, Token,
9};
10 
11// --- Parsed Structures ---
12 
13struct View {
14 root: WidgetNode,
15}
16 
17struct WidgetNode {
18 name: Ident,
19 builder_arg: Option<Expr>,
20 props: Vec<Prop>,
21 children: Option<Vec<Child>>,
22}
23 
24struct Prop {
25 name: Ident,
26 value: PropValue,
27}
28 
29enum PropValue {
30 Node(WidgetNode),
31 Expr(Expr),
32}
33 
34enum Child {
35 Node(WidgetNode),
36 Expr(Expr),
37}
38 
39// --- Parsing Logic ---
40 
41impl Parse for View {
42 fn parse(input: ParseStream) -> syn::Result<Self> {
43 let root = input.parse()?;
44 Ok(View { root })
45 }
46}
47 
48impl Parse for WidgetNode {
49 fn parse(input: ParseStream) -> syn::Result<Self> {
50 let name: Ident = input.parse()?;
51 let content;
52 braced!(content in input);
53 
54 let mut builder_arg = None;
55 let mut props = Vec::new();
56 let mut children = None;
57 
58 if !content.is_empty() {
59 let is_key_value = if content.peek(Ident) {
60 content.peek2(Token![:])
61 } else {
62 false
63 };
64 
65 if !is_key_value {
66 let arg: Expr = content.parse()?;
67 builder_arg = Some(arg);
68 
69 if content.peek(Token![,]) {
70 content.parse::<Token![,]>()?;
71 }
72 }
73 }
74 
75 while !content.is_empty() {
76 if content.peek(Ident) && content.peek2(Token![:]) {
77 let key: Ident = content.parse()?;
78 content.parse::<Token![:]>()?;
79 
80 if key == "children" {
81 let children_content;
82 bracketed!(children_content in content);
83 
84 let parsed_children: Punctuated<Child, Token![,]> =
85 children_content.parse_terminated(Child::parse, Token![,])?;
86 children = Some(parsed_children.into_iter().collect());
87 } else {
88 // Parse value: could be WidgetNode (DSL) or Expr
89 let value = if content.peek(Ident) && content.peek2(syn::token::Brace) {
90 let node: WidgetNode = content.parse()?;
91 PropValue::Node(node)
92 } else {
93 let expr: Expr = content.parse()?;
94 PropValue::Expr(expr)
95 };
96 props.push(Prop { name: key, value });
97 }
98 
99 if content.peek(Token![,]) {
100 content.parse::<Token![,]>()?;
101 }
102 } else {
103 return Err(content.error("Expected property or children"));
104 }
105 }
106 
107 Ok(WidgetNode {
108 name,
109 builder_arg,
110 props,
111 children,
112 })
113 }
114}
115 
116impl Parse for Child {
117 fn parse(input: ParseStream) -> syn::Result<Self> {
118 if input.peek(Ident) && input.peek2(syn::token::Brace) {
119 let node: WidgetNode = input.parse()?;
120 Ok(Child::Node(node))
121 } else {
122 let expr: Expr = input.parse()?;
123 Ok(Child::Expr(expr))
124 }
125 }
126}
127 
128// --- Code Generation ---
129 
130impl ToTokens for View {
131 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
132 // Return the root UiNode
133 self.root.to_tokens(tokens);
134 }
135}
136 
137impl ToTokens for PropValue {
138 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
139 match self {
140 PropValue::Node(_) => {
141 tokens.extend(
142 quote! { compile_error!("Unexpected nested widget in PropValue generation") },
143 );
144 }
145 PropValue::Expr(expr) => {
146 tokens.extend(quote! { strato_core::ui_node::PropValue::from(#expr) });
147 }
148 }
149 }
150}
151 
152impl ToTokens for WidgetNode {
153 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
154 let name_str = self.name.to_string();
155 let props = &self.props;
156 
157 // Handle props
158 let mut prop_tokens = Vec::new();
159 
160 // 1. Add constructor arg as "value" or specific prop
161 if let Some(arg) = &self.builder_arg {
162 // Heuristic: Text/Button -> "text", others -> "value"
163 let prop_name = match name_str.as_str() {
164 "Text" | "Button" | "Label" => "text",
165 "Image" => "source",
166 _ => "value",
167 };
168 
169 prop_tokens.push(quote! {
170 (#prop_name.to_string(), strato_core::ui_node::PropValue::from(#arg))
171 });
172 }
173 
174 // 2. Add standard props
175 for prop in props {
176 let key = prop.name.to_string();
177 let value = &prop.value;
178 
179 match value {
180 PropValue::Node(_node) => {
181 if key == "child" {
182 // Handled in children section
183 } else {
184 // ERROR: Nested widgets in props (other than child) are FORBIDDEN in this pure AST.
185 // We could panic here or emit a compile error.
186 // For now, emit a compile error via quote if possible, or just ignore.
187 // panic!("Nested widgets in properties (except 'child') are not supported in Semantic AST. Found widget in '{}'", key);
188 
189 // Better: prevent compilation
190 let err_msg = format!(
191 "Property '{}' contains a Widget. Widgets can only be children.",
192 key
193 );
194 prop_tokens.push(quote! { compile_error!(#err_msg) });
195 }
196 }
197 PropValue::Expr(expr) => {
198 prop_tokens.push(quote! {
199 (#key.to_string(), strato_core::ui_node::PropValue::from(#expr))
200 });
201 }
202 }
203 }
204 
205 let mut children_tokens = Vec::new();
206 // 1. Explicit children from `children: [...]`
207 if let Some(children) = &self.children {
208 for child in children {
209 match child {
210 Child::Node(node) => {
211 children_tokens.push(quote! { #node });
212 }
213 Child::Expr(expr) => {
214 // Heuristic: string literal -> Text node
215 if let Expr::Lit(syn::ExprLit {
216 lit: Lit::Str(_), ..
217 }) = expr
218 {
219 children_tokens.push(
220 quote! { strato_core::ui_node::UiNode::Text(#expr.to_string()) },
221 );
222 } else {
223 // Dynamic expression? We can't easily turn it into UiNode unless it IS a UiNode.
224 // Assuming expression evaluates to UiNode.
225 children_tokens.push(quote! { #expr });
226 }
227 }
228 }
229 }
230 }
231 
232 // 2. "child" prop moved to children
233 for prop in props {
234 if prop.name == "child" {
235 if let PropValue::Node(node) = &prop.value {
236 children_tokens.push(quote! { #node });
237 }
238 }
239 }
240 
241 tokens.extend(quote! {
242 strato_core::ui_node::UiNode::Widget(strato_core::ui_node::WidgetNode {
243 name: #name_str.to_string(),
244 props: vec![ #(#prop_tokens),* ],
245 children: vec![ #(#children_tokens),* ],
246 })
247 });
248 }
249}
250 
251// --- Macro Entry Point ---
252 
253/// Declarative UI definition macro
254///
255/// ```rust,ignore
256/// use strato_macros::view;
257///
258/// view! {
259/// Column {
260/// spacing: 10.0,
261/// children: [
262/// Text { "Hello" },
263/// Button { child: Text { "Click" } }
264/// ]
265/// }
266/// }
267/// ```
268#[proc_macro]
269pub fn view(input: TokenStream) -> TokenStream {
270 let view_def = parse_macro_input!(input as View);
271 quote! {
272 {
273 use strato_widgets::prelude::*;
274 #view_def
275 }
276 }
277 .into()
278}
279 
280/// Derive macro for Widget trait (Placeholder)
281#[proc_macro_derive(Widget, attributes(widget))]
282pub fn derive_widget(_input: TokenStream) -> TokenStream {
283 TokenStream::new()
284}
285