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}