Skip to main content

runar_compiler_rust/frontend/
parser.rs

1//! Pass 1: Parse
2//!
3//! Uses SWC to parse a TypeScript source file and extract the SmartContract
4//! subclass into a Rúnar AST.
5
6use swc_common::sync::Lrc;
7use swc_common::{FileName, SourceMap};
8use swc_ecma_ast as swc;
9use swc_ecma_ast::{
10    Accessibility, AssignExpr, AssignOp, AssignTarget, CallExpr, Callee, Class, ClassDecl,
11    ClassMember, Decl, EsVersion, Expr, ForStmt, IfStmt, Lit, MemberExpr as SwcMemberExpr,
12    MemberProp, ModuleDecl, ModuleItem, Param, ParamOrTsParamProp, Pat, PropName, ReturnStmt,
13    SimpleAssignTarget, Stmt, SuperProp, TsEntityName, TsKeywordTypeKind, TsLit,
14    TsParamPropParam, TsType, UnaryExpr as SwcUnaryExpr, UpdateExpr, UpdateOp, VarDecl,
15    VarDeclKind, VarDeclOrExpr,
16};
17use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsSyntax};
18
19use super::ast::{
20    BinaryOp, ContractNode, Expression, MethodNode, ParamNode, PrimitiveTypeName,
21    PropertyNode, SourceLocation, Statement, TypeNode, UnaryOp, Visibility,
22};
23
24// ---------------------------------------------------------------------------
25// Public API
26// ---------------------------------------------------------------------------
27
28/// Result of parsing a source file.
29pub struct ParseResult {
30    pub contract: Option<ContractNode>,
31    pub errors: Vec<String>,
32}
33
34/// Parse a TypeScript source string and extract the Rúnar contract AST.
35pub fn parse(source: &str, file_name: Option<&str>) -> ParseResult {
36    let mut errors: Vec<String> = Vec::new();
37    let file = file_name.unwrap_or("contract.ts");
38
39    // Set up SWC parser
40    let cm: Lrc<SourceMap> = Lrc::new(SourceMap::default());
41    let fm = cm.new_source_file(Lrc::new(FileName::Custom(file.to_string())), source.to_string());
42    let lexer = Lexer::new(
43        Syntax::Typescript(TsSyntax {
44            tsx: false,
45            decorators: false,
46            ..Default::default()
47        }),
48        EsVersion::Es2022,
49        StringInput::from(&*fm),
50        None,
51    );
52    let mut parser = Parser::new_from(lexer);
53
54    let module = match parser.parse_module() {
55        Ok(m) => m,
56        Err(e) => {
57            errors.push(format!("Parse error: {:?}", e));
58            return ParseResult {
59                contract: None,
60                errors,
61            };
62        }
63    };
64
65    // Collect any parser errors
66    for e in parser.take_errors() {
67        errors.push(format!("Parse error: {:?}", e));
68    }
69
70    // Find the class that extends SmartContract or StatefulSmartContract
71    let mut contract_class: Option<&ClassDecl> = None;
72    let mut detected_parent_class: &str = "SmartContract";
73
74    for item in &module.body {
75        if let ModuleItem::Stmt(Stmt::Decl(Decl::Class(class_decl))) = item {
76            if let Some(super_class) = &class_decl.class.super_class {
77                if let Some(base_name) = get_base_class_name(super_class) {
78                    if contract_class.is_some() {
79                        errors.push(
80                            "Only one SmartContract subclass is allowed per file".to_string(),
81                        );
82                    }
83                    contract_class = Some(class_decl);
84                    detected_parent_class = base_name;
85                }
86            }
87        }
88    }
89
90    // Also check export declarations
91    for item in &module.body {
92        if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
93            if let Decl::Class(class_decl) = &export_decl.decl {
94                if let Some(super_class) = &class_decl.class.super_class {
95                    if let Some(base_name) = get_base_class_name(super_class) {
96                        if contract_class.is_some() {
97                            errors.push(
98                                "Only one SmartContract subclass is allowed per file".to_string(),
99                            );
100                        }
101                        contract_class = Some(class_decl);
102                        detected_parent_class = base_name;
103                    }
104                }
105            }
106        }
107    }
108
109    let class_decl = match contract_class {
110        Some(c) => c,
111        None => {
112            errors.push("No class extending SmartContract or StatefulSmartContract found".to_string());
113            return ParseResult {
114                contract: None,
115                errors,
116            };
117        }
118    };
119
120    let contract_name = class_decl.ident.sym.to_string();
121    let class = &class_decl.class;
122
123    // Extract properties
124    let properties = parse_properties(class, file, &mut errors);
125
126    // Extract constructor
127    let constructor_node = parse_constructor(class, file, &mut errors);
128
129    // Extract methods
130    let methods = parse_methods(class, file, &mut errors);
131
132    let contract = ContractNode {
133        name: contract_name,
134        parent_class: detected_parent_class.to_string(),
135        properties,
136        constructor: constructor_node,
137        methods,
138        source_file: file.to_string(),
139    };
140
141    ParseResult {
142        contract: Some(contract),
143        errors,
144    }
145}
146
147// ---------------------------------------------------------------------------
148// Helpers
149// ---------------------------------------------------------------------------
150
151fn get_base_class_name(expr: &Expr) -> Option<&str> {
152    match expr {
153        Expr::Ident(ident) => {
154            let name = ident.sym.as_ref();
155            if name == "SmartContract" || name == "StatefulSmartContract" {
156                Some(name)
157            } else {
158                None
159            }
160        }
161        _ => None,
162    }
163}
164
165fn loc(file: &str, line: usize, column: usize) -> SourceLocation {
166    SourceLocation {
167        file: file.to_string(),
168        line,
169        column,
170    }
171}
172
173fn default_loc(file: &str) -> SourceLocation {
174    loc(file, 1, 0)
175}
176
177// ---------------------------------------------------------------------------
178// Properties
179// ---------------------------------------------------------------------------
180
181fn parse_properties(class: &Class, file: &str, errors: &mut Vec<String>) -> Vec<PropertyNode> {
182    let mut result = Vec::new();
183
184    for member in &class.body {
185        if let ClassMember::ClassProp(prop) = member {
186            let name = match &prop.key {
187                PropName::Ident(ident) => ident.sym.to_string(),
188                _ => {
189                    errors.push("Property must have an identifier name".to_string());
190                    continue;
191                }
192            };
193
194            let readonly = prop.readonly;
195
196            let prop_type = if let Some(ref ann) = prop.type_ann {
197                parse_type_node(&ann.type_ann, file, errors)
198            } else {
199                errors.push(format!(
200                    "Property '{}' must have an explicit type annotation",
201                    name
202                ));
203                TypeNode::Custom("unknown".to_string())
204            };
205
206            // Parse initializer if present (SWC ClassProp.value)
207            let initializer = prop.value.as_ref().map(|v| parse_expression(v, file, errors));
208
209            result.push(PropertyNode {
210                name,
211                prop_type,
212                readonly,
213                initializer,
214                source_location: default_loc(file),
215            });
216        }
217    }
218
219    result
220}
221
222// ---------------------------------------------------------------------------
223// Constructor
224// ---------------------------------------------------------------------------
225
226fn parse_constructor(class: &Class, file: &str, errors: &mut Vec<String>) -> MethodNode {
227    for member in &class.body {
228        if let ClassMember::Constructor(ctor) = member {
229            let params = parse_constructor_params(&ctor.params, file, errors);
230            let body = if let Some(ref block) = ctor.body {
231                parse_block_stmts(&block.stmts, file, errors)
232            } else {
233                Vec::new()
234            };
235
236            return MethodNode {
237                name: "constructor".to_string(),
238                params,
239                body,
240                visibility: Visibility::Public,
241                source_location: default_loc(file),
242            };
243        }
244    }
245
246    errors.push("Contract must have a constructor".to_string());
247    MethodNode {
248        name: "constructor".to_string(),
249        params: Vec::new(),
250        body: Vec::new(),
251        visibility: Visibility::Public,
252        source_location: default_loc(file),
253    }
254}
255
256fn parse_constructor_params(
257    params: &[ParamOrTsParamProp],
258    file: &str,
259    errors: &mut Vec<String>,
260) -> Vec<ParamNode> {
261    let mut result = Vec::new();
262
263    for param in params {
264        match param {
265            ParamOrTsParamProp::Param(p) => {
266                if let Some(ast_param) = parse_param_pat(&p.pat, file, errors) {
267                    result.push(ast_param);
268                }
269            }
270            ParamOrTsParamProp::TsParamProp(ts_param) => {
271                match &ts_param.param {
272                    TsParamPropParam::Ident(ident) => {
273                        let name = ident.id.sym.to_string();
274                        let param_type = if let Some(ref ann) = ident.type_ann {
275                            parse_type_node(&ann.type_ann, file, errors)
276                        } else {
277                            errors.push(format!(
278                                "Parameter '{}' must have an explicit type annotation",
279                                name
280                            ));
281                            TypeNode::Custom("unknown".to_string())
282                        };
283                        result.push(ParamNode { name, param_type });
284                    }
285                    TsParamPropParam::Assign(_) => {
286                        errors.push("Default parameter values are not supported".to_string());
287                    }
288                }
289            }
290        }
291    }
292
293    result
294}
295
296// ---------------------------------------------------------------------------
297// Methods
298// ---------------------------------------------------------------------------
299
300fn parse_methods(class: &Class, file: &str, errors: &mut Vec<String>) -> Vec<MethodNode> {
301    let mut result = Vec::new();
302
303    for member in &class.body {
304        if let ClassMember::Method(method) = member {
305            let name = match &method.key {
306                PropName::Ident(ident) => ident.sym.to_string(),
307                _ => {
308                    errors.push("Method must have an identifier name".to_string());
309                    continue;
310                }
311            };
312
313            let params = parse_method_params(&method.function.params, file, errors);
314
315            let visibility = if method.accessibility == Some(Accessibility::Public) {
316                Visibility::Public
317            } else {
318                Visibility::Private
319            };
320
321            let body = if let Some(ref block) = method.function.body {
322                parse_block_stmts(&block.stmts, file, errors)
323            } else {
324                Vec::new()
325            };
326
327            result.push(MethodNode {
328                name,
329                params,
330                body,
331                visibility,
332                source_location: default_loc(file),
333            });
334        }
335    }
336
337    result
338}
339
340fn parse_method_params(
341    params: &[Param],
342    file: &str,
343    errors: &mut Vec<String>,
344) -> Vec<ParamNode> {
345    let mut result = Vec::new();
346    for param in params {
347        if let Some(ast_param) = parse_param_pat(&param.pat, file, errors) {
348            result.push(ast_param);
349        }
350    }
351    result
352}
353
354fn parse_param_pat(
355    pat: &Pat,
356    file: &str,
357    errors: &mut Vec<String>,
358) -> Option<ParamNode> {
359    match pat {
360        Pat::Ident(ident) => {
361            let name = ident.id.sym.to_string();
362            let param_type = if let Some(ref ann) = ident.type_ann {
363                parse_type_node(&ann.type_ann, file, errors)
364            } else {
365                errors.push(format!(
366                    "Parameter '{}' must have an explicit type annotation",
367                    name
368                ));
369                TypeNode::Custom("unknown".to_string())
370            };
371            Some(ParamNode { name, param_type })
372        }
373        _ => {
374            errors.push("Unsupported parameter pattern".to_string());
375            None
376        }
377    }
378}
379
380// ---------------------------------------------------------------------------
381// Type nodes
382// ---------------------------------------------------------------------------
383
384fn parse_type_node(ts_type: &TsType, file: &str, errors: &mut Vec<String>) -> TypeNode {
385    match ts_type {
386        // Keyword types
387        TsType::TsKeywordType(kw) => match kw.kind {
388            TsKeywordTypeKind::TsBigIntKeyword => TypeNode::Primitive(PrimitiveTypeName::Bigint),
389            TsKeywordTypeKind::TsBooleanKeyword => {
390                TypeNode::Primitive(PrimitiveTypeName::Boolean)
391            }
392            TsKeywordTypeKind::TsVoidKeyword => TypeNode::Primitive(PrimitiveTypeName::Void),
393            TsKeywordTypeKind::TsNumberKeyword => {
394                errors.push("'number' type is not allowed in Rúnar contracts; use 'bigint' instead".to_string());
395                TypeNode::Primitive(PrimitiveTypeName::Bigint)
396            }
397            TsKeywordTypeKind::TsStringKeyword => {
398                errors.push("'string' type is not allowed in Rúnar contracts; use 'ByteString' instead".to_string());
399                TypeNode::Primitive(PrimitiveTypeName::ByteString)
400            }
401            _ => {
402                errors.push(format!("Unsupported keyword type: {:?}", kw.kind));
403                TypeNode::Custom("unknown".to_string())
404            }
405        },
406
407        // Type references: Sha256, PubKey, FixedArray<T, N>, etc.
408        TsType::TsTypeRef(type_ref) => {
409            let type_name = ts_entity_name_to_string(&type_ref.type_name);
410
411            // Check for FixedArray<T, N>
412            if type_name == "FixedArray" {
413                if let Some(ref type_params) = type_ref.type_params {
414                    let params = &type_params.params;
415                    if params.len() != 2 {
416                        errors.push(
417                            "FixedArray requires exactly 2 type arguments: FixedArray<T, N>"
418                                .to_string(),
419                        );
420                        return TypeNode::Custom(type_name);
421                    }
422
423                    let element = parse_type_node(&params[0], file, errors);
424
425                    // The second parameter should be a literal type for the length
426                    let length = extract_type_literal_number(&params[1]);
427                    if let Some(len) = length {
428                        return TypeNode::FixedArray {
429                            element: Box::new(element),
430                            length: len,
431                        };
432                    } else {
433                        errors.push(
434                            "FixedArray size must be a non-negative integer literal".to_string(),
435                        );
436                        return TypeNode::Custom(type_name);
437                    }
438                } else {
439                    errors.push("FixedArray requires type arguments".to_string());
440                    return TypeNode::Custom(type_name);
441                }
442            }
443
444            // Check for primitive types referenced by name
445            if let Some(prim) = PrimitiveTypeName::from_str(&type_name) {
446                return TypeNode::Primitive(prim);
447            }
448
449            // Unknown type reference
450            TypeNode::Custom(type_name)
451        }
452
453        _ => {
454            errors.push("Unsupported type annotation".to_string());
455            TypeNode::Custom("unknown".to_string())
456        }
457    }
458}
459
460fn ts_entity_name_to_string(entity: &TsEntityName) -> String {
461    match entity {
462        TsEntityName::Ident(ident) => ident.sym.to_string(),
463        TsEntityName::TsQualifiedName(qual) => {
464            format!(
465                "{}.{}",
466                ts_entity_name_to_string(&qual.left),
467                qual.right.sym
468            )
469        }
470    }
471}
472
473/// Try to extract a literal number from a type node (e.g. `10` in `FixedArray<bigint, 10>`).
474fn extract_type_literal_number(ts_type: &TsType) -> Option<usize> {
475    match ts_type {
476        TsType::TsLitType(lit) => match &lit.lit {
477            TsLit::Number(n) => Some(n.value as usize),
478            _ => None,
479        },
480        _ => None,
481    }
482}
483
484// ---------------------------------------------------------------------------
485// Statements
486// ---------------------------------------------------------------------------
487
488fn parse_block_stmts(stmts: &[Stmt], file: &str, errors: &mut Vec<String>) -> Vec<Statement> {
489    let mut result = Vec::new();
490    for stmt in stmts {
491        if let Some(parsed) = parse_statement(stmt, file, errors) {
492            result.push(parsed);
493        }
494    }
495    result
496}
497
498fn parse_statement(stmt: &Stmt, file: &str, errors: &mut Vec<String>) -> Option<Statement> {
499    match stmt {
500        Stmt::Decl(Decl::Var(var_decl)) => parse_variable_statement(var_decl, file, errors),
501
502        Stmt::Expr(expr_stmt) => parse_expression_statement(&expr_stmt.expr, file, errors),
503
504        Stmt::If(if_stmt) => Some(parse_if_statement(if_stmt, file, errors)),
505
506        Stmt::For(for_stmt) => Some(parse_for_statement(for_stmt, file, errors)),
507
508        Stmt::Return(ret_stmt) => Some(parse_return_statement(ret_stmt, file, errors)),
509
510        Stmt::Block(block) => {
511            // Flatten block statements
512            let stmts = parse_block_stmts(&block.stmts, file, errors);
513            // Return as individual statements by wrapping in a block-like structure
514            // For simplicity, we report an error -- blocks should be part of if/for
515            if stmts.is_empty() {
516                None
517            } else {
518                errors.push("Standalone block statements are not supported; use if/for".to_string());
519                None
520            }
521        }
522
523        _ => {
524            errors.push(format!("Unsupported statement kind: {:?}", stmt));
525            None
526        }
527    }
528}
529
530fn parse_variable_statement(
531    var_decl: &VarDecl,
532    file: &str,
533    errors: &mut Vec<String>,
534) -> Option<Statement> {
535    if var_decl.decls.is_empty() {
536        return None;
537    }
538
539    let decl = &var_decl.decls[0];
540    let name = match &decl.name {
541        Pat::Ident(ident) => ident.id.sym.to_string(),
542        _ => {
543            errors.push("Destructuring patterns are not supported in variable declarations".to_string());
544            return None;
545        }
546    };
547
548    let is_const = var_decl.kind == VarDeclKind::Const;
549
550    let init = if let Some(ref init_expr) = decl.init {
551        parse_expression(init_expr, file, errors)
552    } else {
553        errors.push(format!("Variable '{}' must have an initializer", name));
554        Expression::BigIntLiteral { value: 0 }
555    };
556
557    let var_type = if let Pat::Ident(ident) = &decl.name {
558        if let Some(ref ann) = ident.type_ann {
559            Some(parse_type_node(&ann.type_ann, file, errors))
560        } else {
561            None
562        }
563    } else {
564        None
565    };
566
567    Some(Statement::VariableDecl {
568        name,
569        var_type,
570        mutable: !is_const,
571        init,
572        source_location: default_loc(file),
573    })
574}
575
576fn parse_expression_statement(
577    expr: &Expr,
578    file: &str,
579    errors: &mut Vec<String>,
580) -> Option<Statement> {
581    // Check if this is an assignment expression (a = b, this.x = b)
582    if let Expr::Assign(assign) = expr {
583        return Some(parse_assignment_expr(assign, file, errors));
584    }
585
586    let expression = parse_expression(expr, file, errors);
587    Some(Statement::ExpressionStatement {
588        expression,
589        source_location: default_loc(file),
590    })
591}
592
593fn parse_assignment_expr(
594    assign: &AssignExpr,
595    file: &str,
596    errors: &mut Vec<String>,
597) -> Statement {
598    let target = parse_assign_target(&assign.left, file, errors);
599
600    match assign.op {
601        AssignOp::Assign => {
602            let value = parse_expression(&assign.right, file, errors);
603            Statement::Assignment {
604                target,
605                value,
606                source_location: default_loc(file),
607            }
608        }
609        // Compound assignments: +=, -=, *=, /=, %=
610        op => {
611            let bin_op = match op {
612                AssignOp::AddAssign => Some(BinaryOp::Add),
613                AssignOp::SubAssign => Some(BinaryOp::Sub),
614                AssignOp::MulAssign => Some(BinaryOp::Mul),
615                AssignOp::DivAssign => Some(BinaryOp::Div),
616                AssignOp::ModAssign => Some(BinaryOp::Mod),
617                _ => {
618                    errors.push(format!("Unsupported compound assignment operator: {:?}", op));
619                    None
620                }
621            };
622
623            if let Some(bin_op) = bin_op {
624                let right = parse_expression(&assign.right, file, errors);
625                let target_for_rhs = parse_assign_target(&assign.left, file, errors);
626                let value = Expression::BinaryExpr {
627                    op: bin_op,
628                    left: Box::new(target_for_rhs),
629                    right: Box::new(right),
630                };
631                Statement::Assignment {
632                    target,
633                    value,
634                    source_location: default_loc(file),
635                }
636            } else {
637                let value = parse_expression(&assign.right, file, errors);
638                Statement::Assignment {
639                    target,
640                    value,
641                    source_location: default_loc(file),
642                }
643            }
644        }
645    }
646}
647
648fn parse_assign_target(
649    target: &AssignTarget,
650    file: &str,
651    errors: &mut Vec<String>,
652) -> Expression {
653    match target {
654        AssignTarget::Simple(simple) => match simple {
655            SimpleAssignTarget::Ident(ident) => Expression::Identifier {
656                name: ident.id.sym.to_string(),
657            },
658            SimpleAssignTarget::Member(member) => {
659                parse_member_expression(member, file, errors)
660            }
661            _ => {
662                errors.push("Unsupported assignment target".to_string());
663                Expression::Identifier {
664                    name: "_error".to_string(),
665                }
666            }
667        },
668        AssignTarget::Pat(_) => {
669            errors.push("Destructuring assignment is not supported".to_string());
670            Expression::Identifier {
671                name: "_error".to_string(),
672            }
673        }
674    }
675}
676
677fn parse_if_statement(if_stmt: &IfStmt, file: &str, errors: &mut Vec<String>) -> Statement {
678    let condition = parse_expression(&if_stmt.test, file, errors);
679    let then_branch = parse_stmt_or_block(&if_stmt.cons, file, errors);
680
681    let else_branch = if_stmt
682        .alt
683        .as_ref()
684        .map(|alt| parse_stmt_or_block(alt, file, errors));
685
686    Statement::IfStatement {
687        condition,
688        then_branch,
689        else_branch,
690        source_location: default_loc(file),
691    }
692}
693
694fn parse_stmt_or_block(
695    stmt: &Stmt,
696    file: &str,
697    errors: &mut Vec<String>,
698) -> Vec<Statement> {
699    match stmt {
700        Stmt::Block(block) => parse_block_stmts(&block.stmts, file, errors),
701        _ => {
702            if let Some(s) = parse_statement(stmt, file, errors) {
703                vec![s]
704            } else {
705                Vec::new()
706            }
707        }
708    }
709}
710
711fn parse_for_statement(for_stmt: &ForStmt, file: &str, errors: &mut Vec<String>) -> Statement {
712    // Parse initializer
713    let init = if let Some(ref init_expr) = for_stmt.init {
714        match init_expr {
715            VarDeclOrExpr::VarDecl(var_decl) => {
716                if let Some(stmt) = parse_variable_statement(var_decl, file, errors) {
717                    stmt
718                } else {
719                    make_default_for_init(file)
720                }
721            }
722            VarDeclOrExpr::Expr(_) => {
723                errors.push(
724                    "For loop must have a variable declaration initializer".to_string(),
725                );
726                make_default_for_init(file)
727            }
728        }
729    } else {
730        errors.push("For loop must have an initializer".to_string());
731        make_default_for_init(file)
732    };
733
734    // Parse condition
735    let condition = if let Some(ref cond) = for_stmt.test {
736        parse_expression(cond, file, errors)
737    } else {
738        errors.push("For loop must have a condition".to_string());
739        Expression::BoolLiteral { value: false }
740    };
741
742    // Parse update
743    let update = if let Some(ref upd) = for_stmt.update {
744        parse_for_update(upd, file, errors)
745    } else {
746        errors.push("For loop must have an update expression".to_string());
747        Statement::ExpressionStatement {
748            expression: Expression::BigIntLiteral { value: 0 },
749            source_location: default_loc(file),
750        }
751    };
752
753    // Parse body
754    let body = parse_stmt_or_block(&for_stmt.body, file, errors);
755
756    Statement::ForStatement {
757        init: Box::new(init),
758        condition,
759        update: Box::new(update),
760        body,
761        source_location: default_loc(file),
762    }
763}
764
765fn parse_for_update(
766    expr: &Expr,
767    file: &str,
768    errors: &mut Vec<String>,
769) -> Statement {
770    match expr {
771        Expr::Update(update) => {
772            let operand = parse_expression(&update.arg, file, errors);
773            let is_increment = update.op == UpdateOp::PlusPlus;
774            let expression = if is_increment {
775                Expression::IncrementExpr {
776                    operand: Box::new(operand),
777                    prefix: update.prefix,
778                }
779            } else {
780                Expression::DecrementExpr {
781                    operand: Box::new(operand),
782                    prefix: update.prefix,
783                }
784            };
785            Statement::ExpressionStatement {
786                expression,
787                source_location: default_loc(file),
788            }
789        }
790        _ => {
791            let expression = parse_expression(expr, file, errors);
792            Statement::ExpressionStatement {
793                expression,
794                source_location: default_loc(file),
795            }
796        }
797    }
798}
799
800fn parse_return_statement(
801    ret_stmt: &ReturnStmt,
802    file: &str,
803    errors: &mut Vec<String>,
804) -> Statement {
805    let value = ret_stmt
806        .arg
807        .as_ref()
808        .map(|e| parse_expression(e, file, errors));
809
810    Statement::ReturnStatement {
811        value,
812        source_location: default_loc(file),
813    }
814}
815
816fn make_default_for_init(file: &str) -> Statement {
817    Statement::VariableDecl {
818        name: "_i".to_string(),
819        var_type: None,
820        mutable: true,
821        init: Expression::BigIntLiteral { value: 0 },
822        source_location: default_loc(file),
823    }
824}
825
826// ---------------------------------------------------------------------------
827// Expressions
828// ---------------------------------------------------------------------------
829
830fn parse_expression(expr: &Expr, file: &str, errors: &mut Vec<String>) -> Expression {
831    match expr {
832        Expr::Bin(bin) => parse_binary_expression(bin, file, errors),
833
834        Expr::Unary(unary) => parse_unary_expression(unary, file, errors),
835
836        Expr::Update(update) => parse_update_expression(update, file, errors),
837
838        Expr::Call(call) => parse_call_expression(call, file, errors),
839
840        Expr::Member(member) => parse_member_expression(member, file, errors),
841
842        Expr::SuperProp(super_prop) => {
843            // super.x -- unlikely in Rúnar but handle gracefully
844            match &super_prop.prop {
845                SuperProp::Ident(ident) => Expression::MemberExpr {
846                    object: Box::new(Expression::Identifier {
847                        name: "super".to_string(),
848                    }),
849                    property: ident.sym.to_string(),
850                },
851                SuperProp::Computed(comp) => {
852                    let _ = parse_expression(&comp.expr, file, errors);
853                    errors.push("Computed super property access not supported".to_string());
854                    Expression::Identifier {
855                        name: "super".to_string(),
856                    }
857                }
858            }
859        }
860
861        Expr::Ident(ident) => Expression::Identifier {
862            name: ident.sym.to_string(),
863        },
864
865        Expr::Lit(Lit::BigInt(bigint)) => {
866            // Parse the BigInt value -- SWC gives the numeric part
867            let val = bigint_to_i64(bigint);
868            Expression::BigIntLiteral { value: val }
869        }
870
871        Expr::Lit(Lit::Num(num)) => {
872            // Plain numeric literal -- treat as bigint for Rúnar
873            Expression::BigIntLiteral {
874                value: num.value as i64,
875            }
876        }
877
878        Expr::Lit(Lit::Bool(b)) => Expression::BoolLiteral { value: b.value },
879
880        Expr::Lit(Lit::Str(s)) => {
881            // String literals are hex-encoded ByteString values
882            Expression::ByteStringLiteral {
883                value: s.value.to_string(),
884            }
885        }
886
887        Expr::Tpl(tpl) => {
888            // Template literal with no substitutions
889            if tpl.exprs.is_empty() && tpl.quasis.len() == 1 {
890                Expression::ByteStringLiteral {
891                    value: tpl.quasis[0].raw.to_string(),
892                }
893            } else {
894                errors.push("Template literals with expressions are not supported".to_string());
895                Expression::ByteStringLiteral {
896                    value: String::new(),
897                }
898            }
899        }
900
901        Expr::Cond(cond) => {
902            let condition = parse_expression(&cond.test, file, errors);
903            let consequent = parse_expression(&cond.cons, file, errors);
904            let alternate = parse_expression(&cond.alt, file, errors);
905            Expression::TernaryExpr {
906                condition: Box::new(condition),
907                consequent: Box::new(consequent),
908                alternate: Box::new(alternate),
909            }
910        }
911
912        Expr::Paren(paren) => parse_expression(&paren.expr, file, errors),
913
914        Expr::This(_) => Expression::Identifier {
915            name: "this".to_string(),
916        },
917
918        Expr::TsAs(as_expr) => {
919            // Type assertions: ignore the type, parse the expression
920            parse_expression(&as_expr.expr, file, errors)
921        }
922
923        Expr::TsNonNull(nn) => {
924            // Non-null assertion: just parse the inner expression
925            parse_expression(&nn.expr, file, errors)
926        }
927
928        Expr::Assign(assign) => {
929            // Assignment expression in expression context -- should be handled
930            // at statement level, but in case it appears in an expression context
931            errors.push("Assignment expressions in expression context are not recommended".to_string());
932            let value = parse_expression(&assign.right, file, errors);
933            value
934        }
935
936        _ => {
937            errors.push(format!("Unsupported expression: {:?}", expr));
938            Expression::BigIntLiteral { value: 0 }
939        }
940    }
941}
942
943fn parse_binary_expression(
944    bin: &swc::BinExpr,
945    file: &str,
946    errors: &mut Vec<String>,
947) -> Expression {
948    let left = parse_expression(&bin.left, file, errors);
949    let right = parse_expression(&bin.right, file, errors);
950
951    let op = match bin.op {
952        swc::BinaryOp::Add => BinaryOp::Add,
953        swc::BinaryOp::Sub => BinaryOp::Sub,
954        swc::BinaryOp::Mul => BinaryOp::Mul,
955        swc::BinaryOp::Div => BinaryOp::Div,
956        swc::BinaryOp::Mod => BinaryOp::Mod,
957        swc::BinaryOp::EqEqEq => BinaryOp::StrictEq,
958        swc::BinaryOp::NotEqEq => BinaryOp::StrictNe,
959        swc::BinaryOp::Lt => BinaryOp::Lt,
960        swc::BinaryOp::LtEq => BinaryOp::Le,
961        swc::BinaryOp::Gt => BinaryOp::Gt,
962        swc::BinaryOp::GtEq => BinaryOp::Ge,
963        swc::BinaryOp::LogicalAnd => BinaryOp::And,
964        swc::BinaryOp::LogicalOr => BinaryOp::Or,
965        swc::BinaryOp::BitAnd => BinaryOp::BitAnd,
966        swc::BinaryOp::BitOr => BinaryOp::BitOr,
967        swc::BinaryOp::BitXor => BinaryOp::BitXor,
968        swc::BinaryOp::EqEq => {
969            errors.push("Use === instead of == for equality comparison".to_string());
970            BinaryOp::StrictEq
971        }
972        swc::BinaryOp::NotEq => {
973            errors.push("Use !== instead of != for inequality comparison".to_string());
974            BinaryOp::StrictNe
975        }
976        _ => {
977            errors.push(format!("Unsupported binary operator: {:?}", bin.op));
978            BinaryOp::Add
979        }
980    };
981
982    Expression::BinaryExpr {
983        op,
984        left: Box::new(left),
985        right: Box::new(right),
986    }
987}
988
989fn parse_unary_expression(
990    unary: &SwcUnaryExpr,
991    file: &str,
992    errors: &mut Vec<String>,
993) -> Expression {
994    let operand = parse_expression(&unary.arg, file, errors);
995
996    let op = match unary.op {
997        swc::UnaryOp::Bang => UnaryOp::Not,
998        swc::UnaryOp::Minus => UnaryOp::Neg,
999        swc::UnaryOp::Tilde => UnaryOp::BitNot,
1000        _ => {
1001            errors.push(format!("Unsupported unary operator: {:?}", unary.op));
1002            UnaryOp::Neg
1003        }
1004    };
1005
1006    Expression::UnaryExpr {
1007        op,
1008        operand: Box::new(operand),
1009    }
1010}
1011
1012fn parse_update_expression(
1013    update: &UpdateExpr,
1014    file: &str,
1015    errors: &mut Vec<String>,
1016) -> Expression {
1017    let operand = parse_expression(&update.arg, file, errors);
1018
1019    if update.op == UpdateOp::PlusPlus {
1020        Expression::IncrementExpr {
1021            operand: Box::new(operand),
1022            prefix: update.prefix,
1023        }
1024    } else {
1025        Expression::DecrementExpr {
1026            operand: Box::new(operand),
1027            prefix: update.prefix,
1028        }
1029    }
1030}
1031
1032fn parse_call_expression(
1033    call: &CallExpr,
1034    file: &str,
1035    errors: &mut Vec<String>,
1036) -> Expression {
1037    let callee = match &call.callee {
1038        Callee::Expr(e) => parse_expression(e, file, errors),
1039        Callee::Super(_) => Expression::Identifier {
1040            name: "super".to_string(),
1041        },
1042        Callee::Import(_) => {
1043            errors.push("Dynamic import is not supported".to_string());
1044            Expression::Identifier {
1045                name: "_error".to_string(),
1046            }
1047        }
1048    };
1049
1050    let args: Vec<Expression> = call
1051        .args
1052        .iter()
1053        .map(|arg| parse_expression(&arg.expr, file, errors))
1054        .collect();
1055
1056    Expression::CallExpr {
1057        callee: Box::new(callee),
1058        args,
1059    }
1060}
1061
1062fn parse_member_expression(
1063    member: &SwcMemberExpr,
1064    file: &str,
1065    errors: &mut Vec<String>,
1066) -> Expression {
1067    let prop_name = match &member.prop {
1068        MemberProp::Ident(ident) => ident.sym.to_string(),
1069        MemberProp::Computed(comp) => {
1070            // Computed member access: obj[expr]
1071            let object = parse_expression(&member.obj, file, errors);
1072            let index = parse_expression(&comp.expr, file, errors);
1073            return Expression::IndexAccess {
1074                object: Box::new(object),
1075                index: Box::new(index),
1076            };
1077        }
1078        MemberProp::PrivateName(_priv_name) => {
1079            errors.push("Private field access (#field) is not supported".to_string());
1080            "_private".to_string()
1081        }
1082    };
1083
1084    // this.x -> PropertyAccess
1085    if let Expr::This(_) = &*member.obj {
1086        return Expression::PropertyAccess {
1087            property: prop_name,
1088        };
1089    }
1090
1091    // General member access: obj.method
1092    let object = parse_expression(&member.obj, file, errors);
1093    Expression::MemberExpr {
1094        object: Box::new(object),
1095        property: prop_name,
1096    }
1097}
1098
1099// ---------------------------------------------------------------------------
1100// BigInt helpers
1101// ---------------------------------------------------------------------------
1102
1103/// Convert SWC BigInt to i64. SWC represents BigInt as a boxed `num_bigint::BigInt`.
1104fn bigint_to_i64(bigint_lit: &swc::BigInt) -> i64 {
1105    // SWC BigInt has a `value` field of type `Box<num_bigint::BigInt>`.
1106    // We convert via string representation to i64.
1107    use std::str::FromStr;
1108    let s = bigint_lit.value.to_string();
1109    i64::from_str(&s).unwrap_or(0)
1110}
1111
1112// ---------------------------------------------------------------------------
1113// Multi-format dispatch
1114// ---------------------------------------------------------------------------
1115
1116/// Parse a source string, automatically selecting the parser based on file extension.
1117///
1118/// Supported extensions:
1119/// - `.runar.sol` -> Solidity-like parser
1120/// - `.runar.move` -> Move-style parser
1121/// - `.runar.rs` -> Rust DSL parser
1122/// - `.runar.py` -> Python parser
1123/// - anything else (including `.runar.ts`) -> TypeScript parser (default)
1124pub fn parse_source(source: &str, file_name: Option<&str>) -> ParseResult {
1125    let name = file_name.unwrap_or("contract.ts");
1126    if name.ends_with(".runar.sol") {
1127        return super::parser_sol::parse_solidity(source, file_name);
1128    }
1129    if name.ends_with(".runar.move") {
1130        return super::parser_move::parse_move(source, file_name);
1131    }
1132    if name.ends_with(".runar.rs") {
1133        return super::parser_rustmacro::parse_rust_dsl(source, file_name);
1134    }
1135    if name.ends_with(".runar.py") {
1136        return super::parser_python::parse_python(source, file_name);
1137    }
1138    // Default: TypeScript parser
1139    parse(source, file_name)
1140}