Skip to main content

normalize_surface_syntax/ir/
expr.rs

1//! Expression types for the IR.
2
3use super::Span;
4use serde::{Deserialize, Serialize};
5
6/// An expression that produces a value.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub enum Expr {
9    /// Literal value.
10    Literal(Literal),
11
12    /// Variable reference.
13    Ident(String),
14
15    /// Binary operation: `left op right`.
16    Binary {
17        left: Box<Expr>,
18        op: BinaryOp,
19        right: Box<Expr>,
20        /// Source location (populated by readers; ignored by writers).
21        #[serde(skip_serializing_if = "Option::is_none")]
22        span: Option<Span>,
23    },
24
25    /// Unary operation: `op expr`.
26    Unary {
27        op: UnaryOp,
28        expr: Box<Expr>,
29        /// Source location (populated by readers; ignored by writers).
30        #[serde(skip_serializing_if = "Option::is_none")]
31        span: Option<Span>,
32    },
33
34    /// Function call: `callee(args...)`.
35    Call {
36        callee: Box<Expr>,
37        args: Vec<Expr>,
38        /// Source location (populated by readers; ignored by writers).
39        #[serde(skip_serializing_if = "Option::is_none")]
40        span: Option<Span>,
41    },
42
43    /// Member access: `object.property` or `object[property]`.
44    Member {
45        object: Box<Expr>,
46        property: Box<Expr>,
47        /// True for `obj[expr]`, false for `obj.ident`.
48        computed: bool,
49        /// Source location (populated by readers; ignored by writers).
50        #[serde(skip_serializing_if = "Option::is_none")]
51        span: Option<Span>,
52    },
53
54    /// Array literal: `[a, b, c]`.
55    Array(Vec<Expr>),
56
57    /// Object literal: `{ key: value, ... }`.
58    Object(Vec<(String, Expr)>),
59
60    /// Anonymous function: `function(params) { body }`.
61    Function(Box<crate::Function>),
62
63    /// Ternary/conditional: `cond ? then : else`.
64    Conditional {
65        test: Box<Expr>,
66        consequent: Box<Expr>,
67        alternate: Box<Expr>,
68        /// Source location (populated by readers; ignored by writers).
69        #[serde(skip_serializing_if = "Option::is_none")]
70        span: Option<Span>,
71    },
72
73    /// Assignment: `target = value`.
74    Assign {
75        target: Box<Expr>,
76        value: Box<Expr>,
77        /// Source location (populated by readers; ignored by writers).
78        #[serde(skip_serializing_if = "Option::is_none")]
79        span: Option<Span>,
80    },
81
82    /// Template literal: `` `text${expr}more` ``.
83    ///
84    /// Carries the original template structure for languages that support native
85    /// string interpolation (TypeScript/JavaScript). Writers for other languages
86    /// (Lua, Python) fall back to string concatenation.
87    TemplateLiteral(Vec<TemplatePart>),
88}
89
90/// A part of a template literal.
91#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
92pub enum TemplatePart {
93    /// A literal text fragment.
94    Text(String),
95    /// An interpolated expression: `${expr}`.
96    Expr(Box<Expr>),
97}
98
99/// Literal values.
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub enum Literal {
102    Null,
103    Bool(bool),
104    Number(f64),
105    String(String),
106}
107
108/// Binary operators.
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110pub enum BinaryOp {
111    // Arithmetic
112    Add,
113    Sub,
114    Mul,
115    Div,
116    Mod,
117
118    // Comparison
119    Eq,
120    Ne,
121    Lt,
122    Le,
123    Gt,
124    Ge,
125
126    // Logical
127    And,
128    Or,
129
130    // String
131    Concat,
132}
133
134/// Unary operators.
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
136pub enum UnaryOp {
137    Neg,
138    Not,
139}
140
141// Builder methods for expressions
142impl Expr {
143    pub fn null() -> Self {
144        Expr::Literal(Literal::Null)
145    }
146
147    pub fn bool(v: bool) -> Self {
148        Expr::Literal(Literal::Bool(v))
149    }
150
151    pub fn number(v: impl Into<f64>) -> Self {
152        Expr::Literal(Literal::Number(v.into()))
153    }
154
155    pub fn string(v: impl Into<String>) -> Self {
156        Expr::Literal(Literal::String(v.into()))
157    }
158
159    pub fn ident(name: impl Into<String>) -> Self {
160        Expr::Ident(name.into())
161    }
162
163    pub fn binary(left: Expr, op: BinaryOp, right: Expr) -> Self {
164        Expr::Binary {
165            left: Box::new(left),
166            op,
167            right: Box::new(right),
168            span: None,
169        }
170    }
171
172    pub fn unary(op: UnaryOp, expr: Expr) -> Self {
173        Expr::Unary {
174            op,
175            expr: Box::new(expr),
176            span: None,
177        }
178    }
179
180    pub fn call(callee: Expr, args: Vec<Expr>) -> Self {
181        Expr::Call {
182            callee: Box::new(callee),
183            args,
184            span: None,
185        }
186    }
187
188    pub fn member(object: Expr, property: impl Into<String>) -> Self {
189        Expr::Member {
190            object: Box::new(object),
191            property: Box::new(Expr::string(property)),
192            computed: false,
193            span: None,
194        }
195    }
196
197    pub fn index(object: Expr, index: Expr) -> Self {
198        Expr::Member {
199            object: Box::new(object),
200            property: Box::new(index),
201            computed: true,
202            span: None,
203        }
204    }
205
206    pub fn array(items: Vec<Expr>) -> Self {
207        Expr::Array(items)
208    }
209
210    pub fn object(pairs: Vec<(String, Expr)>) -> Self {
211        Expr::Object(pairs)
212    }
213
214    pub fn conditional(test: Expr, consequent: Expr, alternate: Expr) -> Self {
215        Expr::Conditional {
216            test: Box::new(test),
217            consequent: Box::new(consequent),
218            alternate: Box::new(alternate),
219            span: None,
220        }
221    }
222
223    pub fn assign(target: Expr, value: Expr) -> Self {
224        Expr::Assign {
225            target: Box::new(target),
226            value: Box::new(value),
227            span: None,
228        }
229    }
230
231    pub fn template_literal(parts: Vec<TemplatePart>) -> Self {
232        Expr::TemplateLiteral(parts)
233    }
234
235    /// Attach a source location span to this expression.
236    pub fn with_span(self, span: Span) -> Self {
237        match self {
238            Expr::Binary {
239                left, op, right, ..
240            } => Expr::Binary {
241                left,
242                op,
243                right,
244                span: Some(span),
245            },
246            Expr::Unary { op, expr, .. } => Expr::Unary {
247                op,
248                expr,
249                span: Some(span),
250            },
251            Expr::Call { callee, args, .. } => Expr::Call {
252                callee,
253                args,
254                span: Some(span),
255            },
256            Expr::Member {
257                object,
258                property,
259                computed,
260                ..
261            } => Expr::Member {
262                object,
263                property,
264                computed,
265                span: Some(span),
266            },
267            Expr::Conditional {
268                test,
269                consequent,
270                alternate,
271                ..
272            } => Expr::Conditional {
273                test,
274                consequent,
275                alternate,
276                span: Some(span),
277            },
278            Expr::Assign { target, value, .. } => Expr::Assign {
279                target,
280                value,
281                span: Some(span),
282            },
283            other => other,
284        }
285    }
286}