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};
23use super::diagnostic::Diagnostic;
24
25pub struct ParseResult {
31 pub contract: Option<ContractNode>,
32 pub errors: Vec<Diagnostic>,
33}
34
35impl ParseResult {
36 pub fn error_strings(&self) -> Vec<String> {
38 self.errors.iter().map(|d| d.format_message()).collect()
39 }
40}
41
42pub fn parse(source: &str, file_name: Option<&str>) -> ParseResult {
44 let mut errors: Vec<Diagnostic> = Vec::new();
45 let file = file_name.unwrap_or("contract.ts");
46
47 let cm: Lrc<SourceMap> = Lrc::new(SourceMap::default());
49 let fm = cm.new_source_file(Lrc::new(FileName::Custom(file.to_string())), source.to_string());
50 let lexer = Lexer::new(
51 Syntax::Typescript(TsSyntax {
52 tsx: false,
53 decorators: false,
54 ..Default::default()
55 }),
56 EsVersion::Es2022,
57 StringInput::from(&*fm),
58 None,
59 );
60 let mut parser = Parser::new_from(lexer);
61
62 let module = match parser.parse_module() {
63 Ok(m) => m,
64 Err(e) => {
65 errors.push(Diagnostic::error(format!("Parse error: {:?}", e), None));
66 return ParseResult {
67 contract: None,
68 errors,
69 };
70 }
71 };
72
73 for e in parser.take_errors() {
75 errors.push(Diagnostic::error(format!("Parse error: {:?}", e), None));
76 }
77
78 let mut contract_class: Option<&ClassDecl> = None;
80 let mut detected_parent_class: &str = "SmartContract";
81
82 for item in &module.body {
83 if let ModuleItem::Stmt(Stmt::Decl(Decl::Class(class_decl))) = item {
84 if let Some(super_class) = &class_decl.class.super_class {
85 if let Some(base_name) = get_base_class_name(super_class) {
86 if contract_class.is_some() {
87 errors.push(Diagnostic::error(
88 "Only one SmartContract subclass is allowed per file", None,
89 ));
90 }
91 contract_class = Some(class_decl);
92 detected_parent_class = base_name;
93 }
94 }
95 }
96 }
97
98 for item in &module.body {
100 if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
101 if let Decl::Class(class_decl) = &export_decl.decl {
102 if let Some(super_class) = &class_decl.class.super_class {
103 if let Some(base_name) = get_base_class_name(super_class) {
104 if contract_class.is_some() {
105 errors.push(Diagnostic::error(
106 "Only one SmartContract subclass is allowed per file", None,
107 ));
108 }
109 contract_class = Some(class_decl);
110 detected_parent_class = base_name;
111 }
112 }
113 }
114 }
115 }
116
117 let class_decl = match contract_class {
118 Some(c) => c,
119 None => {
120 errors.push(Diagnostic::error("No class extending SmartContract or StatefulSmartContract found", None));
121 return ParseResult {
122 contract: None,
123 errors,
124 };
125 }
126 };
127
128 let contract_name = class_decl.ident.sym.to_string();
129 let class = &class_decl.class;
130
131 let properties = parse_properties(class, file, &mut errors);
133
134 let constructor_node = parse_constructor(class, file, &mut errors);
136
137 let methods = parse_methods(class, file, &mut errors);
139
140 let contract = ContractNode {
141 name: contract_name,
142 parent_class: detected_parent_class.to_string(),
143 properties,
144 constructor: constructor_node,
145 methods,
146 source_file: file.to_string(),
147 };
148
149 ParseResult {
150 contract: Some(contract),
151 errors,
152 }
153}
154
155fn get_base_class_name(expr: &Expr) -> Option<&str> {
160 match expr {
161 Expr::Ident(ident) => {
162 let name = ident.sym.as_ref();
163 if name == "SmartContract" || name == "StatefulSmartContract" {
164 Some(name)
165 } else {
166 None
167 }
168 }
169 _ => None,
170 }
171}
172
173fn loc(file: &str, line: usize, column: usize) -> SourceLocation {
174 SourceLocation {
175 file: file.to_string(),
176 line,
177 column,
178 }
179}
180
181fn default_loc(file: &str) -> SourceLocation {
182 loc(file, 1, 0)
183}
184
185fn parse_properties(class: &Class, file: &str, errors: &mut Vec<Diagnostic>) -> Vec<PropertyNode> {
190 let mut result = Vec::new();
191
192 for member in &class.body {
193 if let ClassMember::ClassProp(prop) = member {
194 let name = match &prop.key {
195 PropName::Ident(ident) => ident.sym.to_string(),
196 _ => {
197 errors.push(Diagnostic::error("Property must have an identifier name", None));
198 continue;
199 }
200 };
201
202 let readonly = prop.readonly;
203
204 let prop_type = if let Some(ref ann) = prop.type_ann {
205 parse_type_node(&ann.type_ann, file, errors)
206 } else {
207 errors.push(Diagnostic::error(format!(
208 "Property '{}' must have an explicit type annotation",
209 name
210 ), None));
211 TypeNode::Custom("unknown".to_string())
212 };
213
214 let initializer = prop.value.as_ref().map(|v| parse_expression(v, file, errors));
216
217 result.push(PropertyNode {
218 name,
219 prop_type,
220 readonly,
221 initializer,
222 source_location: default_loc(file),
223 });
224 }
225 }
226
227 result
228}
229
230fn parse_constructor(class: &Class, file: &str, errors: &mut Vec<Diagnostic>) -> MethodNode {
235 for member in &class.body {
236 if let ClassMember::Constructor(ctor) = member {
237 let params = parse_constructor_params(&ctor.params, file, errors);
238 let body = if let Some(ref block) = ctor.body {
239 parse_block_stmts(&block.stmts, file, errors)
240 } else {
241 Vec::new()
242 };
243
244 return MethodNode {
245 name: "constructor".to_string(),
246 params,
247 body,
248 visibility: Visibility::Public,
249 source_location: default_loc(file),
250 };
251 }
252 }
253
254 errors.push(Diagnostic::error("Contract must have a constructor", None));
255 MethodNode {
256 name: "constructor".to_string(),
257 params: Vec::new(),
258 body: Vec::new(),
259 visibility: Visibility::Public,
260 source_location: default_loc(file),
261 }
262}
263
264fn parse_constructor_params(
265 params: &[ParamOrTsParamProp],
266 file: &str,
267 errors: &mut Vec<Diagnostic>,
268) -> Vec<ParamNode> {
269 let mut result = Vec::new();
270
271 for param in params {
272 match param {
273 ParamOrTsParamProp::Param(p) => {
274 if let Some(ast_param) = parse_param_pat(&p.pat, file, errors) {
275 result.push(ast_param);
276 }
277 }
278 ParamOrTsParamProp::TsParamProp(ts_param) => {
279 match &ts_param.param {
280 TsParamPropParam::Ident(ident) => {
281 let name = ident.id.sym.to_string();
282 let param_type = if let Some(ref ann) = ident.type_ann {
283 parse_type_node(&ann.type_ann, file, errors)
284 } else {
285 errors.push(Diagnostic::error(format!(
286 "Parameter '{}' must have an explicit type annotation",
287 name
288 ), None));
289 TypeNode::Custom("unknown".to_string())
290 };
291 result.push(ParamNode { name, param_type });
292 }
293 TsParamPropParam::Assign(_) => {
294 errors.push(Diagnostic::error("Default parameter values are not supported", None));
295 }
296 }
297 }
298 }
299 }
300
301 result
302}
303
304fn parse_methods(class: &Class, file: &str, errors: &mut Vec<Diagnostic>) -> Vec<MethodNode> {
309 let mut result = Vec::new();
310
311 for member in &class.body {
312 if let ClassMember::Method(method) = member {
313 let name = match &method.key {
314 PropName::Ident(ident) => ident.sym.to_string(),
315 _ => {
316 errors.push(Diagnostic::error("Method must have an identifier name", None));
317 continue;
318 }
319 };
320
321 let params = parse_method_params(&method.function.params, file, errors);
322
323 let visibility = if method.accessibility == Some(Accessibility::Public) {
324 Visibility::Public
325 } else {
326 Visibility::Private
327 };
328
329 let body = if let Some(ref block) = method.function.body {
330 parse_block_stmts(&block.stmts, file, errors)
331 } else {
332 Vec::new()
333 };
334
335 result.push(MethodNode {
336 name,
337 params,
338 body,
339 visibility,
340 source_location: default_loc(file),
341 });
342 }
343 }
344
345 result
346}
347
348fn parse_method_params(
349 params: &[Param],
350 file: &str,
351 errors: &mut Vec<Diagnostic>,
352) -> Vec<ParamNode> {
353 let mut result = Vec::new();
354 for param in params {
355 if let Some(ast_param) = parse_param_pat(¶m.pat, file, errors) {
356 result.push(ast_param);
357 }
358 }
359 result
360}
361
362fn parse_param_pat(
363 pat: &Pat,
364 file: &str,
365 errors: &mut Vec<Diagnostic>,
366) -> Option<ParamNode> {
367 match pat {
368 Pat::Ident(ident) => {
369 let name = ident.id.sym.to_string();
370 let param_type = if let Some(ref ann) = ident.type_ann {
371 parse_type_node(&ann.type_ann, file, errors)
372 } else {
373 errors.push(Diagnostic::error(format!(
374 "Parameter '{}' must have an explicit type annotation",
375 name
376 ), None));
377 TypeNode::Custom("unknown".to_string())
378 };
379 Some(ParamNode { name, param_type })
380 }
381 _ => {
382 errors.push(Diagnostic::error("Unsupported parameter pattern", None));
383 None
384 }
385 }
386}
387
388fn parse_type_node(ts_type: &TsType, file: &str, errors: &mut Vec<Diagnostic>) -> TypeNode {
393 match ts_type {
394 TsType::TsKeywordType(kw) => match kw.kind {
396 TsKeywordTypeKind::TsBigIntKeyword => TypeNode::Primitive(PrimitiveTypeName::Bigint),
397 TsKeywordTypeKind::TsBooleanKeyword => {
398 TypeNode::Primitive(PrimitiveTypeName::Boolean)
399 }
400 TsKeywordTypeKind::TsVoidKeyword => TypeNode::Primitive(PrimitiveTypeName::Void),
401 TsKeywordTypeKind::TsNumberKeyword => {
402 errors.push(Diagnostic::error("'number' type is not allowed in Rúnar contracts; use 'bigint' instead", None));
403 TypeNode::Primitive(PrimitiveTypeName::Bigint)
404 }
405 TsKeywordTypeKind::TsStringKeyword => {
406 errors.push(Diagnostic::error("'string' type is not allowed in Rúnar contracts; use 'ByteString' instead", None));
407 TypeNode::Primitive(PrimitiveTypeName::ByteString)
408 }
409 _ => {
410 errors.push(Diagnostic::error(format!("Unsupported keyword type: {:?}", kw.kind), None));
411 TypeNode::Custom("unknown".to_string())
412 }
413 },
414
415 TsType::TsTypeRef(type_ref) => {
417 let type_name = ts_entity_name_to_string(&type_ref.type_name);
418
419 if type_name == "FixedArray" {
421 if let Some(ref type_params) = type_ref.type_params {
422 let params = &type_params.params;
423 if params.len() != 2 {
424 errors.push(Diagnostic::error(
425 "FixedArray requires exactly 2 type arguments: FixedArray<T, N>",
426 None,
427 ));
428 return TypeNode::Custom(type_name);
429 }
430
431 let element = parse_type_node(¶ms[0], file, errors);
432
433 let length = extract_type_literal_number(¶ms[1]);
435 if let Some(len) = length {
436 return TypeNode::FixedArray {
437 element: Box::new(element),
438 length: len,
439 };
440 } else {
441 errors.push(Diagnostic::error(
442 "FixedArray size must be a non-negative integer literal",
443 None,
444 ));
445 return TypeNode::Custom(type_name);
446 }
447 } else {
448 errors.push(Diagnostic::error("FixedArray requires type arguments", None));
449 return TypeNode::Custom(type_name);
450 }
451 }
452
453 if let Some(prim) = PrimitiveTypeName::from_str(&type_name) {
455 return TypeNode::Primitive(prim);
456 }
457
458 TypeNode::Custom(type_name)
460 }
461
462 _ => {
463 errors.push(Diagnostic::error("Unsupported type annotation", None));
464 TypeNode::Custom("unknown".to_string())
465 }
466 }
467}
468
469fn ts_entity_name_to_string(entity: &TsEntityName) -> String {
470 match entity {
471 TsEntityName::Ident(ident) => ident.sym.to_string(),
472 TsEntityName::TsQualifiedName(qual) => {
473 format!(
474 "{}.{}",
475 ts_entity_name_to_string(&qual.left),
476 qual.right.sym
477 )
478 }
479 }
480}
481
482fn extract_type_literal_number(ts_type: &TsType) -> Option<usize> {
484 match ts_type {
485 TsType::TsLitType(lit) => match &lit.lit {
486 TsLit::Number(n) => Some(n.value as usize),
487 _ => None,
488 },
489 _ => None,
490 }
491}
492
493fn parse_block_stmts(stmts: &[Stmt], file: &str, errors: &mut Vec<Diagnostic>) -> Vec<Statement> {
498 let mut result = Vec::new();
499 for stmt in stmts {
500 if let Some(parsed) = parse_statement(stmt, file, errors) {
501 result.push(parsed);
502 }
503 }
504 result
505}
506
507fn parse_statement(stmt: &Stmt, file: &str, errors: &mut Vec<Diagnostic>) -> Option<Statement> {
508 match stmt {
509 Stmt::Decl(Decl::Var(var_decl)) => parse_variable_statement(var_decl, file, errors),
510
511 Stmt::Expr(expr_stmt) => parse_expression_statement(&expr_stmt.expr, file, errors),
512
513 Stmt::If(if_stmt) => Some(parse_if_statement(if_stmt, file, errors)),
514
515 Stmt::For(for_stmt) => Some(parse_for_statement(for_stmt, file, errors)),
516
517 Stmt::Return(ret_stmt) => Some(parse_return_statement(ret_stmt, file, errors)),
518
519 Stmt::Block(block) => {
520 let stmts = parse_block_stmts(&block.stmts, file, errors);
522 if stmts.is_empty() {
525 None
526 } else {
527 errors.push(Diagnostic::error("Standalone block statements are not supported; use if/for", None));
528 None
529 }
530 }
531
532 _ => {
533 errors.push(Diagnostic::error(format!("Unsupported statement kind: {:?}", stmt), None));
534 None
535 }
536 }
537}
538
539fn parse_variable_statement(
540 var_decl: &VarDecl,
541 file: &str,
542 errors: &mut Vec<Diagnostic>,
543) -> Option<Statement> {
544 if var_decl.decls.is_empty() {
545 return None;
546 }
547
548 let decl = &var_decl.decls[0];
549 let name = match &decl.name {
550 Pat::Ident(ident) => ident.id.sym.to_string(),
551 _ => {
552 errors.push(Diagnostic::error("Destructuring patterns are not supported in variable declarations", None));
553 return None;
554 }
555 };
556
557 let is_const = var_decl.kind == VarDeclKind::Const;
558
559 let init = if let Some(ref init_expr) = decl.init {
560 parse_expression(init_expr, file, errors)
561 } else {
562 errors.push(Diagnostic::error(format!("Variable '{}' must have an initializer", name), None));
563 Expression::BigIntLiteral { value: 0 }
564 };
565
566 let var_type = if let Pat::Ident(ident) = &decl.name {
567 if let Some(ref ann) = ident.type_ann {
568 Some(parse_type_node(&ann.type_ann, file, errors))
569 } else {
570 None
571 }
572 } else {
573 None
574 };
575
576 Some(Statement::VariableDecl {
577 name,
578 var_type,
579 mutable: !is_const,
580 init,
581 source_location: default_loc(file),
582 })
583}
584
585fn parse_expression_statement(
586 expr: &Expr,
587 file: &str,
588 errors: &mut Vec<Diagnostic>,
589) -> Option<Statement> {
590 if let Expr::Assign(assign) = expr {
592 return Some(parse_assignment_expr(assign, file, errors));
593 }
594
595 let expression = parse_expression(expr, file, errors);
596 Some(Statement::ExpressionStatement {
597 expression,
598 source_location: default_loc(file),
599 })
600}
601
602fn parse_assignment_expr(
603 assign: &AssignExpr,
604 file: &str,
605 errors: &mut Vec<Diagnostic>,
606) -> Statement {
607 let target = parse_assign_target(&assign.left, file, errors);
608
609 match assign.op {
610 AssignOp::Assign => {
611 let value = parse_expression(&assign.right, file, errors);
612 Statement::Assignment {
613 target,
614 value,
615 source_location: default_loc(file),
616 }
617 }
618 op => {
620 let bin_op = match op {
621 AssignOp::AddAssign => Some(BinaryOp::Add),
622 AssignOp::SubAssign => Some(BinaryOp::Sub),
623 AssignOp::MulAssign => Some(BinaryOp::Mul),
624 AssignOp::DivAssign => Some(BinaryOp::Div),
625 AssignOp::ModAssign => Some(BinaryOp::Mod),
626 _ => {
627 errors.push(Diagnostic::error(format!("Unsupported compound assignment operator: {:?}", op), None));
628 None
629 }
630 };
631
632 if let Some(bin_op) = bin_op {
633 let right = parse_expression(&assign.right, file, errors);
634 let target_for_rhs = parse_assign_target(&assign.left, file, errors);
635 let value = Expression::BinaryExpr {
636 op: bin_op,
637 left: Box::new(target_for_rhs),
638 right: Box::new(right),
639 };
640 Statement::Assignment {
641 target,
642 value,
643 source_location: default_loc(file),
644 }
645 } else {
646 let value = parse_expression(&assign.right, file, errors);
647 Statement::Assignment {
648 target,
649 value,
650 source_location: default_loc(file),
651 }
652 }
653 }
654 }
655}
656
657fn parse_assign_target(
658 target: &AssignTarget,
659 file: &str,
660 errors: &mut Vec<Diagnostic>,
661) -> Expression {
662 match target {
663 AssignTarget::Simple(simple) => match simple {
664 SimpleAssignTarget::Ident(ident) => Expression::Identifier {
665 name: ident.id.sym.to_string(),
666 },
667 SimpleAssignTarget::Member(member) => {
668 parse_member_expression(member, file, errors)
669 }
670 _ => {
671 errors.push(Diagnostic::error("Unsupported assignment target", None));
672 Expression::Identifier {
673 name: "_error".to_string(),
674 }
675 }
676 },
677 AssignTarget::Pat(_) => {
678 errors.push(Diagnostic::error("Destructuring assignment is not supported", None));
679 Expression::Identifier {
680 name: "_error".to_string(),
681 }
682 }
683 }
684}
685
686fn parse_if_statement(if_stmt: &IfStmt, file: &str, errors: &mut Vec<Diagnostic>) -> Statement {
687 let condition = parse_expression(&if_stmt.test, file, errors);
688 let then_branch = parse_stmt_or_block(&if_stmt.cons, file, errors);
689
690 let else_branch = if_stmt
691 .alt
692 .as_ref()
693 .map(|alt| parse_stmt_or_block(alt, file, errors));
694
695 Statement::IfStatement {
696 condition,
697 then_branch,
698 else_branch,
699 source_location: default_loc(file),
700 }
701}
702
703fn parse_stmt_or_block(
704 stmt: &Stmt,
705 file: &str,
706 errors: &mut Vec<Diagnostic>,
707) -> Vec<Statement> {
708 match stmt {
709 Stmt::Block(block) => parse_block_stmts(&block.stmts, file, errors),
710 _ => {
711 if let Some(s) = parse_statement(stmt, file, errors) {
712 vec![s]
713 } else {
714 Vec::new()
715 }
716 }
717 }
718}
719
720fn parse_for_statement(for_stmt: &ForStmt, file: &str, errors: &mut Vec<Diagnostic>) -> Statement {
721 let init = if let Some(ref init_expr) = for_stmt.init {
723 match init_expr {
724 VarDeclOrExpr::VarDecl(var_decl) => {
725 if let Some(stmt) = parse_variable_statement(var_decl, file, errors) {
726 stmt
727 } else {
728 make_default_for_init(file)
729 }
730 }
731 VarDeclOrExpr::Expr(_) => {
732 errors.push(Diagnostic::error(
733 "For loop must have a variable declaration initializer",
734 None,
735 ));
736 make_default_for_init(file)
737 }
738 }
739 } else {
740 errors.push(Diagnostic::error("For loop must have an initializer", None));
741 make_default_for_init(file)
742 };
743
744 let condition = if let Some(ref cond) = for_stmt.test {
746 parse_expression(cond, file, errors)
747 } else {
748 errors.push(Diagnostic::error("For loop must have a condition", None));
749 Expression::BoolLiteral { value: false }
750 };
751
752 let update = if let Some(ref upd) = for_stmt.update {
754 parse_for_update(upd, file, errors)
755 } else {
756 errors.push(Diagnostic::error("For loop must have an update expression", None));
757 Statement::ExpressionStatement {
758 expression: Expression::BigIntLiteral { value: 0 },
759 source_location: default_loc(file),
760 }
761 };
762
763 let body = parse_stmt_or_block(&for_stmt.body, file, errors);
765
766 Statement::ForStatement {
767 init: Box::new(init),
768 condition,
769 update: Box::new(update),
770 body,
771 source_location: default_loc(file),
772 }
773}
774
775fn parse_for_update(
776 expr: &Expr,
777 file: &str,
778 errors: &mut Vec<Diagnostic>,
779) -> Statement {
780 match expr {
781 Expr::Update(update) => {
782 let operand = parse_expression(&update.arg, file, errors);
783 let is_increment = update.op == UpdateOp::PlusPlus;
784 let expression = if is_increment {
785 Expression::IncrementExpr {
786 operand: Box::new(operand),
787 prefix: update.prefix,
788 }
789 } else {
790 Expression::DecrementExpr {
791 operand: Box::new(operand),
792 prefix: update.prefix,
793 }
794 };
795 Statement::ExpressionStatement {
796 expression,
797 source_location: default_loc(file),
798 }
799 }
800 _ => {
801 let expression = parse_expression(expr, file, errors);
802 Statement::ExpressionStatement {
803 expression,
804 source_location: default_loc(file),
805 }
806 }
807 }
808}
809
810fn parse_return_statement(
811 ret_stmt: &ReturnStmt,
812 file: &str,
813 errors: &mut Vec<Diagnostic>,
814) -> Statement {
815 let value = ret_stmt
816 .arg
817 .as_ref()
818 .map(|e| parse_expression(e, file, errors));
819
820 Statement::ReturnStatement {
821 value,
822 source_location: default_loc(file),
823 }
824}
825
826fn make_default_for_init(file: &str) -> Statement {
827 Statement::VariableDecl {
828 name: "_i".to_string(),
829 var_type: None,
830 mutable: true,
831 init: Expression::BigIntLiteral { value: 0 },
832 source_location: default_loc(file),
833 }
834}
835
836fn parse_expression(expr: &Expr, file: &str, errors: &mut Vec<Diagnostic>) -> Expression {
841 match expr {
842 Expr::Bin(bin) => parse_binary_expression(bin, file, errors),
843
844 Expr::Unary(unary) => parse_unary_expression(unary, file, errors),
845
846 Expr::Update(update) => parse_update_expression(update, file, errors),
847
848 Expr::Call(call) => parse_call_expression(call, file, errors),
849
850 Expr::Member(member) => parse_member_expression(member, file, errors),
851
852 Expr::SuperProp(super_prop) => {
853 match &super_prop.prop {
855 SuperProp::Ident(ident) => Expression::MemberExpr {
856 object: Box::new(Expression::Identifier {
857 name: "super".to_string(),
858 }),
859 property: ident.sym.to_string(),
860 },
861 SuperProp::Computed(comp) => {
862 let _ = parse_expression(&comp.expr, file, errors);
863 errors.push(Diagnostic::error("Computed super property access not supported", None));
864 Expression::Identifier {
865 name: "super".to_string(),
866 }
867 }
868 }
869 }
870
871 Expr::Ident(ident) => Expression::Identifier {
872 name: ident.sym.to_string(),
873 },
874
875 Expr::Lit(Lit::BigInt(bigint)) => {
876 let val = bigint_to_i64(bigint);
878 Expression::BigIntLiteral { value: val }
879 }
880
881 Expr::Lit(Lit::Num(num)) => {
882 Expression::BigIntLiteral {
884 value: num.value as i64,
885 }
886 }
887
888 Expr::Lit(Lit::Bool(b)) => Expression::BoolLiteral { value: b.value },
889
890 Expr::Lit(Lit::Str(s)) => {
891 Expression::ByteStringLiteral {
893 value: s.value.to_string(),
894 }
895 }
896
897 Expr::Tpl(tpl) => {
898 if tpl.exprs.is_empty() && tpl.quasis.len() == 1 {
900 Expression::ByteStringLiteral {
901 value: tpl.quasis[0].raw.to_string(),
902 }
903 } else {
904 errors.push(Diagnostic::error("Template literals with expressions are not supported", None));
905 Expression::ByteStringLiteral {
906 value: String::new(),
907 }
908 }
909 }
910
911 Expr::Cond(cond) => {
912 let condition = parse_expression(&cond.test, file, errors);
913 let consequent = parse_expression(&cond.cons, file, errors);
914 let alternate = parse_expression(&cond.alt, file, errors);
915 Expression::TernaryExpr {
916 condition: Box::new(condition),
917 consequent: Box::new(consequent),
918 alternate: Box::new(alternate),
919 }
920 }
921
922 Expr::Paren(paren) => parse_expression(&paren.expr, file, errors),
923
924 Expr::This(_) => Expression::Identifier {
925 name: "this".to_string(),
926 },
927
928 Expr::TsAs(as_expr) => {
929 parse_expression(&as_expr.expr, file, errors)
931 }
932
933 Expr::TsNonNull(nn) => {
934 parse_expression(&nn.expr, file, errors)
936 }
937
938 Expr::Assign(assign) => {
939 errors.push(Diagnostic::error("Assignment expressions in expression context are not recommended", None));
942 let value = parse_expression(&assign.right, file, errors);
943 value
944 }
945
946 _ => {
947 errors.push(Diagnostic::error(format!("Unsupported expression: {:?}", expr), None));
948 Expression::BigIntLiteral { value: 0 }
949 }
950 }
951}
952
953fn parse_binary_expression(
954 bin: &swc::BinExpr,
955 file: &str,
956 errors: &mut Vec<Diagnostic>,
957) -> Expression {
958 let left = parse_expression(&bin.left, file, errors);
959 let right = parse_expression(&bin.right, file, errors);
960
961 let op = match bin.op {
962 swc::BinaryOp::Add => BinaryOp::Add,
963 swc::BinaryOp::Sub => BinaryOp::Sub,
964 swc::BinaryOp::Mul => BinaryOp::Mul,
965 swc::BinaryOp::Div => BinaryOp::Div,
966 swc::BinaryOp::Mod => BinaryOp::Mod,
967 swc::BinaryOp::EqEqEq => BinaryOp::StrictEq,
968 swc::BinaryOp::NotEqEq => BinaryOp::StrictNe,
969 swc::BinaryOp::Lt => BinaryOp::Lt,
970 swc::BinaryOp::LtEq => BinaryOp::Le,
971 swc::BinaryOp::Gt => BinaryOp::Gt,
972 swc::BinaryOp::GtEq => BinaryOp::Ge,
973 swc::BinaryOp::LogicalAnd => BinaryOp::And,
974 swc::BinaryOp::LogicalOr => BinaryOp::Or,
975 swc::BinaryOp::BitAnd => BinaryOp::BitAnd,
976 swc::BinaryOp::BitOr => BinaryOp::BitOr,
977 swc::BinaryOp::BitXor => BinaryOp::BitXor,
978 swc::BinaryOp::EqEq => {
979 BinaryOp::StrictEq
981 }
982 swc::BinaryOp::NotEq => {
983 BinaryOp::StrictNe
985 }
986 _ => {
987 errors.push(Diagnostic::error(format!("Unsupported binary operator: {:?}", bin.op), None));
988 BinaryOp::Add
989 }
990 };
991
992 Expression::BinaryExpr {
993 op,
994 left: Box::new(left),
995 right: Box::new(right),
996 }
997}
998
999fn parse_unary_expression(
1000 unary: &SwcUnaryExpr,
1001 file: &str,
1002 errors: &mut Vec<Diagnostic>,
1003) -> Expression {
1004 let operand = parse_expression(&unary.arg, file, errors);
1005
1006 let op = match unary.op {
1007 swc::UnaryOp::Bang => UnaryOp::Not,
1008 swc::UnaryOp::Minus => UnaryOp::Neg,
1009 swc::UnaryOp::Tilde => UnaryOp::BitNot,
1010 _ => {
1011 errors.push(Diagnostic::error(format!("Unsupported unary operator: {:?}", unary.op), None));
1012 UnaryOp::Neg
1013 }
1014 };
1015
1016 Expression::UnaryExpr {
1017 op,
1018 operand: Box::new(operand),
1019 }
1020}
1021
1022fn parse_update_expression(
1023 update: &UpdateExpr,
1024 file: &str,
1025 errors: &mut Vec<Diagnostic>,
1026) -> Expression {
1027 let operand = parse_expression(&update.arg, file, errors);
1028
1029 if update.op == UpdateOp::PlusPlus {
1030 Expression::IncrementExpr {
1031 operand: Box::new(operand),
1032 prefix: update.prefix,
1033 }
1034 } else {
1035 Expression::DecrementExpr {
1036 operand: Box::new(operand),
1037 prefix: update.prefix,
1038 }
1039 }
1040}
1041
1042fn parse_call_expression(
1043 call: &CallExpr,
1044 file: &str,
1045 errors: &mut Vec<Diagnostic>,
1046) -> Expression {
1047 let callee = match &call.callee {
1048 Callee::Expr(e) => parse_expression(e, file, errors),
1049 Callee::Super(_) => Expression::Identifier {
1050 name: "super".to_string(),
1051 },
1052 Callee::Import(_) => {
1053 errors.push(Diagnostic::error("Dynamic import is not supported", None));
1054 Expression::Identifier {
1055 name: "_error".to_string(),
1056 }
1057 }
1058 };
1059
1060 let args: Vec<Expression> = call
1061 .args
1062 .iter()
1063 .map(|arg| parse_expression(&arg.expr, file, errors))
1064 .collect();
1065
1066 Expression::CallExpr {
1067 callee: Box::new(callee),
1068 args,
1069 }
1070}
1071
1072fn parse_member_expression(
1073 member: &SwcMemberExpr,
1074 file: &str,
1075 errors: &mut Vec<Diagnostic>,
1076) -> Expression {
1077 let prop_name = match &member.prop {
1078 MemberProp::Ident(ident) => ident.sym.to_string(),
1079 MemberProp::Computed(comp) => {
1080 let object = parse_expression(&member.obj, file, errors);
1082 let index = parse_expression(&comp.expr, file, errors);
1083 return Expression::IndexAccess {
1084 object: Box::new(object),
1085 index: Box::new(index),
1086 };
1087 }
1088 MemberProp::PrivateName(_priv_name) => {
1089 errors.push(Diagnostic::error("Private field access (#field) is not supported", None));
1090 "_private".to_string()
1091 }
1092 };
1093
1094 if let Expr::This(_) = &*member.obj {
1096 return Expression::PropertyAccess {
1097 property: prop_name,
1098 };
1099 }
1100
1101 let object = parse_expression(&member.obj, file, errors);
1103 Expression::MemberExpr {
1104 object: Box::new(object),
1105 property: prop_name,
1106 }
1107}
1108
1109fn bigint_to_i64(bigint_lit: &swc::BigInt) -> i64 {
1115 use std::str::FromStr;
1118 let s = bigint_lit.value.to_string();
1119 i64::from_str(&s).unwrap_or(0)
1120}
1121
1122pub fn parse_source(source: &str, file_name: Option<&str>) -> ParseResult {
1135 let name = file_name.unwrap_or("contract.ts");
1136 if name.ends_with(".runar.sol") {
1137 return super::parser_sol::parse_solidity(source, file_name);
1138 }
1139 if name.ends_with(".runar.move") {
1140 return super::parser_move::parse_move(source, file_name);
1141 }
1142 if name.ends_with(".runar.rs") {
1143 return super::parser_rustmacro::parse_rust_dsl(source, file_name);
1144 }
1145 if name.ends_with(".runar.py") {
1146 return super::parser_python::parse_python(source, file_name);
1147 }
1148 if name.ends_with(".runar.go") {
1149 return super::parser_gocontract::parse_go_contract(source, file_name);
1150 }
1151 if name.ends_with(".runar.rb") {
1152 return super::parser_ruby::parse_ruby(source, file_name);
1153 }
1154 parse(source, file_name)
1156}
1157
1158#[cfg(test)]
1163mod tests {
1164 use super::*;
1165
1166 const P2PKH_SOURCE: &str = r#"
1171 import { SmartContract, assert, Sig, PubKey, Ripemd160, hash160 } from 'runar-lang';
1172
1173 class P2PKH extends SmartContract {
1174 readonly pubKeyHash: Ripemd160;
1175
1176 constructor(pubKeyHash: Ripemd160) {
1177 super(pubKeyHash);
1178 }
1179
1180 public unlock(sig: Sig, pubKey: PubKey) {
1181 assert(hash160(pubKey) === this.pubKeyHash);
1182 assert(checkSig(sig, pubKey));
1183 }
1184 }
1185 "#;
1186
1187 #[test]
1188 fn test_parse_p2pkh_contract_name() {
1189 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1190 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1191 let contract = result.contract.expect("should produce a contract");
1192 assert_eq!(contract.name, "P2PKH");
1193 }
1194
1195 #[test]
1196 fn test_parse_p2pkh_parent_class() {
1197 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1198 let contract = result.contract.unwrap();
1199 assert_eq!(contract.parent_class, "SmartContract");
1200 }
1201
1202 #[test]
1203 fn test_parse_p2pkh_properties() {
1204 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1205 let contract = result.contract.unwrap();
1206 assert_eq!(contract.properties.len(), 1);
1207 assert_eq!(contract.properties[0].name, "pubKeyHash");
1208 assert!(contract.properties[0].readonly);
1209 assert!(matches!(
1210 &contract.properties[0].prop_type,
1211 TypeNode::Primitive(PrimitiveTypeName::Ripemd160)
1212 ));
1213 }
1214
1215 #[test]
1216 fn test_parse_p2pkh_constructor() {
1217 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1218 let contract = result.contract.unwrap();
1219 assert_eq!(contract.constructor.name, "constructor");
1220 assert_eq!(contract.constructor.params.len(), 1);
1221 assert_eq!(contract.constructor.params[0].name, "pubKeyHash");
1222 }
1223
1224 #[test]
1225 fn test_parse_p2pkh_methods() {
1226 let result = parse(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1227 let contract = result.contract.unwrap();
1228 assert_eq!(contract.methods.len(), 1);
1229 assert_eq!(contract.methods[0].name, "unlock");
1230 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1231 assert_eq!(contract.methods[0].params.len(), 2);
1232 assert_eq!(contract.methods[0].params[0].name, "sig");
1233 assert_eq!(contract.methods[0].params[1].name, "pubKey");
1234 }
1235
1236 const COUNTER_SOURCE: &str = r#"
1241 import { StatefulSmartContract, assert } from 'runar-lang';
1242
1243 class Counter extends StatefulSmartContract {
1244 count: bigint;
1245
1246 constructor(count: bigint) {
1247 super(count);
1248 }
1249
1250 public increment() {
1251 this.count++;
1252 assert(this.count > 0n);
1253 }
1254 }
1255 "#;
1256
1257 #[test]
1258 fn test_parse_counter_stateful() {
1259 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1260 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1261 let contract = result.contract.expect("should produce a contract");
1262 assert_eq!(contract.name, "Counter");
1263 assert_eq!(contract.parent_class, "StatefulSmartContract");
1264 }
1265
1266 #[test]
1267 fn test_parse_counter_mutable_property() {
1268 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1269 let contract = result.contract.unwrap();
1270 assert_eq!(contract.properties.len(), 1);
1271 assert_eq!(contract.properties[0].name, "count");
1272 assert!(!contract.properties[0].readonly, "count should be mutable");
1273 assert!(matches!(
1274 &contract.properties[0].prop_type,
1275 TypeNode::Primitive(PrimitiveTypeName::Bigint)
1276 ));
1277 }
1278
1279 #[test]
1280 fn test_parse_counter_increment_method() {
1281 let result = parse(COUNTER_SOURCE, Some("Counter.runar.ts"));
1282 let contract = result.contract.unwrap();
1283 assert_eq!(contract.methods.len(), 1);
1284 assert_eq!(contract.methods[0].name, "increment");
1285 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1286 assert!(contract.methods[0].params.is_empty());
1287 assert!(!contract.methods[0].body.is_empty(), "increment body should not be empty");
1288 }
1289
1290 #[test]
1295 fn test_parse_multiple_methods() {
1296 let source = r#"
1297 import { SmartContract, assert } from 'runar-lang';
1298
1299 class Multi extends SmartContract {
1300 readonly x: bigint;
1301
1302 constructor(x: bigint) {
1303 super(x);
1304 }
1305
1306 public methodA(a: bigint) {
1307 assert(a > 0n);
1308 }
1309
1310 public methodB(b: bigint) {
1311 assert(b > 0n);
1312 }
1313
1314 private helper(v: bigint) {
1315 assert(v > 0n);
1316 }
1317 }
1318 "#;
1319 let result = parse(source, Some("Multi.runar.ts"));
1320 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1321 let contract = result.contract.unwrap();
1322 assert_eq!(contract.methods.len(), 3);
1323 assert_eq!(contract.methods[0].name, "methodA");
1324 assert_eq!(contract.methods[0].visibility, Visibility::Public);
1325 assert_eq!(contract.methods[1].name, "methodB");
1326 assert_eq!(contract.methods[1].visibility, Visibility::Public);
1327 assert_eq!(contract.methods[2].name, "helper");
1328 assert_eq!(contract.methods[2].visibility, Visibility::Private);
1329 }
1330
1331 #[test]
1336 fn test_parse_property_with_initializer() {
1337 let source = r#"
1338 import { StatefulSmartContract, assert } from 'runar-lang';
1339
1340 class WithInit extends StatefulSmartContract {
1341 count: bigint = 0n;
1342
1343 constructor() {
1344 super();
1345 }
1346
1347 public check() {
1348 assert(this.count === 0n);
1349 }
1350 }
1351 "#;
1352 let result = parse(source, Some("WithInit.runar.ts"));
1353 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1354 let contract = result.contract.unwrap();
1355 assert_eq!(contract.properties.len(), 1);
1356 assert!(
1357 contract.properties[0].initializer.is_some(),
1358 "property should have an initializer"
1359 );
1360 }
1361
1362 #[test]
1367 fn test_parse_no_contract_class_error() {
1368 let source = r#"
1369 class NotAContract {
1370 doSomething() {}
1371 }
1372 "#;
1373 let result = parse(source, Some("bad.runar.ts"));
1374 assert!(result.contract.is_none(), "should not produce a contract");
1375 assert!(
1376 result.errors.iter().any(|e| e.message.contains("No class extending SmartContract")),
1377 "should report missing SmartContract error, got: {:?}",
1378 result.errors
1379 );
1380 }
1381
1382 #[test]
1383 fn test_parse_syntax_error() {
1384 let source = "class { this is not valid }}}}}";
1385 let result = parse(source, Some("bad.runar.ts"));
1386 assert!(
1387 !result.errors.is_empty(),
1388 "should report parse errors for invalid syntax"
1389 );
1390 }
1391
1392 #[test]
1393 fn test_parse_empty_source_error() {
1394 let source = "";
1395 let result = parse(source, Some("empty.runar.ts"));
1396 assert!(result.contract.is_none());
1397 assert!(
1398 result.errors.iter().any(|e| e.message.contains("No class extending SmartContract")),
1399 "empty source should report no contract found, got: {:?}",
1400 result.errors
1401 );
1402 }
1403
1404 #[test]
1409 fn test_parse_binary_expressions() {
1410 let source = r#"
1411 import { SmartContract, assert } from 'runar-lang';
1412
1413 class BinOps extends SmartContract {
1414 readonly x: bigint;
1415
1416 constructor(x: bigint) {
1417 super(x);
1418 }
1419
1420 public check(a: bigint, b: bigint) {
1421 const sum = a + b;
1422 const diff = a - b;
1423 const prod = a * b;
1424 assert(sum > 0n);
1425 }
1426 }
1427 "#;
1428 let result = parse(source, Some("BinOps.runar.ts"));
1429 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1430 let contract = result.contract.unwrap();
1431 let body = &contract.methods[0].body;
1432 assert!(body.len() >= 4, "expected at least 4 statements, got {}", body.len());
1434 }
1435
1436 #[test]
1437 fn test_parse_ternary_expression() {
1438 let source = r#"
1439 import { SmartContract, assert } from 'runar-lang';
1440
1441 class Ternary extends SmartContract {
1442 readonly x: bigint;
1443
1444 constructor(x: bigint) {
1445 super(x);
1446 }
1447
1448 public check(a: bigint) {
1449 const result = a > 0n ? 1n : 0n;
1450 assert(result === 1n);
1451 }
1452 }
1453 "#;
1454 let result = parse(source, Some("Ternary.runar.ts"));
1455 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1456 let contract = result.contract.unwrap();
1457 if let Statement::VariableDecl { init, .. } = &contract.methods[0].body[0] {
1459 assert!(
1460 matches!(init, Expression::TernaryExpr { .. }),
1461 "expected ternary expression, got {:?}",
1462 init
1463 );
1464 } else {
1465 panic!("expected VariableDecl as first statement");
1466 }
1467 }
1468
1469 #[test]
1474 fn test_parse_source_ts_dispatch() {
1475 let result = parse_source(P2PKH_SOURCE, Some("P2PKH.runar.ts"));
1477 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1478 let contract = result.contract.expect("should produce a contract via TS parser");
1479 assert_eq!(contract.name, "P2PKH");
1480 }
1481
1482 #[test]
1483 fn test_parse_source_default_dispatch() {
1484 let result = parse_source(P2PKH_SOURCE, None);
1486 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1487 assert!(result.contract.is_some());
1488 }
1489
1490 #[test]
1495 fn test_parse_for_loop() {
1496 let source = r#"
1497 import { SmartContract, assert } from 'runar-lang';
1498
1499 class Loop extends SmartContract {
1500 readonly x: bigint;
1501
1502 constructor(x: bigint) {
1503 super(x);
1504 }
1505
1506 public check() {
1507 let sum = 0n;
1508 for (let i = 0n; i < 10n; i++) {
1509 sum += i;
1510 }
1511 assert(sum === 45n);
1512 }
1513 }
1514 "#;
1515 let result = parse(source, Some("Loop.runar.ts"));
1516 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1517 let contract = result.contract.unwrap();
1518 let body = &contract.methods[0].body;
1519 let has_for = body.iter().any(|s| matches!(s, Statement::ForStatement { .. }));
1521 assert!(has_for, "should contain a ForStatement, got: {:?}", body);
1522 }
1523
1524 #[test]
1529 fn test_parse_if_else() {
1530 let source = r#"
1531 import { SmartContract, assert } from 'runar-lang';
1532
1533 class IfElse extends SmartContract {
1534 readonly x: bigint;
1535
1536 constructor(x: bigint) {
1537 super(x);
1538 }
1539
1540 public check(a: bigint) {
1541 if (a > 0n) {
1542 assert(a > 0n);
1543 } else {
1544 assert(a === 0n);
1545 }
1546 }
1547 }
1548 "#;
1549 let result = parse(source, Some("IfElse.runar.ts"));
1550 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1551 let contract = result.contract.unwrap();
1552 let body = &contract.methods[0].body;
1553 let has_if = body.iter().any(|s| matches!(s, Statement::IfStatement { .. }));
1554 assert!(has_if, "should contain an IfStatement");
1555 }
1556
1557 #[test]
1562 fn test_parse_exported_contract() {
1563 let source = r#"
1564 import { SmartContract, assert } from 'runar-lang';
1565
1566 export class Exported extends SmartContract {
1567 readonly val: bigint;
1568
1569 constructor(val: bigint) {
1570 super(val);
1571 }
1572
1573 public check() {
1574 assert(this.val > 0n);
1575 }
1576 }
1577 "#;
1578 let result = parse(source, Some("Exported.runar.ts"));
1579 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1580 let contract = result.contract.expect("exported class should be found");
1581 assert_eq!(contract.name, "Exported");
1582 }
1583
1584 #[test]
1589 fn test_parse_various_param_types() {
1590 let source = r#"
1591 import { SmartContract, assert, PubKey, Sig, ByteString, Sha256 } from 'runar-lang';
1592
1593 class TypeTest extends SmartContract {
1594 readonly h: Sha256;
1595
1596 constructor(h: Sha256) {
1597 super(h);
1598 }
1599
1600 public check(sig: Sig, pubKey: PubKey, data: ByteString, flag: boolean) {
1601 assert(flag);
1602 }
1603 }
1604 "#;
1605 let result = parse(source, Some("TypeTest.runar.ts"));
1606 assert!(result.errors.is_empty(), "unexpected errors: {:?}", result.errors);
1607 let contract = result.contract.unwrap();
1608 let params = &contract.methods[0].params;
1609 assert_eq!(params.len(), 4);
1610 assert!(matches!(¶ms[0].param_type, TypeNode::Primitive(PrimitiveTypeName::Sig)));
1611 assert!(matches!(¶ms[1].param_type, TypeNode::Primitive(PrimitiveTypeName::PubKey)));
1612 assert!(matches!(¶ms[2].param_type, TypeNode::Primitive(PrimitiveTypeName::ByteString)));
1613 assert!(matches!(¶ms[3].param_type, TypeNode::Primitive(PrimitiveTypeName::Boolean)));
1614 }
1615}