Skip to main content

meow_meow_script/
ast.rs

1// All AST types in one place to avoid circular module dependencies.
2// `Expression::Function` contains `BlockStatement` which contains `Vec<Statement>` which
3// contains `Expression` — putting them in separate files would create a true circular import.
4
5#[derive(Debug, Clone, PartialEq)]
6pub struct Span {
7    pub start: usize,
8    pub end: usize,
9}
10
11impl Span {
12    pub fn new(start: usize, end: usize) -> Self {
13        Self { start, end }
14    }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Ident(pub String);
19
20// ---------------------------------------------------------------------------
21// Expressions
22// ---------------------------------------------------------------------------
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum Expression {
26    String(String),
27    Number(f64),
28    /// Numeric literal with a unit suffix in source (e.g. `50%`, `20gu`,
29    /// `30deg`). Evaluator materializes as `Value::Dimension`; consumers
30    /// like the Style setters convert to `SizeDimension`.
31    Dimension(f64, crate::token::Unit),
32    Bool(bool),
33    Null,
34    Identifier(Ident),
35    Array(Vec<Expression>),
36    Table(Vec<TableFieldValue>),
37    Index {
38        base: Box<Expression>,
39        index: Box<Expression>,
40    },
41    Call(CallExpression),
42    Component(ComponentExpression),
43    BinaryOp {
44        op: BinOpKind,
45        lhs: Box<Expression>,
46        rhs: Box<Expression>,
47    },
48    UnaryOp {
49        op: UnaryOpKind,
50        operand: Box<Expression>,
51    },
52    Function {
53        params: Vec<Ident>,
54        body: BlockStatement,
55    },
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub struct TableFieldValue {
60    pub name: Ident,
61    pub value: Expression,
62}
63
64#[derive(Debug, Clone, PartialEq)]
65pub enum BinOpKind {
66    Add,
67    Sub,
68    Mul,
69    Div,
70    Rem,
71    Eq,
72    NotEq,
73    Lt,
74    Gt,
75    LtEq,
76    GtEq,
77    And,
78    Or,
79    Pipe,  // |> forward pipe (function application)
80    Query, // -> component query / dispatch
81    Dot,   // obj.method(args) — method receiver
82}
83
84#[derive(Debug, Clone, PartialEq)]
85pub enum UnaryOpKind {
86    Neg,
87    Not,
88}
89
90/// A free-standing function call: `foo(a, b)` or a method call `obj.method(a, b)`.
91///
92/// For plain calls the callee is `Expression::Identifier`.
93/// For method calls the callee is `Expression::BinaryOp { op: BinOpKind::Dot, .. }`.
94#[derive(Debug, Clone, PartialEq)]
95pub struct CallExpression {
96    pub callee: Box<Expression>,
97    pub args: Vec<Expression>,
98}
99
100/// A constructor or chained builder call on a component expression header.
101///
102/// `T.position(x, y, z).scale(a, b, c)` produces two `ConstructorCall`s:
103/// `[{ method: "position", args: [x,y,z] }, { method: "scale", args: [a,b,c] }]`
104///
105/// The first entry is the "primary" constructor (selects the component variant).
106/// Subsequent entries are chained builder calls applied after creation.
107#[derive(Debug, Clone, PartialEq)]
108pub struct ConstructorCall {
109    pub method: Ident,
110    pub args: Vec<Expression>,
111}
112
113/// A component expression: the declarative tree-building form.
114///
115/// `ComponentType.method(args)[.method2(args2)...] { body }`
116///
117/// - `T { ... }` — no constructor, has body
118/// - `R.cube()` — one constructor, no body
119/// - `T.position(x,y,z).scale(a,b,c) { C {} }` — two constructors + body
120///
121/// The body is a plain `BlockStatement`: all MMS language features are
122/// available inside it. CE emissions inside the body become children of this
123/// node; builder calls (identifiers not in env) configure this component.
124#[derive(Debug, Clone, PartialEq)]
125pub struct ComponentExpression {
126    pub component_type: Ident,
127    /// All chained constructor calls from the header (before `{`), in order.
128    /// Empty when there is no `.method(...)` on the type name.
129    pub constructors: Vec<ConstructorCall>,
130    pub body: BlockStatement,
131}
132
133// ---------------------------------------------------------------------------
134// Statements
135// ---------------------------------------------------------------------------
136
137#[derive(Debug, Clone, PartialEq)]
138pub struct BlockStatement {
139    pub statements: Vec<Statement>,
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub enum Statement {
144    Assignment(AssignmentStatement),
145    /// `x = expr` or `obj.field = expr` — mutate an existing binding or table field.
146    ///
147    /// Scope note (v1): reassignment updates the binding in the current block's local env.
148    /// It does NOT propagate outward to enclosing scopes — that requires a scope chain
149    /// (deferred). Inside a `for` loop body the loop's accumulated env is used, so
150    /// accumulator patterns (`sum = sum + i`) work correctly within the loop.
151    Reassign {
152        target: Expression,
153        value: Expression,
154    },
155    Return(ReturnStatement),
156    If(IfStatement),
157    Block(BlockStatement),
158    Expression(Expression),
159    ForIn {
160        binding: Ident,
161        iterable: Expression,
162        body: BlockStatement,
163    },
164    While {
165        condition: Expression,
166        body: BlockStatement,
167    },
168    Break,
169    Continue,
170    Import {
171        items: Vec<ImportItem>,
172        path: String,
173    },
174}
175
176#[derive(Debug, Clone, PartialEq)]
177pub struct AssignmentStatement {
178    pub name: Ident,
179    pub value: Expression,
180    /// `true` when declared with `export let` / `export fn`.
181    pub exported: bool,
182}
183
184/// One item in an `import { ... } from "..."` list.
185#[derive(Debug, Clone, PartialEq)]
186pub enum ImportItem {
187    /// `{ name }` — import a named export.
188    Named(Ident),
189    /// `{ name as alias }` — import a named export under a different local name.
190    NamedAlias { name: Ident, alias: Ident },
191    /// `{ 0 as alias }` — import the Nth root CE emit, bound to `alias`.
192    PositionalAlias { index: usize, alias: Ident },
193}
194
195#[derive(Debug, Clone, PartialEq)]
196pub struct ReturnStatement {
197    pub value: Option<Expression>,
198}
199
200#[derive(Debug, Clone, PartialEq)]
201pub struct IfStatement {
202    pub condition: Expression,
203    pub then_branch: BlockStatement,
204    pub else_branch: Option<ElseBranch>,
205}
206
207#[derive(Debug, Clone, PartialEq)]
208pub enum ElseBranch {
209    Block(BlockStatement),
210    If(Box<IfStatement>),
211}