dreamstack/compiler/ds-parser/src/ast.rs
enzotar 7805b94704 feat: component registry with styled variants, dreamstack add/convert CLI, and showcase
- Phase 1: Component parser + codegen (emit_component_decl, emit_component_use, emit_match)
- Phase 2: 6 registry components (button, input, card, badge, dialog, toast)
- Phase 3: dreamstack add CLI with dependency resolution and --list/--all
- Phase 4: dreamstack convert TSX→DS transpiler with --shadcn GitHub fetch
- Phase 5: 120+ lines variant CSS (buttons, badges, cards, dialog, toast, input)
- New example: showcase.ds demonstrating all component styles
2026-02-26 13:27:49 -08:00

409 lines
9.8 KiB
Rust

/// The AST for DreamStack.
/// Homoiconic: every node is also representable as data (tagged vectors/maps).
/// A complete DreamStack program is a list of top-level declarations.
#[derive(Debug, Clone)]
pub struct Program {
pub declarations: Vec<Declaration>,
}
/// Top-level declarations.
#[derive(Debug, Clone)]
pub enum Declaration {
/// `let name = expr`
Let(LetDecl),
/// `view name = expr` or `view name(params) = expr`
View(ViewDecl),
/// `effect name(params): ReturnType`
Effect(EffectDecl),
/// `on event_name -> body`
OnHandler(OnHandler),
/// `component Name(props) = body`
Component(ComponentDecl),
/// `route "/path" -> view_expr`
Route(RouteDecl),
/// `constrain element.prop = expr`
Constrain(ConstrainDecl),
/// `stream main on "ws://..." { mode: signal }`
Stream(StreamDecl),
/// `every 500 -> expr`
Every(EveryDecl),
/// Top-level expression statement: `log("hello")`, `push(items, x)`
ExprStatement(Expr),
/// `import { Card, Button } from "./components"`
Import(ImportDecl),
/// `export let count = 0`, `export component Card(...) = ...`
Export(String, Box<Declaration>),
/// `type PositiveInt = Int where value > 0`
TypeAlias(TypeAliasDecl),
/// `layout dashboard { sidebar.width == 250 }`
Layout(LayoutDecl),
}
/// `layout name { constraints }`
#[derive(Debug, Clone)]
pub struct LayoutDecl {
pub name: String,
pub constraints: Vec<LayoutConstraint>,
pub span: Span,
}
/// A single layout constraint: `left op right [strength]`
#[derive(Debug, Clone)]
pub struct LayoutConstraint {
pub left: LayoutExpr,
pub op: ConstraintOp,
pub right: LayoutExpr,
pub strength: ConstraintStrength,
}
/// Constraint operators.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConstraintOp {
Eq, // ==
Gte, // >=
Lte, // <=
}
/// Constraint strength/priority.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConstraintStrength {
Required,
Strong,
Medium,
Weak,
}
impl Default for ConstraintStrength {
fn default() -> Self { ConstraintStrength::Required }
}
/// A layout expression: `element.property`, `200`, or `expr + expr`.
#[derive(Debug, Clone)]
pub enum LayoutExpr {
/// `sidebar.width`, `parent.height`
Prop(String, String),
/// Numeric constant
Const(f64),
/// Addition: `sidebar.x + sidebar.width`
Add(Box<LayoutExpr>, Box<LayoutExpr>),
/// Subtraction: `parent.width - sidebar.width`
Sub(Box<LayoutExpr>, Box<LayoutExpr>),
/// Multiplication: `parent.width * 0.3`
Mul(Box<LayoutExpr>, Box<LayoutExpr>),
}
/// `import { Card, Button } from "./components"`
#[derive(Debug, Clone)]
pub struct ImportDecl {
pub names: Vec<String>,
pub source: String,
pub span: Span,
}
/// `let count = 0` or `let doubled = count * 2`
#[derive(Debug, Clone)]
pub struct LetDecl {
pub name: String,
pub type_annotation: Option<TypeExpr>,
pub value: Expr,
pub span: Span,
}
/// `view counter = column [ ... ]`
/// `view profile(id: UserId) = ...`
#[derive(Debug, Clone)]
pub struct ViewDecl {
pub name: String,
pub params: Vec<Param>,
pub body: Expr,
pub span: Span,
}
/// `effect fetchUser(id: UserId): Result<User, ApiError>`
#[derive(Debug, Clone)]
pub struct EffectDecl {
pub name: String,
pub params: Vec<Param>,
pub return_type: TypeExpr,
pub span: Span,
}
/// `on toggle_sidebar -> ...`
#[derive(Debug, Clone)]
pub struct OnHandler {
pub event: String,
pub param: Option<String>,
pub body: Expr,
pub span: Span,
}
/// `component Card(title: String, children: View) = ...`
#[derive(Debug, Clone)]
pub struct ComponentDecl {
pub name: String,
pub props: Vec<Param>,
pub body: Expr,
pub span: Span,
}
/// `route "/users/:id" -> column [ text "User {id}" ]`
#[derive(Debug, Clone)]
pub struct RouteDecl {
pub path: String,
pub body: Expr,
pub span: Span,
}
/// `constrain sidebar.width = clamp(200, parent.width * 0.2, 350)`
#[derive(Debug, Clone)]
pub struct ConstrainDecl {
pub element: String,
pub prop: String,
pub expr: Expr,
pub span: Span,
}
/// `stream main on "ws://localhost:9100" { mode: signal }`
#[derive(Debug, Clone)]
pub struct StreamDecl {
pub view_name: String,
pub relay_url: Expr,
pub mode: StreamMode,
pub transport: StreamTransport,
/// Explicit signal output list. Empty = stream all signals (backwards-compat).
pub output: Vec<String>,
pub span: Span,
}
/// `every 500 -> expr` — periodic timer
#[derive(Debug, Clone)]
pub struct EveryDecl {
pub interval_ms: Expr,
pub body: Expr,
pub span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StreamMode {
Pixel, // raw RGBA framebuffer every frame
Delta, // XOR + RLE — only changed pixels
Signal, // JSON signal diffs (DreamStack-native)
}
impl Default for StreamMode {
fn default() -> Self { StreamMode::Signal }
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StreamTransport {
WebSocket, // default — frames over WebSocket via relay
WebRTC, // peer-to-peer data channels, relay for signaling only
}
impl Default for StreamTransport {
fn default() -> Self { StreamTransport::WebSocket }
}
/// Function/view parameter.
#[derive(Debug, Clone)]
pub struct Param {
pub name: String,
pub type_annotation: Option<TypeExpr>,
}
/// Type expressions (simplified for Phase 0).
#[derive(Debug, Clone)]
pub enum TypeExpr {
Named(String),
Generic(String, Vec<TypeExpr>),
/// Refinement type: `Int where value > 0`
Refined {
base: Box<TypeExpr>,
predicate: Box<Expr>,
},
}
/// `type PositiveInt = Int where value > 0`
#[derive(Debug, Clone)]
pub struct TypeAliasDecl {
pub name: String,
pub definition: TypeExpr,
pub span: Span,
}
/// Expressions — the core of the language.
#[derive(Debug, Clone)]
pub enum Expr {
/// Integer literal: `42`
IntLit(i64),
/// Float literal: `3.14`
FloatLit(f64),
/// String literal: `"hello"` (may contain `{interpolation}`)
StringLit(StringLit),
/// Boolean literal: `true` / `false`
BoolLit(bool),
/// Identifier: `count`, `sidebar.width`
Ident(String),
/// Dotted access: `user.name`
DotAccess(Box<Expr>, String),
/// Binary operation: `a + b`, `count > 10`
BinOp(Box<Expr>, BinOp, Box<Expr>),
/// Unary operation: `-x`, `!flag`
UnaryOp(UnaryOp, Box<Expr>),
/// Assignment: `count += 1`, `panel_x.target = 0`
Assign(Box<Expr>, AssignOp, Box<Expr>),
/// Function call: `clamp(200, 20vw, 350)`
Call(String, Vec<Expr>),
/// Block: multiple expressions, last is the value
Block(Vec<Expr>),
/// View element: `text "hello"`, `button "+" { click: ... }`
Element(Element),
/// Container: `column [ ... ]`, `row [ ... ]`
Container(Container),
/// When conditional: `when count > 10 -> ...`
When(Box<Expr>, Box<Expr>),
/// Match expression
Match(Box<Expr>, Vec<MatchArm>),
/// Pipe: `expr | operator`
Pipe(Box<Expr>, Box<Expr>),
/// `perform effectName(args)`
Perform(String, Vec<Expr>),
/// `stream from source`
StreamFrom {
source: String,
mode: Option<StreamMode>,
/// Receiver-side field selector. Empty = receive all fields.
select: Vec<String>,
},
/// Lambda: `(x -> x * 2)`
Lambda(Vec<String>, Box<Expr>),
/// Record literal: `{ key: value, ... }`
Record(Vec<(String, Expr)>),
/// List literal: `[a, b, c]`
List(Vec<Expr>),
/// `if cond then a else b`
If(Box<Expr>, Box<Expr>, Box<Expr>),
/// Spring: `spring(target: 0, stiffness: 300, damping: 30)`
Spring(Vec<(String, Expr)>),
/// `for item in items -> body` with optional index `for item, i in items -> body`
ForIn {
item: String,
index: Option<String>,
iter: Box<Expr>,
body: Box<Expr>,
},
/// Component instantiation: `<Card title="hello" />`
ComponentUse {
name: String,
props: Vec<(String, Expr)>,
children: Vec<Expr>,
},
/// Index access: `grid[i]`, `pads[8 + i]`
Index(Box<Expr>, Box<Expr>),
}
/// String literal with interpolation segments.
#[derive(Debug, Clone)]
pub struct StringLit {
pub segments: Vec<StringSegment>,
}
#[derive(Debug, Clone)]
pub enum StringSegment {
Literal(String),
Interpolation(Box<Expr>),
}
/// Binary operators.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Neq,
Lt,
Gt,
Lte,
Gte,
And,
Or,
}
/// Unary operators.
#[derive(Debug, Clone, Copy)]
pub enum UnaryOp {
Neg,
Not,
}
/// Assignment operators.
#[derive(Debug, Clone, Copy)]
pub enum AssignOp {
Set,
AddAssign,
SubAssign,
}
/// A UI element: `text label`, `button "+" { click: handler }`
#[derive(Debug, Clone)]
pub struct Element {
pub tag: String,
pub args: Vec<Expr>,
pub props: Vec<(String, Expr)>,
pub modifiers: Vec<Modifier>,
}
/// A container: `column [ child1, child2 ]`
#[derive(Debug, Clone)]
pub struct Container {
pub kind: ContainerKind,
pub children: Vec<Expr>,
pub props: Vec<(String, Expr)>,
}
#[derive(Debug, Clone)]
pub enum ContainerKind {
Column,
Row,
Stack,
List,
Panel,
Form,
Scene,
Custom(String),
}
/// Match arm: `Ok(u) -> column [ ... ]`
#[derive(Debug, Clone)]
pub struct MatchArm {
pub pattern: Pattern,
pub body: Expr,
}
/// Pattern matching.
#[derive(Debug, Clone)]
pub enum Pattern {
Wildcard,
Ident(String),
Constructor(String, Vec<Pattern>),
Literal(Expr),
}
/// Modifiers: `| animate fade-in 200ms`
#[derive(Debug, Clone)]
pub struct Modifier {
pub name: String,
pub args: Vec<Expr>,
}
/// Source location tracking.
#[derive(Debug, Clone, Copy, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
pub line: usize,
}