Skip to main content

normalize_surface_syntax/ir/
stmt.rs

1//! Statement types for the IR.
2
3use super::{Expr, Pat, Span};
4use serde::{Deserialize, Serialize};
5
6/// A single name in an import or export specifier list.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct ImportName {
9    /// The exported name being imported (e.g. `foo` in `import { foo } from '...'`).
10    pub name: String,
11    /// Local alias, if any (e.g. `bar` in `import { foo as bar } from '...'`).
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub alias: Option<String>,
14    /// True for namespace imports: `import * as ns from '...'`.
15    pub is_namespace: bool,
16}
17
18impl ImportName {
19    /// Plain named import: `import { name } from '...'`.
20    pub fn named(name: impl Into<String>) -> Self {
21        Self {
22            name: name.into(),
23            alias: None,
24            is_namespace: false,
25        }
26    }
27
28    /// Aliased named import: `import { name as alias } from '...'`.
29    pub fn aliased(name: impl Into<String>, alias: impl Into<String>) -> Self {
30        Self {
31            name: name.into(),
32            alias: Some(alias.into()),
33            is_namespace: false,
34        }
35    }
36
37    /// Default import: `import Name from '...'`.
38    pub fn default(name: impl Into<String>) -> Self {
39        Self {
40            name: name.into(),
41            alias: None,
42            is_namespace: false,
43        }
44    }
45
46    /// Namespace import: `import * as ns from '...'`.
47    pub fn namespace(alias: impl Into<String>) -> Self {
48        Self {
49            name: "*".into(),
50            alias: Some(alias.into()),
51            is_namespace: true,
52        }
53    }
54}
55
56/// A single name in an export specifier list.
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct ExportName {
59    /// The local name being exported (e.g. `foo` in `export { foo }`).
60    pub name: String,
61    /// Exported alias, if any (e.g. `bar` in `export { foo as bar }`).
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub alias: Option<String>,
64}
65
66impl ExportName {
67    /// Plain export: `export { name }`.
68    pub fn named(name: impl Into<String>) -> Self {
69        Self {
70            name: name.into(),
71            alias: None,
72        }
73    }
74
75    /// Aliased export: `export { name as alias }`.
76    pub fn aliased(name: impl Into<String>, alias: impl Into<String>) -> Self {
77        Self {
78            name: name.into(),
79            alias: Some(alias.into()),
80        }
81    }
82}
83
84/// A method in a class definition.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct Method {
87    /// Method name.
88    pub name: String,
89    /// Parameters.
90    pub params: Vec<super::Param>,
91    /// Method body.
92    pub body: Vec<Stmt>,
93    /// True for `static` methods.
94    pub is_static: bool,
95    /// Optional return type annotation.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub return_type: Option<String>,
98}
99
100impl Method {
101    pub fn new(name: impl Into<String>, params: Vec<super::Param>, body: Vec<Stmt>) -> Self {
102        Self {
103            name: name.into(),
104            params,
105            body,
106            is_static: false,
107            return_type: None,
108        }
109    }
110}
111
112/// A statement (doesn't produce a value directly).
113#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
114pub enum Stmt {
115    /// Expression statement: `expr;`.
116    Expr(Expr),
117
118    /// Variable declaration: `let name = init` or `const name = init`.
119    Let {
120        name: String,
121        init: Option<Expr>,
122        mutable: bool,
123        /// Optional type annotation (e.g. `string` for `let x: string = ...`).
124        #[serde(skip_serializing_if = "Option::is_none")]
125        type_annotation: Option<String>,
126        /// Source location (populated by readers; ignored by writers).
127        #[serde(skip_serializing_if = "Option::is_none")]
128        span: Option<Span>,
129    },
130
131    /// Block: `{ stmts... }`.
132    Block(Vec<Stmt>),
133
134    /// If statement: `if (test) consequent else alternate`.
135    If {
136        test: Expr,
137        consequent: Box<Stmt>,
138        alternate: Option<Box<Stmt>>,
139        /// Source location (populated by readers; ignored by writers).
140        #[serde(skip_serializing_if = "Option::is_none")]
141        span: Option<Span>,
142    },
143
144    /// While loop: `while (test) body`.
145    While {
146        test: Expr,
147        body: Box<Stmt>,
148        /// Source location (populated by readers; ignored by writers).
149        #[serde(skip_serializing_if = "Option::is_none")]
150        span: Option<Span>,
151    },
152
153    /// For loop: `for (init; test; update) body`.
154    For {
155        init: Option<Box<Stmt>>,
156        test: Option<Expr>,
157        update: Option<Expr>,
158        body: Box<Stmt>,
159        /// Source location (populated by readers; ignored by writers).
160        #[serde(skip_serializing_if = "Option::is_none")]
161        span: Option<Span>,
162    },
163
164    /// For-in/for-of loop: `for (variable in/of iterable) body`.
165    ForIn {
166        variable: String,
167        iterable: Expr,
168        body: Box<Stmt>,
169        /// Source location (populated by readers; ignored by writers).
170        #[serde(skip_serializing_if = "Option::is_none")]
171        span: Option<Span>,
172    },
173
174    /// Return statement: `return expr`.
175    Return(Option<Expr>),
176
177    /// Break statement.
178    Break,
179
180    /// Continue statement.
181    Continue,
182
183    /// Try/catch/finally statement.
184    TryCatch {
185        body: Box<Stmt>,
186        catch_param: Option<String>,
187        catch_body: Option<Box<Stmt>>,
188        finally_body: Option<Box<Stmt>>,
189        /// Source location (populated by readers; ignored by writers).
190        #[serde(skip_serializing_if = "Option::is_none")]
191        span: Option<Span>,
192    },
193
194    /// Function declaration.
195    Function(crate::Function),
196
197    /// Import statement: `import { X, Y } from 'source'` or `import * as ns from 'source'`.
198    ///
199    /// `names` is empty for side-effect-only imports: `import './side-effect'`.
200    Import {
201        /// The module specifier string (e.g. `"./module"`, `"react"`).
202        source: String,
203        /// Named/namespace/default specifiers. Empty means side-effect import.
204        names: Vec<ImportName>,
205        /// Source location (populated by readers; ignored by writers).
206        #[serde(skip_serializing_if = "Option::is_none")]
207        span: Option<Span>,
208    },
209
210    /// Export statement: `export { X, Y }` or re-export: `export { X } from 'source'`.
211    Export {
212        /// Names being exported.
213        names: Vec<ExportName>,
214        /// Source module for re-exports (e.g. `"./other"` in `export { X } from './other'`).
215        #[serde(skip_serializing_if = "Option::is_none")]
216        source: Option<String>,
217        /// Source location (populated by readers; ignored by writers).
218        #[serde(skip_serializing_if = "Option::is_none")]
219        span: Option<Span>,
220    },
221
222    /// Class definition: `class Foo extends Bar { method() { ... } }`.
223    Class {
224        /// Class name.
225        name: String,
226        /// Superclass name (e.g. `"Bar"` in `class Foo extends Bar`).
227        #[serde(skip_serializing_if = "Option::is_none")]
228        extends: Option<String>,
229        /// Methods (including constructor).
230        methods: Vec<Method>,
231        /// Source location (populated by readers; ignored by writers).
232        #[serde(skip_serializing_if = "Option::is_none")]
233        span: Option<Span>,
234    },
235
236    /// Destructuring declaration: `const { a, b: c } = obj` or `const [x, y] = arr`.
237    ///
238    /// The pattern captures the full structural binding; the `mutable` flag distinguishes
239    /// `let` from `const`. Writers emit the appropriate destructuring syntax for the target
240    /// language (TypeScript/JavaScript: `const { a } = obj`; Python: `a, b = obj`).
241    Destructure {
242        /// The binding pattern.
243        pat: Pat,
244        /// The right-hand side expression.
245        value: Expr,
246        /// `true` for `let` (mutable), `false` for `const`.
247        mutable: bool,
248        /// Source location (populated by readers; ignored by writers).
249        #[serde(skip_serializing_if = "Option::is_none")]
250        span: Option<Span>,
251    },
252
253    /// Comment (line or block). Used to preserve documentation comments during translation.
254    ///
255    /// `text` is the raw comment text including delimiters (e.g. `// foo`, `/* bar */`,
256    /// `/** JSDoc */`, `-- Lua`, `--[[ block ]]`). Writers emit the text verbatim so the
257    /// correct delimiter style for the target language must be supplied by the writer.
258    ///
259    /// For cross-language translation, use `Stmt::comment_line(text)` / `Stmt::comment_block(text)`
260    /// which store only the content; writers format it according to the target language.
261    Comment {
262        /// Comment content (without delimiters).
263        text: String,
264        /// Whether this was originally a block comment (`/* */`, `--[[ ]]`).
265        block: bool,
266        /// Source location.
267        #[serde(skip_serializing_if = "Option::is_none")]
268        span: Option<Span>,
269    },
270}
271
272// Builder methods for statements
273impl Stmt {
274    pub fn expr(e: Expr) -> Self {
275        Stmt::Expr(e)
276    }
277
278    pub fn let_decl(name: impl Into<String>, init: Option<Expr>) -> Self {
279        Stmt::Let {
280            name: name.into(),
281            init,
282            mutable: true,
283            type_annotation: None,
284            span: None,
285        }
286    }
287
288    pub fn const_decl(name: impl Into<String>, init: Expr) -> Self {
289        Stmt::Let {
290            name: name.into(),
291            init: Some(init),
292            mutable: false,
293            type_annotation: None,
294            span: None,
295        }
296    }
297
298    pub fn block(stmts: Vec<Stmt>) -> Self {
299        Stmt::Block(stmts)
300    }
301
302    pub fn if_stmt(test: Expr, consequent: Stmt, alternate: Option<Stmt>) -> Self {
303        Stmt::If {
304            test,
305            consequent: Box::new(consequent),
306            alternate: alternate.map(Box::new),
307            span: None,
308        }
309    }
310
311    pub fn while_loop(test: Expr, body: Stmt) -> Self {
312        Stmt::While {
313            test,
314            body: Box::new(body),
315            span: None,
316        }
317    }
318
319    pub fn for_loop(
320        init: Option<Stmt>,
321        test: Option<Expr>,
322        update: Option<Expr>,
323        body: Stmt,
324    ) -> Self {
325        Stmt::For {
326            init: init.map(Box::new),
327            test,
328            update,
329            body: Box::new(body),
330            span: None,
331        }
332    }
333
334    pub fn for_in(variable: impl Into<String>, iterable: Expr, body: Stmt) -> Self {
335        Stmt::ForIn {
336            variable: variable.into(),
337            iterable,
338            body: Box::new(body),
339            span: None,
340        }
341    }
342
343    pub fn return_stmt(expr: Option<Expr>) -> Self {
344        Stmt::Return(expr)
345    }
346
347    pub fn break_stmt() -> Self {
348        Stmt::Break
349    }
350
351    pub fn continue_stmt() -> Self {
352        Stmt::Continue
353    }
354
355    pub fn try_catch(
356        body: Stmt,
357        catch_param: Option<String>,
358        catch_body: Option<Stmt>,
359        finally_body: Option<Stmt>,
360    ) -> Self {
361        Stmt::TryCatch {
362            body: Box::new(body),
363            catch_param,
364            catch_body: catch_body.map(Box::new),
365            finally_body: finally_body.map(Box::new),
366            span: None,
367        }
368    }
369
370    pub fn function(f: crate::Function) -> Self {
371        Stmt::Function(f)
372    }
373
374    /// Create an import statement.
375    pub fn import(source: impl Into<String>, names: Vec<ImportName>) -> Self {
376        Stmt::Import {
377            source: source.into(),
378            names,
379            span: None,
380        }
381    }
382
383    /// Create an export statement.
384    pub fn export(names: Vec<ExportName>, source: Option<String>) -> Self {
385        Stmt::Export {
386            names,
387            source,
388            span: None,
389        }
390    }
391
392    /// Create a class definition.
393    pub fn class(name: impl Into<String>, extends: Option<String>, methods: Vec<Method>) -> Self {
394        Stmt::Class {
395            name: name.into(),
396            extends,
397            methods,
398            span: None,
399        }
400    }
401
402    /// Create a destructuring declaration.
403    pub fn destructure(pat: Pat, value: Expr, mutable: bool) -> Self {
404        Stmt::Destructure {
405            pat,
406            value,
407            mutable,
408            span: None,
409        }
410    }
411
412    /// Create a line comment from raw content (without `//` or `--` delimiter).
413    pub fn comment_line(text: impl Into<String>) -> Self {
414        Stmt::Comment {
415            text: text.into(),
416            block: false,
417            span: None,
418        }
419    }
420
421    /// Create a block comment from raw content (without `/* */` or `--[[ ]]` delimiters).
422    pub fn comment_block(text: impl Into<String>) -> Self {
423        Stmt::Comment {
424            text: text.into(),
425            block: true,
426            span: None,
427        }
428    }
429
430    /// Attach a source location span to this statement.
431    pub fn with_span(self, span: Span) -> Self {
432        match self {
433            Stmt::Let {
434                name,
435                init,
436                mutable,
437                type_annotation,
438                ..
439            } => Stmt::Let {
440                name,
441                init,
442                mutable,
443                type_annotation,
444                span: Some(span),
445            },
446            Stmt::If {
447                test,
448                consequent,
449                alternate,
450                ..
451            } => Stmt::If {
452                test,
453                consequent,
454                alternate,
455                span: Some(span),
456            },
457            Stmt::While { test, body, .. } => Stmt::While {
458                test,
459                body,
460                span: Some(span),
461            },
462            Stmt::For {
463                init,
464                test,
465                update,
466                body,
467                ..
468            } => Stmt::For {
469                init,
470                test,
471                update,
472                body,
473                span: Some(span),
474            },
475            Stmt::ForIn {
476                variable,
477                iterable,
478                body,
479                ..
480            } => Stmt::ForIn {
481                variable,
482                iterable,
483                body,
484                span: Some(span),
485            },
486            Stmt::TryCatch {
487                body,
488                catch_param,
489                catch_body,
490                finally_body,
491                ..
492            } => Stmt::TryCatch {
493                body,
494                catch_param,
495                catch_body,
496                finally_body,
497                span: Some(span),
498            },
499            Stmt::Destructure {
500                pat,
501                value,
502                mutable,
503                ..
504            } => Stmt::Destructure {
505                pat,
506                value,
507                mutable,
508                span: Some(span),
509            },
510            Stmt::Comment { text, block, .. } => Stmt::Comment {
511                text,
512                block,
513                span: Some(span),
514            },
515            Stmt::Import { source, names, .. } => Stmt::Import {
516                source,
517                names,
518                span: Some(span),
519            },
520            Stmt::Export { names, source, .. } => Stmt::Export {
521                names,
522                source,
523                span: Some(span),
524            },
525            Stmt::Class {
526                name,
527                extends,
528                methods,
529                ..
530            } => Stmt::Class {
531                name,
532                extends,
533                methods,
534                span: Some(span),
535            },
536            other => other,
537        }
538    }
539}