1use 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
24pub struct ParseResult {
30 pub contract: Option<ContractNode>,
31 pub errors: Vec<String>,
32}
33
34pub 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 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 for e in parser.take_errors() {
67 errors.push(format!("Parse error: {:?}", e));
68 }
69
70 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 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 let properties = parse_properties(class, file, &mut errors);
125
126 let constructor_node = parse_constructor(class, file, &mut errors);
128
129 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
147fn 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
177fn 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 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
222fn 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
296fn 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(¶m.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
380fn parse_type_node(ts_type: &TsType, file: &str, errors: &mut Vec<String>) -> TypeNode {
385 match ts_type {
386 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 TsType::TsTypeRef(type_ref) => {
409 let type_name = ts_entity_name_to_string(&type_ref.type_name);
410
411 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(¶ms[0], file, errors);
424
425 let length = extract_type_literal_number(¶ms[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 if let Some(prim) = PrimitiveTypeName::from_str(&type_name) {
446 return TypeNode::Primitive(prim);
447 }
448
449 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
473fn 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
484fn 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 let stmts = parse_block_stmts(&block.stmts, file, errors);
513 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 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 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 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 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 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 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
826fn 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 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 let val = bigint_to_i64(bigint);
868 Expression::BigIntLiteral { value: val }
869 }
870
871 Expr::Lit(Lit::Num(num)) => {
872 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 Expression::ByteStringLiteral {
883 value: s.value.to_string(),
884 }
885 }
886
887 Expr::Tpl(tpl) => {
888 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 parse_expression(&as_expr.expr, file, errors)
921 }
922
923 Expr::TsNonNull(nn) => {
924 parse_expression(&nn.expr, file, errors)
926 }
927
928 Expr::Assign(assign) => {
929 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 BinaryOp::StrictEq
971 }
972 swc::BinaryOp::NotEq => {
973 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 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 if let Expr::This(_) = &*member.obj {
1086 return Expression::PropertyAccess {
1087 property: prop_name,
1088 };
1089 }
1090
1091 let object = parse_expression(&member.obj, file, errors);
1093 Expression::MemberExpr {
1094 object: Box::new(object),
1095 property: prop_name,
1096 }
1097}
1098
1099fn bigint_to_i64(bigint_lit: &swc::BigInt) -> i64 {
1105 use std::str::FromStr;
1108 let s = bigint_lit.value.to_string();
1109 i64::from_str(&s).unwrap_or(0)
1110}
1111
1112pub 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 if name.ends_with(".runar.go") {
1139 return super::parser_gocontract::parse_go_contract(source, file_name);
1140 }
1141 parse(source, file_name)
1143}
1144
1145#[cfg(test)]
1150mod tests {
1151 use super::*;
1152
1153 const P2PKH_SOURCE: &str = r#"
1158 import { SmartContract, assert, Sig, PubKey, Ripemd160, hash160 } from 'runar-lang';
1159
1160 class P2PKH extends SmartContract {
1161 readonly pubKeyHash: Ripemd160;
1162
1163 constructor(pubKeyHash: Ripemd160) {
1164 super(pubKeyHash);
1165 }
1166
1167 public unlock(sig: Sig, pubKey: PubKey) {
1168 assert(hash160(pubKey) === this.pubKeyHash);
1169 assert(checkSig(sig, pubKey));
1170 }
1171 }
1172 "#;
1173
1174 #[test]
1175 fn test_parse_p2pkh_contract_name() {
1176 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1177 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1178 let contract = result.contract.expect("should produce a contract");
1179 assert_eq!(contract.name, "P2PKH");
1180 }
1181
1182 #[test]
1183 fn test_parse_p2pkh_parent_class() {
1184 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1185 let contract = result.contract.unwrap();
1186 assert_eq!(contract.parent_class, "SmartContract");
1187 }
1188
1189 #[test]
1190 fn test_parse_p2pkh_properties() {
1191 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1192 let contract = result.contract.unwrap();
1193 assert_eq!(contract.properties.len(), 1);
1194 assert_eq!(contract.properties[0].name, "pubKeyHash");
1195 assert!(contract.properties[0].readonly);
1196 assert!(matches!(
1197 &contract.properties[0].prop_type,
1198 TypeNode::Primitive(PrimitiveTypeName::Ripemd160)
1199 ));
1200 }
1201
1202 #[test]
1203 fn test_parse_p2pkh_constructor() {
1204 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1205 let contract = result.contract.unwrap();
1206 assert_eq!(contract.constructor.name, "constructor");
1207 assert_eq!(contract.constructor.params.len(), 1);
1208 assert_eq!(contract.constructor.params[0].name, "pubKeyHash");
1209 }
1210
1211 #[test]
1212 fn test_parse_p2pkh_methods() {
1213 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1214 let contract = result.contract.unwrap();
1215 assert_eq!(contract.methods.len(), 1);
1216 assert_eq!(contract.methods[0].name, "unlock");
1217 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1218 assert_eq!(contract.methods[0].params.len(), 2);
1219 assert_eq!(contract.methods[0].params[0].name, "sig");
1220 assert_eq!(contract.methods[0].params[1].name, "pubKey");
1221 }
1222
1223 const COUNTER_SOURCE: &str = r#"
1228 import { StatefulSmartContract, assert } from 'runar-lang';
1229
1230 class Counter extends StatefulSmartContract {
1231 count: bigint;
1232
1233 constructor(count: bigint) {
1234 super(count);
1235 }
1236
1237 public increment() {
1238 this.count++;
1239 assert(this.count > 0n);
1240 }
1241 }
1242 "#;
1243
1244 #[test]
1245 fn test_parse_counter_stateful() {
1246 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1247 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1248 let contract = result.contract.expect("should produce a contract");
1249 assert_eq!(contract.name, "Counter");
1250 assert_eq!(contract.parent_class, "StatefulSmartContract");
1251 }
1252
1253 #[test]
1254 fn test_parse_counter_mutable_property() {
1255 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1256 let contract = result.contract.unwrap();
1257 assert_eq!(contract.properties.len(), 1);
1258 assert_eq!(contract.properties[0].name, "count");
1259 assert!(!contract.properties[0].readonly, "count should be mutable");
1260 assert!(matches!(
1261 &contract.properties[0].prop_type,
1262 TypeNode::Primitive(PrimitiveTypeName::Bigint)
1263 ));
1264 }
1265
1266 #[test]
1267 fn test_parse_counter_increment_method() {
1268 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1269 let contract = result.contract.unwrap();
1270 assert_eq!(contract.methods.len(), 1);
1271 assert_eq!(contract.methods[0].name, "increment");
1272 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1273 assert!(contract.methods[0].params.is_empty());
1274 assert!(!contract.methods[0].body.is_empty(), "increment body should not be empty");
1275 }
1276
1277 #[test]
1282 fn test_parse_multiple_methods() {
1283 let source = r#"
1284 import { SmartContract, assert } from 'runar-lang';
1285
1286 class Multi extends SmartContract {
1287 readonly x: bigint;
1288
1289 constructor(x: bigint) {
1290 super(x);
1291 }
1292
1293 public methodA(a: bigint) {
1294 assert(a > 0n);
1295 }
1296
1297 public methodB(b: bigint) {
1298 assert(b > 0n);
1299 }
1300
1301 private helper(v: bigint) {
1302 assert(v > 0n);
1303 }
1304 }
1305 "#;
1306 let result = parse(source, Some("Multi.runar.ts"));
1307 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1308 let contract = result.contract.unwrap();
1309 assert_eq!(contract.methods.len(), 3);
1310 assert_eq!(contract.methods[0].name, "methodA");
1311 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1312 assert_eq!(contract.methods[1].name, "methodB");
1313 assert_eq!(contract.methods[1].visibility, Visibility::Public);
1314 assert_eq!(contract.methods[2].name, "helper");
1315 assert_eq!(contract.methods[2].visibility, Visibility::Private);
1316 }
1317
1318 #[test]
1323 fn test_parse_property_with_initializer() {
1324 let source = r#"
1325 import { StatefulSmartContract, assert } from 'runar-lang';
1326
1327 class WithInit extends StatefulSmartContract {
1328 count: bigint = 0n;
1329
1330 constructor() {
1331 super();
1332 }
1333
1334 public check() {
1335 assert(this.count === 0n);
1336 }
1337 }
1338 "#;
1339 let result = parse(source, Some("WithInit.runar.ts"));
1340 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1341 let contract = result.contract.unwrap();
1342 assert_eq!(contract.properties.len(), 1);
1343 assert!(
1344 contract.properties[0].initializer.is_some(),
1345 "property should have an initializer"
1346 );
1347 }
1348
1349 #[test]
1354 fn test_parse_no_contract_class_error() {
1355 let source = r#"
1356 class NotAContract {
1357 doSomething() {}
1358 }
1359 "#;
1360 let result = parse(source, Some("bad.runar.ts"));
1361 assert!(result.contract.is_none(), "should not produce a contract");
1362 assert!(
1363 result.errors.iter().any(|e| e.contains("No class extending SmartContract")),
1364 "should report missing SmartContract error, got: {:?}",
1365 result.errors
1366 );
1367 }
1368
1369 #[test]
1370 fn test_parse_syntax_error() {
1371 let source = "class { this is not valid }}}}}";
1372 let result = parse(source, Some("bad.runar.ts"));
1373 assert!(
1374 !result.errors.is_empty(),
1375 "should report parse errors for invalid syntax"
1376 );
1377 }
1378
1379 #[test]
1380 fn test_parse_empty_source_error() {
1381 let source = "";
1382 let result = parse(source, Some("empty.runar.ts"));
1383 assert!(result.contract.is_none());
1384 assert!(
1385 result.errors.iter().any(|e| e.contains("No class extending SmartContract")),
1386 "empty source should report no contract found, got: {:?}",
1387 result.errors
1388 );
1389 }
1390
1391 #[test]
1396 fn test_parse_binary_expressions() {
1397 let source = r#"
1398 import { SmartContract, assert } from 'runar-lang';
1399
1400 class BinOps extends SmartContract {
1401 readonly x: bigint;
1402
1403 constructor(x: bigint) {
1404 super(x);
1405 }
1406
1407 public check(a: bigint, b: bigint) {
1408 const sum = a + b;
1409 const diff = a - b;
1410 const prod = a * b;
1411 assert(sum > 0n);
1412 }
1413 }
1414 "#;
1415 let result = parse(source, Some("BinOps.runar.ts"));
1416 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1417 let contract = result.contract.unwrap();
1418 let body = &contract.methods[0].body;
1419 assert!(body.len() >= 4, "expected at least 4 statements, got {}", body.len());
1421 }
1422
1423 #[test]
1424 fn test_parse_ternary_expression() {
1425 let source = r#"
1426 import { SmartContract, assert } from 'runar-lang';
1427
1428 class Ternary extends SmartContract {
1429 readonly x: bigint;
1430
1431 constructor(x: bigint) {
1432 super(x);
1433 }
1434
1435 public check(a: bigint) {
1436 const result = a > 0n ? 1n : 0n;
1437 assert(result === 1n);
1438 }
1439 }
1440 "#;
1441 let result = parse(source, Some("Ternary.runar.ts"));
1442 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1443 let contract = result.contract.unwrap();
1444 if let Statement::VariableDecl { init, .. } = &contract.methods[0].body[0] {
1446 assert!(
1447 matches!(init, Expression::TernaryExpr { .. }),
1448 "expected ternary expression, got {:?}",
1449 init
1450 );
1451 } else {
1452 panic!("expected VariableDecl as first statement");
1453 }
1454 }
1455
1456 #[test]
1461 fn test_parse_source_ts_dispatch() {
1462 let result = parse_source(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1464 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1465 let contract = result.contract.expect("should produce a contract via TS parser");
1466 assert_eq!(contract.name, "P2PKH");
1467 }
1468
1469 #[test]
1470 fn test_parse_source_default_dispatch() {
1471 let result = parse_source(P2PKH_SOURCE, None);
1473 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1474 assert!(result.contract.is_some());
1475 }
1476
1477 #[test]
1482 fn test_parse_for_loop() {
1483 let source = r#"
1484 import { SmartContract, assert } from 'runar-lang';
1485
1486 class Loop extends SmartContract {
1487 readonly x: bigint;
1488
1489 constructor(x: bigint) {
1490 super(x);
1491 }
1492
1493 public check() {
1494 let sum = 0n;
1495 for (let i = 0n; i < 10n; i++) {
1496 sum += i;
1497 }
1498 assert(sum === 45n);
1499 }
1500 }
1501 "#;
1502 let result = parse(source, Some("Loop.runar.ts"));
1503 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1504 let contract = result.contract.unwrap();
1505 let body = &contract.methods[0].body;
1506 let has_for = body.iter().any(|s| matches!(s, Statement::ForStatement { .. }));
1508 assert!(has_for, "should contain a ForStatement, got: {:?}", body);
1509 }
1510
1511 #[test]
1516 fn test_parse_if_else() {
1517 let source = r#"
1518 import { SmartContract, assert } from 'runar-lang';
1519
1520 class IfElse extends SmartContract {
1521 readonly x: bigint;
1522
1523 constructor(x: bigint) {
1524 super(x);
1525 }
1526
1527 public check(a: bigint) {
1528 if (a > 0n) {
1529 assert(a > 0n);
1530 } else {
1531 assert(a === 0n);
1532 }
1533 }
1534 }
1535 "#;
1536 let result = parse(source, Some("IfElse.runar.ts"));
1537 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1538 let contract = result.contract.unwrap();
1539 let body = &contract.methods[0].body;
1540 let has_if = body.iter().any(|s| matches!(s, Statement::IfStatement { .. }));
1541 assert!(has_if, "should contain an IfStatement");
1542 }
1543
1544 #[test]
1549 fn test_parse_exported_contract() {
1550 let source = r#"
1551 import { SmartContract, assert } from 'runar-lang';
1552
1553 export class Exported extends SmartContract {
1554 readonly val: bigint;
1555
1556 constructor(val: bigint) {
1557 super(val);
1558 }
1559
1560 public check() {
1561 assert(this.val > 0n);
1562 }
1563 }
1564 "#;
1565 let result = parse(source, Some("Exported.runar.ts"));
1566 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1567 let contract = result.contract.expect("exported class should be found");
1568 assert_eq!(contract.name, "Exported");
1569 }
1570
1571 #[test]
1576 fn test_parse_various_param_types() {
1577 let source = r#"
1578 import { SmartContract, assert, PubKey, Sig, ByteString, Sha256 } from 'runar-lang';
1579
1580 class TypeTest extends SmartContract {
1581 readonly h: Sha256;
1582
1583 constructor(h: Sha256) {
1584 super(h);
1585 }
1586
1587 public check(sig: Sig, pubKey: PubKey, data: ByteString, flag: boolean) {
1588 assert(flag);
1589 }
1590 }
1591 "#;
1592 let result = parse(source, Some("TypeTest.runar.ts"));
1593 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1594 let contract = result.contract.unwrap();
1595 let params = &contract.methods[0].params;
1596 assert_eq!(params.len(), 4);
1597 assert!(matches!(¶ms[0].param_type, TypeNode::Primitive(PrimitiveTypeName::Sig)));
1598 assert!(matches!(¶ms[1].param_type, TypeNode::Primitive(PrimitiveTypeName::PubKey)));
1599 assert!(matches!(¶ms[2].param_type, TypeNode::Primitive(PrimitiveTypeName::ByteString)));
1600 assert!(matches!(¶ms[3].param_type, TypeNode::Primitive(PrimitiveTypeName::Boolean)));
1601 }
1602}