- 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
409 lines
9.8 KiB
Rust
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,
|
|
}
|