Updated to 0.2.0

This commit is contained in:
2026-01-31 20:52:02 +01:00
parent a30c1ac52a
commit 0b7a72dc5d
56 changed files with 7365 additions and 0 deletions
@@ -0,0 +1,132 @@
markdown
---
sidebar_position: 2
title: Internals: Macros
---
# Internals: Macros
OSUI heavily relies on procedural macros to provide its ergonomic, declarative syntax. The `osui-macros` crate contains the logic for the `#[component]` attribute macro and the `rsx!` function macro. Understanding how these macros work under the hood gives you a deeper insight into OSUI's architecture and capabilities.
## Introduction to Procedural Macros
Procedural macros are functions that operate on the Rust syntax tree (Abstract Syntax Tree or AST) during compilation. They receive `TokenStream`s as input and produce `TokenStream`s as output, effectively transforming your code. OSUI's macros leverage the following crates:
* **`syn`**: A parser for Rust's syntax tree. It allows macros to parse input `TokenStream`s into structured Rust AST types (like `ItemFn`, `Expr`, `Path`, etc.).
* **`quote`**: A quasiquoting library that makes it easy to generate Rust code (as `TokenStream`s) from AST fragments.
* **`proc-macro2`**: Provides types like `TokenStream` and `Ident` that are compatible with `syn` and `quote`, enabling ergonomic manipulation of tokens.
## `#[component]` Attribute Macro (`macros/src/lib.rs`)
The `#[component]` macro transforms a Rust function into an OSUI component struct.
### Input
It takes an `ItemFn` (the parsed function definition) as input.
```rust
// Original function in user's code
#[component]
pub fn MyComponent(cx: &Arc<Context>, prop1: &String, prop2: &i32) -> View {
// ... function body ...
}
```
### Core Logic
1. **Parse Function Signature**:
* It extracts the function's name (`MyComponent`).
* It validates the first argument `cx: &Arc<Context>`.
* It iterates through the remaining arguments (`prop1: &String`, `prop2: &i32`), which become the component's props.
2. **Generate Component Struct**: For each prop parameter, it determines the *owned* type (e.g., `&String` becomes `String`, `&i32` becomes `i32`). It then uses `quote!` to generate a new struct:
```rust
pub struct MyComponent {
pub prop1: String, // Owned types
pub prop2: i32,
}
```
3. **Implement `ComponentImpl`**: It then generates an `impl ComponentImpl for MyComponent` block. The `call` method of this trait:
* Takes `&self` and `cx: &Arc<Context>`.
* Internally calls a generated `Self::component` method (which is your original function's body).
* Passes `cx` and references (`&self.prop1`, `&self.prop2`) to the stored props from the generated struct.
```rust
impl MyComponent {
// This is your original function, renamed and wrapped
pub fn component(cx: &Arc<Context>, prop1: &str, prop2: &i32) -> View { /* ... body ... */ }
}
impl ComponentImpl for MyComponent {
fn call(&self, cx: &Arc<Context>) -> View {
Self::component(cx, &self.prop1, &self.prop2) // Pass references to owned props
}
}
```
### Output
The macro replaces the original function with the generated struct, its `component` method, and the `ComponentImpl` implementation. This transformation makes `MyComponent` a valid OSUI component that can be instantiated with props in `rsx!`.
## `rsx!` Function Macro (`macros/src/parse.rs` & `macros/src/emit.rs`)
The `rsx!` macro is more complex, involving a two-step process: parsing the custom syntax and then emitting standard Rust code.
### 1. Parsing (`macros/src/parse.rs`)
The `parse` module defines an AST (Abstract Syntax Tree) for the `rsx!` syntax.
* **`RsxRoot`**: The top-level container, holding a vector of `RsxNode`s.
* **`RsxNode`**: An enum representing different types of nodes in the `rsx!` tree:
* `Text(LitStr)`: For `"Hello"` literals.
* `Expr(Expr)`: For `@{some_expression}` blocks.
* `Component { path: Path, props: Vec<RsxProp>, children: Vec<RsxNode> }`: For `MyComponent { prop: val, ... }`.
* `Mount(Ident)`: For `!my_mount_hook`.
* `If { deps: Vec<Dep>, cond: Expr, children: Vec<RsxNode> }`: For `@if condition { ... }`.
* `For { deps: Vec<Dep>, pat: Pat, expr: Expr, children: Vec<RsxNode> }`: For `@for item in items { ... }`.
* **`RsxProp`**: Represents a `name: value` pair for component props.
* **`Dep`**: Represents a dependency for dynamic blocks (`%my_state as my_alias`).
The `parse::RsxRoot::parse` method uses `syn`'s `ParseStream` to tokenize the `rsx!` input and build this AST. It intelligently differentiates between text, expressions, component names, and control flow keywords (`@if`, `@for`, `!`).
### 2. Emitting (`macros/src/emit.rs`)
The `emit` module takes the parsed `RsxRoot` AST and converts it into a `TokenStream` of standard Rust code that constructs `osui::frontend::Rsx` objects.
* **`emit_rsx(root: RsxRoot)`**: The entry point, which initializes an `osui::frontend::Rsx` object and then iterates through the `root.nodes`.
* **`emit_node_scope(node: &RsxNode)`**: For each `RsxNode`, it generates code that calls the appropriate `Rsx` builder method:
* **`RsxNode::Text`**: Emits `r.static_scope(move |scope| { scope.view(...) });` which draws text.
* **`RsxNode::Expr`**: Emits `r.child(expression);`.
* **`RsxNode::Component`**: Emits `r.static_scope(move |scope| { scope.child(ComponentName { props: ... }, None); });`. If children are present, they are recursively emitted into a nested `Rsx` object and passed as the `children` prop.
* **`RsxNode::Mount`**: Emits `mount_hook_instance.mount();`.
* **`RsxNode::If`**: Emits `r.dynamic_scope(move |scope| { if condition { ... } else { ... } }, dependencies);`. The `dependencies` are converted to `Vec<Arc<dyn HookDependency>>`.
* **`RsxNode::For`**: Similar to `If`, emits `r.dynamic_scope(move |scope| { for pattern in expr { ... } }, dependencies);`.
### Output
The `rsx!` macro produces a `TokenStream` that looks something like this (simplified):
```rust
// For: rsx! { "Hello" MyComponent { prop: value } }
{
let mut r = osui::frontend::Rsx::new();
r.static_scope(move |scope| {
scope.view(std::sync::Arc::new(move |ctx| {
ctx.draw_text(osui::render::Point { x: 0, y: 0 }, &format!("Hello"))
}));
});
r.static_scope(move |scope| {
scope.child(
MyComponent { prop: value.to_string() }, // Note: Prop value converted to owned type
None,
);
});
r
}
```
This generated code, when compiled, constructs the `osui::frontend::Rsx` object that OSUI's runtime can then interpret to build the component tree and render the UI.
## Summary
The `osui-macros` crate plays a pivotal role in shaping OSUI's developer experience. `#[component]` streamlines component definition, while `rsx!` provides a powerful, declarative way to compose UIs by transforming custom syntax into efficient runtime calls. These macros are complex but essential for creating a modern, React-like development flow in a TUI environment.
**Next:** Learn how you can contribute to the OSUI project in [Contributing](./03-contributing.md).