Skip to main content

runar_compiler_rust/frontend/
parser_rustmacro.rs

1//! Rust DSL parser for Rúnar contracts (.runar.rs).
2//!
3//! Parses Rust-style contract definitions using a hand-written tokenizer
4//! and recursive descent parser. Produces the same AST as the TypeScript parser.
5
6use super::ast::{
7    BinaryOp, ContractNode, Expression, MethodNode, ParamNode, PrimitiveTypeName,
8    PropertyNode, SourceLocation, Statement, TypeNode, UnaryOp, Visibility,
9};
10use super::diagnostic::Diagnostic;
11use super::parser::ParseResult;
12
13// ---------------------------------------------------------------------------
14// Token types
15// ---------------------------------------------------------------------------
16
17#[derive(Debug, Clone, PartialEq)]
18enum TokenType {
19    Use, Struct, Impl, Fn, Pub, Let, Mut, If, Else, For, Return, In,
20    True, False, Self_,
21    AssertMacro, AssertEqMacro,
22    Ident(String), Number(String), HexString(String),
23    // Attributes
24    HashBracket, // #[
25    // Punctuation
26    LParen, RParen, LBrace, RBrace, LBracket, RBracket,
27    Semi, Comma, Dot, Colon, ColonColon, Arrow,
28    // Operators
29    Plus, Minus, Star, Slash, Percent,
30    EqEq, BangEq, Lt, LtEq, Gt, GtEq,
31    AmpAmp, PipePipe,
32    Amp, Pipe, Caret, Tilde, Bang,
33    Eq, PlusEq, MinusEq,
34    // End
35    Eof,
36}
37
38#[derive(Debug, Clone)]
39struct Token {
40    typ: TokenType,
41    line: usize,
42    col: usize,
43}
44
45// ---------------------------------------------------------------------------
46// Tokenizer
47// ---------------------------------------------------------------------------
48
49fn tokenize(source: &str) -> Vec<Token> {
50    let chars: Vec<char> = source.chars().collect();
51    let mut tokens = Vec::new();
52    let mut pos = 0;
53    let mut line = 1usize;
54    let mut col = 1usize;
55
56    while pos < chars.len() {
57        let ch = chars[pos];
58        let l = line;
59        let c = col;
60
61        // Whitespace
62        if ch.is_whitespace() {
63            if ch == '\n' { line += 1; col = 1; } else { col += 1; }
64            pos += 1;
65            continue;
66        }
67
68        // Line comments
69        if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '/' {
70            while pos < chars.len() && chars[pos] != '\n' { pos += 1; }
71            continue;
72        }
73
74        // Block comments
75        if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '*' {
76            pos += 2; col += 2;
77            while pos + 1 < chars.len() {
78                if chars[pos] == '\n' { line += 1; col = 1; }
79                if chars[pos] == '*' && chars[pos + 1] == '/' { pos += 2; col += 2; break; }
80                pos += 1; col += 1;
81            }
82            continue;
83        }
84
85        // #[ attribute
86        if ch == '#' && pos + 1 < chars.len() && chars[pos + 1] == '[' {
87            tokens.push(Token { typ: TokenType::HashBracket, line: l, col: c });
88            pos += 2; col += 2;
89            continue;
90        }
91
92        // Two-char operators
93        if pos + 1 < chars.len() {
94            let two = format!("{}{}", ch, chars[pos + 1]);
95            let tok = match two.as_str() {
96                "::" => Some(TokenType::ColonColon),
97                "->" => Some(TokenType::Arrow),
98                "==" => Some(TokenType::EqEq),
99                "!=" => Some(TokenType::BangEq),
100                "<=" => Some(TokenType::LtEq),
101                ">=" => Some(TokenType::GtEq),
102                "&&" => Some(TokenType::AmpAmp),
103                "||" => Some(TokenType::PipePipe),
104                "+=" => Some(TokenType::PlusEq),
105                "-=" => Some(TokenType::MinusEq),
106                _ => None,
107            };
108            if let Some(t) = tok {
109                tokens.push(Token { typ: t, line: l, col: c });
110                pos += 2; col += 2;
111                continue;
112            }
113        }
114
115        // Single-char tokens
116        let single = match ch {
117            '(' => Some(TokenType::LParen),
118            ')' => Some(TokenType::RParen),
119            '{' => Some(TokenType::LBrace),
120            '}' => Some(TokenType::RBrace),
121            '[' => Some(TokenType::LBracket),
122            ']' => Some(TokenType::RBracket),
123            ';' => Some(TokenType::Semi),
124            ',' => Some(TokenType::Comma),
125            '.' => Some(TokenType::Dot),
126            ':' => Some(TokenType::Colon),
127            '+' => Some(TokenType::Plus),
128            '-' => Some(TokenType::Minus),
129            '*' => Some(TokenType::Star),
130            '/' => Some(TokenType::Slash),
131            '%' => Some(TokenType::Percent),
132            '<' => Some(TokenType::Lt),
133            '>' => Some(TokenType::Gt),
134            '&' => Some(TokenType::Amp),
135            '|' => Some(TokenType::Pipe),
136            '^' => Some(TokenType::Caret),
137            '~' => Some(TokenType::Tilde),
138            '!' => Some(TokenType::Bang),
139            '=' => Some(TokenType::Eq),
140            _ => None,
141        };
142        if let Some(t) = single {
143            tokens.push(Token { typ: t, line: l, col: c });
144            pos += 1; col += 1;
145            continue;
146        }
147
148        // Hex literal
149        if ch == '0' && pos + 1 < chars.len() && chars[pos + 1] == 'x' {
150            let mut val = String::new();
151            pos += 2; col += 2;
152            while pos < chars.len() && chars[pos].is_ascii_hexdigit() {
153                val.push(chars[pos]);
154                pos += 1; col += 1;
155            }
156            tokens.push(Token { typ: TokenType::HexString(val), line: l, col: c });
157            continue;
158        }
159
160        // Number
161        if ch.is_ascii_digit() {
162            let mut val = String::new();
163            while pos < chars.len() && (chars[pos].is_ascii_digit() || chars[pos] == '_') {
164                if chars[pos] != '_' { val.push(chars[pos]); }
165                pos += 1; col += 1;
166            }
167            tokens.push(Token { typ: TokenType::Number(val), line: l, col: c });
168            continue;
169        }
170
171        // Identifier / keyword
172        if ch.is_alphabetic() || ch == '_' {
173            let mut val = String::new();
174            while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
175                val.push(chars[pos]);
176                pos += 1; col += 1;
177            }
178            // Check for assert!/assert_eq!
179            if (val == "assert" || val == "assert_eq") && pos < chars.len() && chars[pos] == '!' {
180                pos += 1; col += 1;
181                let tok = if val == "assert" { TokenType::AssertMacro } else { TokenType::AssertEqMacro };
182                tokens.push(Token { typ: tok, line: l, col: c });
183                continue;
184            }
185            let tok = match val.as_str() {
186                "use" => TokenType::Use,
187                "struct" => TokenType::Struct,
188                "impl" => TokenType::Impl,
189                "fn" => TokenType::Fn,
190                "pub" => TokenType::Pub,
191                "let" => TokenType::Let,
192                "mut" => TokenType::Mut,
193                "if" => TokenType::If,
194                "else" => TokenType::Else,
195                "for" => TokenType::For,
196                "return" => TokenType::Return,
197                "in" => TokenType::In,
198                "true" => TokenType::True,
199                "false" => TokenType::False,
200                "self" => TokenType::Self_,
201                _ => TokenType::Ident(val),
202            };
203            tokens.push(Token { typ: tok, line: l, col: c });
204            continue;
205        }
206
207        // String literal (double-quoted) — treated as hex ByteString like in TS/Sol/Move
208        if ch == '"' {
209            let mut val = String::new();
210            pos += 1; col += 1;
211            while pos < chars.len() && chars[pos] != '"' {
212                val.push(chars[pos]);
213                pos += 1; col += 1;
214            }
215            if pos < chars.len() { pos += 1; col += 1; } // skip closing quote
216            tokens.push(Token { typ: TokenType::HexString(val), line: l, col: c });
217            continue;
218        }
219
220        pos += 1; col += 1;
221    }
222
223    tokens.push(Token { typ: TokenType::Eof, line, col });
224    tokens
225}
226
227// ---------------------------------------------------------------------------
228// Parser
229// ---------------------------------------------------------------------------
230
231struct RustDslParser {
232    tokens: Vec<Token>,
233    pos: usize,
234    file: String,
235    errors: Vec<Diagnostic>,
236}
237
238impl RustDslParser {
239    fn new(tokens: Vec<Token>, file: String) -> Self {
240        Self { tokens, pos: 0, file, errors: Vec::new() }
241    }
242
243    fn current(&self) -> &Token {
244        self.tokens.get(self.pos).unwrap_or(self.tokens.last().unwrap())
245    }
246
247    fn advance_clone(&mut self) -> Token {
248        let t = self.current().clone();
249        if self.pos < self.tokens.len() - 1 { self.pos += 1; }
250        t
251    }
252
253    fn expect(&mut self, expected: &TokenType) {
254        if std::mem::discriminant(&self.current().typ) != std::mem::discriminant(expected) {
255            self.errors.push(Diagnostic::error(format!("Expected {:?}, got {:?} at {}:{}", expected, self.current().typ, self.current().line, self.current().col), None));
256        }
257        self.advance_clone();
258    }
259
260    fn match_tok(&mut self, expected: &TokenType) -> bool {
261        if std::mem::discriminant(&self.current().typ) == std::mem::discriminant(expected) {
262            self.advance_clone();
263            true
264        } else {
265            false
266        }
267    }
268
269    fn loc(&self) -> SourceLocation {
270        SourceLocation { file: self.file.clone(), line: self.current().line, column: self.current().col }
271    }
272
273    fn parse(mut self) -> ParseResult {
274        // Skip use declarations
275        while matches!(self.current().typ, TokenType::Use) {
276            while !matches!(self.current().typ, TokenType::Semi | TokenType::Eof) { self.advance_clone(); }
277            if matches!(self.current().typ, TokenType::Semi) { self.advance_clone(); }
278        }
279
280        // Look for #[runar::contract] struct
281        let mut properties: Vec<PropertyNode> = Vec::new();
282        let mut contract_name = String::new();
283        let mut parent_class = "SmartContract".to_string();
284        let mut methods: Vec<MethodNode> = Vec::new();
285
286        while !matches!(self.current().typ, TokenType::Eof) {
287            // Attribute: #[...]
288            if matches!(self.current().typ, TokenType::HashBracket) {
289                let attr = self.parse_attribute();
290
291                if attr == "runar::contract" || attr == "runar::stateful_contract" {
292                    if attr == "runar::stateful_contract" {
293                        parent_class = "StatefulSmartContract".to_string();
294                    }
295                    // Parse struct
296                    if matches!(self.current().typ, TokenType::Pub) { self.advance_clone(); }
297                    self.expect(&TokenType::Struct);
298                    if let TokenType::Ident(name) = self.current().typ.clone() {
299                        contract_name = name;
300                        self.advance_clone();
301                    }
302                    self.expect(&TokenType::LBrace);
303
304                    while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
305                        // Check for #[readonly] attribute
306                        let mut readonly = false;
307                        if matches!(self.current().typ, TokenType::HashBracket) {
308                            let field_attr = self.parse_attribute();
309                            if field_attr == "readonly" { readonly = true; }
310                        }
311
312                        let loc = self.loc();
313                        if let TokenType::Ident(field_name) = self.current().typ.clone() {
314                            self.advance_clone();
315                            self.expect(&TokenType::Colon);
316                            let field_type = self.parse_rust_type();
317                            self.match_tok(&TokenType::Comma);
318
319                            if !readonly {
320                                // Check later if any field is mutable
321                            }
322
323                            // Skip txPreimage — it's an implicit stateful param, not a contract property
324                            let camel_name = snake_to_camel(&field_name);
325                            if camel_name != "txPreimage" {
326                                properties.push(PropertyNode {
327                                    name: camel_name,
328                                    prop_type: field_type,
329                                    readonly,
330                                    initializer: None,
331                                    source_location: loc,
332                                });
333                            }
334                        } else {
335                            self.advance_clone();
336                        }
337                    }
338                    self.expect(&TokenType::RBrace);
339                } else if attr.starts_with("runar::methods") {
340                    // Parse impl block
341                    if matches!(self.current().typ, TokenType::Impl) { self.advance_clone(); }
342                    // Skip type name
343                    if let TokenType::Ident(_) = self.current().typ { self.advance_clone(); }
344                    self.expect(&TokenType::LBrace);
345
346                    while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
347                        // Check for #[public] attribute
348                        let mut visibility = Visibility::Private;
349                        if matches!(self.current().typ, TokenType::HashBracket) {
350                            let method_attr = self.parse_attribute();
351                            if method_attr == "public" { visibility = Visibility::Public; }
352                        }
353                        if matches!(self.current().typ, TokenType::Pub) {
354                            self.advance_clone();
355                            visibility = Visibility::Public;
356                        }
357                        methods.push(self.parse_function(visibility));
358                    }
359                    self.expect(&TokenType::RBrace);
360                } else {
361                    // Unknown attribute, skip
362                    continue;
363                }
364            } else {
365                self.advance_clone();
366            }
367        }
368
369        // Determine parent class from property mutability
370        if properties.iter().any(|p| !p.readonly) {
371            parent_class = "StatefulSmartContract".to_string();
372        }
373
374        if contract_name.is_empty() {
375            self.errors.push(Diagnostic::error("No Rúnar contract struct found", None));
376            return ParseResult { contract: None, errors: self.errors };
377        }
378
379        // Extract init() method as property initializers, if present.
380        // init() is a special private method that sets default values on properties.
381        let mut final_methods = Vec::new();
382        for m in methods {
383            if m.name == "init" && m.params.is_empty() {
384                for stmt in &m.body {
385                    if let Statement::Assignment { target, value, .. } = stmt {
386                        if let Expression::PropertyAccess { property } = target {
387                            for p in properties.iter_mut() {
388                                if p.name == *property {
389                                    p.initializer = Some(value.clone());
390                                    break;
391                                }
392                            }
393                        }
394                    }
395                }
396            } else {
397                final_methods.push(m);
398            }
399        }
400        let methods = final_methods;
401
402        // Build constructor (only non-initialized properties)
403        let uninit_props: Vec<&PropertyNode> = properties.iter()
404            .filter(|p| p.initializer.is_none())
405            .collect();
406
407        let loc = SourceLocation { file: self.file.clone(), line: 1, column: 1 };
408
409        // super(...) call as first statement
410        let super_args: Vec<Expression> = uninit_props.iter()
411            .map(|p| Expression::Identifier { name: p.name.clone() })
412            .collect();
413        let super_call = Statement::ExpressionStatement {
414            expression: Expression::CallExpr {
415                callee: Box::new(Expression::Identifier { name: "super".to_string() }),
416                args: super_args,
417            },
418            source_location: loc.clone(),
419        };
420
421        // Property assignments
422        let mut ctor_body = vec![super_call];
423        for p in &uninit_props {
424            ctor_body.push(Statement::Assignment {
425                target: Expression::PropertyAccess { property: p.name.clone() },
426                value: Expression::Identifier { name: p.name.clone() },
427                source_location: loc.clone(),
428            });
429        }
430
431        let constructor = MethodNode {
432            name: "constructor".to_string(),
433            params: uninit_props.iter().map(|p| ParamNode {
434                name: p.name.clone(),
435                param_type: p.prop_type.clone(),
436            }).collect(),
437            body: ctor_body,
438            visibility: Visibility::Public,
439            source_location: loc,
440        };
441
442        let contract = ContractNode {
443            name: contract_name,
444            parent_class,
445            properties,
446            constructor,
447            methods,
448            source_file: self.file.clone(),
449        };
450
451        ParseResult { contract: Some(contract), errors: self.errors }
452    }
453
454    fn parse_attribute(&mut self) -> String {
455        // Already consumed #[
456        self.advance_clone(); // skip #[
457        let mut attr = String::new();
458        let mut depth = 1;
459        while depth > 0 && !matches!(self.current().typ, TokenType::Eof) {
460            match &self.current().typ {
461                TokenType::LBracket => { depth += 1; self.advance_clone(); }
462                TokenType::RBracket => {
463                    depth -= 1;
464                    if depth == 0 { self.advance_clone(); break; }
465                    self.advance_clone();
466                }
467                TokenType::Ident(name) => { attr.push_str(name); self.advance_clone(); }
468                TokenType::ColonColon => { attr.push_str("::"); self.advance_clone(); }
469                TokenType::LParen => { attr.push('('); self.advance_clone(); }
470                TokenType::RParen => { attr.push(')'); self.advance_clone(); }
471                _ => { self.advance_clone(); }
472            }
473        }
474        attr
475    }
476
477    fn parse_rust_type(&mut self) -> TypeNode {
478        if let TokenType::Ident(name) = self.current().typ.clone() {
479            self.advance_clone();
480            let mapped = map_rust_type(&name);
481            if let Some(prim) = PrimitiveTypeName::from_str(&mapped) {
482                TypeNode::Primitive(prim)
483            } else {
484                TypeNode::Custom(mapped)
485            }
486        } else {
487            self.advance_clone();
488            TypeNode::Custom("unknown".to_string())
489        }
490    }
491
492    fn parse_function(&mut self, visibility: Visibility) -> MethodNode {
493        let loc = self.loc();
494        self.expect(&TokenType::Fn);
495
496        let raw_name = if let TokenType::Ident(name) = self.current().typ.clone() {
497            self.advance_clone();
498            name
499        } else {
500            self.advance_clone();
501            "unknown".to_string()
502        };
503        let name = snake_to_camel(&raw_name);
504
505        self.expect(&TokenType::LParen);
506        let mut params: Vec<ParamNode> = Vec::new();
507
508        while !matches!(self.current().typ, TokenType::RParen | TokenType::Eof) {
509            // Skip &self, &mut self
510            if matches!(self.current().typ, TokenType::Amp) {
511                self.advance_clone();
512                if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
513                if matches!(self.current().typ, TokenType::Self_) {
514                    self.advance_clone();
515                    if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
516                    continue;
517                }
518            }
519            if matches!(self.current().typ, TokenType::Self_) {
520                self.advance_clone();
521                if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
522                continue;
523            }
524
525            if let TokenType::Ident(param_name) = self.current().typ.clone() {
526                self.advance_clone();
527                self.expect(&TokenType::Colon);
528                // Skip & and &mut before type
529                if matches!(self.current().typ, TokenType::Amp) {
530                    self.advance_clone();
531                    if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
532                }
533                let param_type = self.parse_rust_type();
534                params.push(ParamNode {
535                    name: snake_to_camel(&param_name),
536                    param_type,
537                });
538            } else {
539                self.advance_clone();
540            }
541            if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
542        }
543        self.expect(&TokenType::RParen);
544
545        // Optional return type
546        if matches!(self.current().typ, TokenType::Arrow) {
547            self.advance_clone();
548            self.parse_rust_type();
549        }
550
551        self.expect(&TokenType::LBrace);
552        let mut body: Vec<Statement> = Vec::new();
553        while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
554            if let Some(stmt) = self.parse_statement() {
555                body.push(stmt);
556            }
557        }
558        self.expect(&TokenType::RBrace);
559
560        MethodNode { name, params, body, visibility, source_location: loc }
561    }
562
563    fn parse_statement(&mut self) -> Option<Statement> {
564        let loc = self.loc();
565
566        // assert!(expr)
567        if matches!(self.current().typ, TokenType::AssertMacro) {
568            self.advance_clone();
569            self.expect(&TokenType::LParen);
570            let expr = self.parse_expression();
571            self.expect(&TokenType::RParen);
572            self.match_tok(&TokenType::Semi);
573            return Some(Statement::ExpressionStatement {
574                expression: Expression::CallExpr {
575                    callee: Box::new(Expression::Identifier { name: "assert".to_string() }),
576                    args: vec![expr],
577                },
578                source_location: loc,
579            });
580        }
581
582        // assert_eq!(a, b)
583        if matches!(self.current().typ, TokenType::AssertEqMacro) {
584            self.advance_clone();
585            self.expect(&TokenType::LParen);
586            let left = self.parse_expression();
587            self.expect(&TokenType::Comma);
588            let right = self.parse_expression();
589            self.expect(&TokenType::RParen);
590            self.match_tok(&TokenType::Semi);
591            return Some(Statement::ExpressionStatement {
592                expression: Expression::CallExpr {
593                    callee: Box::new(Expression::Identifier { name: "assert".to_string() }),
594                    args: vec![Expression::BinaryExpr {
595                        op: BinaryOp::StrictEq,
596                        left: Box::new(left),
597                        right: Box::new(right),
598                    }],
599                },
600                source_location: loc,
601            });
602        }
603
604        // let [mut] name [: type] = expr;
605        if matches!(self.current().typ, TokenType::Let) {
606            self.advance_clone();
607            let mutable = self.match_tok(&TokenType::Mut);
608            let var_name = if let TokenType::Ident(name) = self.current().typ.clone() {
609                self.advance_clone();
610                snake_to_camel(&name)
611            } else {
612                self.advance_clone();
613                "unknown".to_string()
614            };
615            let var_type = if matches!(self.current().typ, TokenType::Colon) {
616                self.advance_clone();
617                if matches!(self.current().typ, TokenType::Amp) { self.advance_clone(); }
618                if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
619                Some(self.parse_rust_type())
620            } else {
621                None
622            };
623            self.expect(&TokenType::Eq);
624            let init = self.parse_expression();
625            self.match_tok(&TokenType::Semi);
626            return Some(Statement::VariableDecl {
627                name: var_name, var_type, mutable, init, source_location: loc,
628            });
629        }
630
631        // if
632        if matches!(self.current().typ, TokenType::If) {
633            self.advance_clone();
634            let condition = self.parse_expression();
635            self.expect(&TokenType::LBrace);
636            let mut then_branch = Vec::new();
637            while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
638                if let Some(s) = self.parse_statement() { then_branch.push(s); }
639            }
640            self.expect(&TokenType::RBrace);
641            let else_branch = if matches!(self.current().typ, TokenType::Else) {
642                self.advance_clone();
643                self.expect(&TokenType::LBrace);
644                let mut eb = Vec::new();
645                while !matches!(self.current().typ, TokenType::RBrace | TokenType::Eof) {
646                    if let Some(s) = self.parse_statement() { eb.push(s); }
647                }
648                self.expect(&TokenType::RBrace);
649                Some(eb)
650            } else {
651                None
652            };
653            return Some(Statement::IfStatement { condition, then_branch, else_branch, source_location: loc });
654        }
655
656        // return
657        if matches!(self.current().typ, TokenType::Return) {
658            self.advance_clone();
659            let value = if !matches!(self.current().typ, TokenType::Semi | TokenType::RBrace) {
660                Some(self.parse_expression())
661            } else {
662                None
663            };
664            self.match_tok(&TokenType::Semi);
665            return Some(Statement::ReturnStatement { value, source_location: loc });
666        }
667
668        // Expression statement
669        let expr = self.parse_expression();
670
671        // Assignment
672        if matches!(self.current().typ, TokenType::Eq) {
673            self.advance_clone();
674            let value = self.parse_expression();
675            self.match_tok(&TokenType::Semi);
676            return Some(Statement::Assignment {
677                target: self.convert_self_access(expr),
678                value,
679                source_location: loc,
680            });
681        }
682
683        // Compound assignments
684        if matches!(self.current().typ, TokenType::PlusEq) {
685            self.advance_clone();
686            let rhs = self.parse_expression();
687            self.match_tok(&TokenType::Semi);
688            let target = self.convert_self_access(expr.clone());
689            return Some(Statement::Assignment {
690                target: target.clone(),
691                value: Expression::BinaryExpr { op: BinaryOp::Add, left: Box::new(target), right: Box::new(rhs) },
692                source_location: loc,
693            });
694        }
695        if matches!(self.current().typ, TokenType::MinusEq) {
696            self.advance_clone();
697            let rhs = self.parse_expression();
698            self.match_tok(&TokenType::Semi);
699            let target = self.convert_self_access(expr.clone());
700            return Some(Statement::Assignment {
701                target: target.clone(),
702                value: Expression::BinaryExpr { op: BinaryOp::Sub, left: Box::new(target), right: Box::new(rhs) },
703                source_location: loc,
704            });
705        }
706
707        let had_semi = self.match_tok(&TokenType::Semi);
708        // Implicit return: expression without semicolon followed immediately by `}`
709        if !had_semi && matches!(self.current().typ, TokenType::RBrace) {
710            return Some(Statement::ReturnStatement { value: Some(expr), source_location: loc });
711        }
712        Some(Statement::ExpressionStatement { expression: expr, source_location: loc })
713    }
714
715    fn convert_self_access(&self, expr: Expression) -> Expression {
716        if let Expression::MemberExpr { ref object, ref property } = expr {
717            if let Expression::Identifier { ref name } = **object {
718                if name == "self" {
719                    return Expression::PropertyAccess { property: snake_to_camel(property) };
720                }
721            }
722        }
723        expr
724    }
725
726    // Expression parsing with precedence climbing
727    fn parse_expression(&mut self) -> Expression { self.parse_or() }
728
729    fn parse_or(&mut self) -> Expression {
730        let mut left = self.parse_and();
731        while matches!(self.current().typ, TokenType::PipePipe) {
732            self.advance_clone();
733            let right = self.parse_and();
734            left = Expression::BinaryExpr { op: BinaryOp::Or, left: Box::new(left), right: Box::new(right) };
735        }
736        left
737    }
738
739    fn parse_and(&mut self) -> Expression {
740        let mut left = self.parse_bit_or();
741        while matches!(self.current().typ, TokenType::AmpAmp) {
742            self.advance_clone();
743            let right = self.parse_bit_or();
744            left = Expression::BinaryExpr { op: BinaryOp::And, left: Box::new(left), right: Box::new(right) };
745        }
746        left
747    }
748
749    fn parse_bit_or(&mut self) -> Expression {
750        let mut left = self.parse_bit_xor();
751        while matches!(self.current().typ, TokenType::Pipe) {
752            self.advance_clone();
753            left = Expression::BinaryExpr { op: BinaryOp::BitOr, left: Box::new(left), right: Box::new(self.parse_bit_xor()) };
754        }
755        left
756    }
757
758    fn parse_bit_xor(&mut self) -> Expression {
759        let mut left = self.parse_bit_and();
760        while matches!(self.current().typ, TokenType::Caret) {
761            self.advance_clone();
762            left = Expression::BinaryExpr { op: BinaryOp::BitXor, left: Box::new(left), right: Box::new(self.parse_bit_and()) };
763        }
764        left
765    }
766
767    fn parse_bit_and(&mut self) -> Expression {
768        let mut left = self.parse_equality();
769        while matches!(self.current().typ, TokenType::Amp) {
770            self.advance_clone();
771            left = Expression::BinaryExpr { op: BinaryOp::BitAnd, left: Box::new(left), right: Box::new(self.parse_equality()) };
772        }
773        left
774    }
775
776    fn parse_equality(&mut self) -> Expression {
777        let mut left = self.parse_comparison();
778        loop {
779            let op = match self.current().typ {
780                TokenType::EqEq => BinaryOp::StrictEq,
781                TokenType::BangEq => BinaryOp::StrictNe,
782                _ => break,
783            };
784            self.advance_clone();
785            left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_comparison()) };
786        }
787        left
788    }
789
790    fn parse_comparison(&mut self) -> Expression {
791        let mut left = self.parse_add_sub();
792        loop {
793            let op = match self.current().typ {
794                TokenType::Lt => BinaryOp::Lt,
795                TokenType::LtEq => BinaryOp::Le,
796                TokenType::Gt => BinaryOp::Gt,
797                TokenType::GtEq => BinaryOp::Ge,
798                _ => break,
799            };
800            self.advance_clone();
801            left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_add_sub()) };
802        }
803        left
804    }
805
806    fn parse_add_sub(&mut self) -> Expression {
807        let mut left = self.parse_mul_div();
808        loop {
809            let op = match self.current().typ {
810                TokenType::Plus => BinaryOp::Add,
811                TokenType::Minus => BinaryOp::Sub,
812                _ => break,
813            };
814            self.advance_clone();
815            left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_mul_div()) };
816        }
817        left
818    }
819
820    fn parse_mul_div(&mut self) -> Expression {
821        let mut left = self.parse_unary();
822        loop {
823            let op = match self.current().typ {
824                TokenType::Star => BinaryOp::Mul,
825                TokenType::Slash => BinaryOp::Div,
826                TokenType::Percent => BinaryOp::Mod,
827                _ => break,
828            };
829            self.advance_clone();
830            left = Expression::BinaryExpr { op, left: Box::new(left), right: Box::new(self.parse_unary()) };
831        }
832        left
833    }
834
835    fn parse_unary(&mut self) -> Expression {
836        match self.current().typ {
837            TokenType::Bang => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::Not, operand: Box::new(self.parse_unary()) } }
838            TokenType::Minus => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::Neg, operand: Box::new(self.parse_unary()) } }
839            TokenType::Tilde => { self.advance_clone(); Expression::UnaryExpr { op: UnaryOp::BitNot, operand: Box::new(self.parse_unary()) } }
840            TokenType::Amp => {
841                self.advance_clone();
842                if matches!(self.current().typ, TokenType::Mut) { self.advance_clone(); }
843                self.parse_postfix()
844            }
845            _ => self.parse_postfix(),
846        }
847    }
848
849    fn parse_postfix(&mut self) -> Expression {
850        let mut expr = self.parse_primary();
851        loop {
852            if matches!(self.current().typ, TokenType::LParen) {
853                self.advance_clone();
854                let mut args = Vec::new();
855                while !matches!(self.current().typ, TokenType::RParen | TokenType::Eof) {
856                    args.push(self.parse_expression());
857                    if matches!(self.current().typ, TokenType::Comma) { self.advance_clone(); }
858                }
859                self.expect(&TokenType::RParen);
860                expr = Expression::CallExpr { callee: Box::new(expr), args };
861            } else if matches!(self.current().typ, TokenType::Dot) {
862                self.advance_clone();
863                let prop = if let TokenType::Ident(name) = self.current().typ.clone() {
864                    self.advance_clone();
865                    snake_to_camel(&name)
866                } else {
867                    self.advance_clone();
868                    "unknown".to_string()
869                };
870                // self.field -> PropertyAccess
871                if let Expression::Identifier { ref name } = expr {
872                    if name == "self" {
873                        expr = Expression::PropertyAccess { property: prop };
874                        continue;
875                    }
876                }
877                expr = Expression::MemberExpr { object: Box::new(expr), property: prop };
878            } else if matches!(self.current().typ, TokenType::ColonColon) {
879                self.advance_clone();
880                if let TokenType::Ident(name) = self.current().typ.clone() {
881                    self.advance_clone();
882                    expr = Expression::Identifier { name: snake_to_camel(&name) };
883                }
884            } else if matches!(self.current().typ, TokenType::LBracket) {
885                self.advance_clone();
886                let index = self.parse_expression();
887                self.expect(&TokenType::RBracket);
888                expr = Expression::IndexAccess { object: Box::new(expr), index: Box::new(index) };
889            } else {
890                break;
891            }
892        }
893        expr
894    }
895
896    fn parse_primary(&mut self) -> Expression {
897        match self.current().typ.clone() {
898            TokenType::Number(val) => {
899                self.advance_clone();
900                let n: i64 = val.parse().unwrap_or(0);
901                Expression::BigIntLiteral { value: n }
902            }
903            TokenType::HexString(val) => {
904                self.advance_clone();
905                Expression::ByteStringLiteral { value: val }
906            }
907            TokenType::True => { self.advance_clone(); Expression::BoolLiteral { value: true } }
908            TokenType::False => { self.advance_clone(); Expression::BoolLiteral { value: false } }
909            TokenType::Self_ => {
910                self.advance_clone();
911                Expression::Identifier { name: "self".to_string() }
912            }
913            TokenType::LParen => {
914                self.advance_clone();
915                let expr = self.parse_expression();
916                self.expect(&TokenType::RParen);
917                expr
918            }
919            TokenType::Ident(name) => {
920                self.advance_clone();
921                let mapped = map_rust_builtin(&name);
922                Expression::Identifier { name: mapped }
923            }
924            _ => {
925                let tok = self.current().clone();
926                self.advance_clone();
927                self.errors.push(Diagnostic::error(format!(
928                    "unsupported token '{:?}' at {}:{} — not valid in Rúnar contract",
929                    tok.typ, tok.line, tok.col), None));
930                Expression::Identifier { name: "unknown".to_string() }
931            }
932        }
933    }
934}
935
936// ---------------------------------------------------------------------------
937// Helpers
938// ---------------------------------------------------------------------------
939
940fn snake_to_camel(name: &str) -> String {
941    let parts: Vec<&str> = name.split('_').collect();
942    if parts.len() <= 1 {
943        return name.to_string();
944    }
945    let mut result = parts[0].to_string();
946    for part in &parts[1..] {
947        if !part.is_empty() {
948            let mut chars = part.chars();
949            if let Some(first) = chars.next() {
950                result.push(first.to_uppercase().next().unwrap());
951                result.extend(chars);
952            }
953        }
954    }
955    result
956}
957
958fn map_rust_type(name: &str) -> String {
959    match name {
960        "Bigint" | "Int" | "i64" | "u64" | "i128" | "u128" => "bigint".to_string(),
961        "Bool" | "bool" => "boolean".to_string(),
962        _ => name.to_string(),
963    }
964}
965
966fn map_rust_builtin(name: &str) -> String {
967    // Handle names that snake_to_camel can't produce correctly BEFORE conversion.
968    // These have acronyms, digit boundaries, or non-standard mappings.
969    match name {
970        "bool_cast" => return "bool".to_string(),
971        "verify_wots" => return "verifyWOTS".to_string(),
972        "verify_slh_dsa_sha2_128s" => return "verifySLHDSA_SHA2_128s".to_string(),
973        "verify_slh_dsa_sha2_128f" => return "verifySLHDSA_SHA2_128f".to_string(),
974        "verify_slh_dsa_sha2_192s" => return "verifySLHDSA_SHA2_192s".to_string(),
975        "verify_slh_dsa_sha2_192f" => return "verifySLHDSA_SHA2_192f".to_string(),
976        "verify_slh_dsa_sha2_256s" => return "verifySLHDSA_SHA2_256s".to_string(),
977        "verify_slh_dsa_sha2_256f" => return "verifySLHDSA_SHA2_256f".to_string(),
978        "bin_2_num" => return "bin2num".to_string(),
979        "int_2_str" => return "int2str".to_string(),
980        "to_byte_string" => return "toByteString".to_string(),
981        _ => {}
982    }
983
984    let camel = snake_to_camel(name);
985    // Map specific Rust builtins that snake_to_camel handles correctly
986    // but we list explicitly for clarity and stability
987    match camel.as_str() {
988        "hash160" => "hash160".to_string(),
989        "hash256" => "hash256".to_string(),
990        "sha256" => "sha256".to_string(),
991        "ripemd160" => "ripemd160".to_string(),
992        "checkSig" => "checkSig".to_string(),
993        "checkMultiSig" => "checkMultiSig".to_string(),
994        "checkPreimage" => "checkPreimage".to_string(),
995        "verifyRabinSig" => "verifyRabinSig".to_string(),
996        "num2bin" => "num2bin".to_string(),
997        "bin2num" => "bin2num".to_string(),
998        "int2str" => "int2str".to_string(),
999        "extractLocktime" => "extractLocktime".to_string(),
1000        "extractOutputHash" => "extractOutputHash".to_string(),
1001        "extractVersion" => "extractVersion".to_string(),
1002        "extractHashPrevouts" => "extractHashPrevouts".to_string(),
1003        "extractHashSequence" => "extractHashSequence".to_string(),
1004        "extractOutpoint" => "extractOutpoint".to_string(),
1005        "extractInputIndex" => "extractInputIndex".to_string(),
1006        "extractScriptCode" => "extractScriptCode".to_string(),
1007        "extractAmount" => "extractAmount".to_string(),
1008        "extractSequence" => "extractSequence".to_string(),
1009        "extractOutputs" => "extractOutputs".to_string(),
1010        "extractSigHashType" => "extractSigHashType".to_string(),
1011        "addOutput" => "addOutput".to_string(),
1012        "reverseBytes" => "reverseBytes".to_string(),
1013        "toByteString" => "toByteString".to_string(),
1014        _ => camel,
1015    }
1016}
1017
1018// ---------------------------------------------------------------------------
1019// Public API
1020// ---------------------------------------------------------------------------
1021
1022pub fn parse_rust_dsl(source: &str, file_name: Option<&str>) -> ParseResult {
1023    let file = file_name.unwrap_or("contract.runar.rs").to_string();
1024    let tokens = tokenize(source);
1025    let parser = RustDslParser::new(tokens, file);
1026    parser.parse()
1027}
1028
1029// ---------------------------------------------------------------------------
1030// Tests
1031// ---------------------------------------------------------------------------
1032
1033#[cfg(test)]
1034mod tests {
1035    use super::*;
1036
1037    #[test]
1038    fn test_parse_basic_rust_contract() {
1039        let source = r#"
1040use runar::prelude::*;
1041
1042#[runar::contract]
1043pub struct P2PKH {
1044    pub_key_hash: Addr,
1045}
1046
1047#[runar::methods]
1048impl P2PKH {
1049    #[public]
1050    fn unlock(&self, sig: Sig, pub_key: PubKey) {
1051        assert!(hash160(pub_key) == self.pub_key_hash);
1052        assert!(check_sig(sig, pub_key));
1053    }
1054}
1055"#;
1056
1057        let result = parse_rust_dsl(source, Some("P2PKH.runar.rs"));
1058        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1059        let contract = result.contract.unwrap();
1060        assert_eq!(contract.name, "P2PKH");
1061        assert_eq!(contract.properties.len(), 1);
1062        assert_eq!(contract.methods.len(), 1);
1063        assert_eq!(contract.methods[0].name, "unlock");
1064        assert_eq!(contract.methods[0].visibility, Visibility::Public);
1065        // self param should be excluded
1066        assert_eq!(contract.methods[0].params.len(), 2);
1067    }
1068
1069    #[test]
1070    fn test_snake_to_camel_conversion() {
1071        assert_eq!(snake_to_camel("pub_key_hash"), "pubKeyHash");
1072        assert_eq!(snake_to_camel("check_sig"), "checkSig");
1073        assert_eq!(snake_to_camel("already"), "already");
1074        assert_eq!(snake_to_camel("a_b_c"), "aBC");
1075        assert_eq!(snake_to_camel("hello_world"), "helloWorld");
1076    }
1077
1078    #[test]
1079    fn test_type_mapping_works() {
1080        // i64 -> bigint
1081        assert_eq!(map_rust_type("i64"), "bigint");
1082        assert_eq!(map_rust_type("u64"), "bigint");
1083        assert_eq!(map_rust_type("i128"), "bigint");
1084        assert_eq!(map_rust_type("u128"), "bigint");
1085        assert_eq!(map_rust_type("Bigint"), "bigint");
1086        // bool -> boolean
1087        assert_eq!(map_rust_type("bool"), "boolean");
1088        assert_eq!(map_rust_type("Bool"), "boolean");
1089        // Pass-through
1090        assert_eq!(map_rust_type("PubKey"), "PubKey");
1091        assert_eq!(map_rust_type("Sig"), "Sig");
1092        assert_eq!(map_rust_type("Addr"), "Addr");
1093    }
1094
1095    #[test]
1096    fn test_public_attribute_makes_method_public() {
1097        let source = r#"
1098use runar::prelude::*;
1099
1100#[runar::contract]
1101pub struct Test {
1102    #[readonly]
1103    x: bigint,
1104}
1105
1106#[runar::methods]
1107impl Test {
1108    #[public]
1109    fn public_method(&self, v: i64) {
1110        assert!(v == self.x);
1111    }
1112
1113    fn private_method(&self, v: i64) -> i64 {
1114        return v + 1;
1115    }
1116}
1117"#;
1118
1119        let result = parse_rust_dsl(source, Some("Test.runar.rs"));
1120        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1121        let contract = result.contract.unwrap();
1122        assert_eq!(contract.methods.len(), 2);
1123
1124        // First method should be public (has #[public])
1125        assert_eq!(contract.methods[0].name, "publicMethod");
1126        assert_eq!(contract.methods[0].visibility, Visibility::Public);
1127
1128        // Second method should be private (no #[public])
1129        assert_eq!(contract.methods[1].name, "privateMethod");
1130        assert_eq!(contract.methods[1].visibility, Visibility::Private);
1131    }
1132
1133    #[test]
1134    fn test_contract_name_extracted_correctly() {
1135        let source = r#"
1136use runar::prelude::*;
1137
1138#[runar::contract]
1139pub struct MyFancyContract {
1140    value: bigint,
1141}
1142
1143#[runar::methods]
1144impl MyFancyContract {
1145    #[public]
1146    fn check(&self, v: i64) {
1147        assert!(v == self.value);
1148    }
1149}
1150"#;
1151
1152        let result = parse_rust_dsl(source, Some("MyFancyContract.runar.rs"));
1153        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1154        let contract = result.contract.unwrap();
1155        assert_eq!(contract.name, "MyFancyContract");
1156    }
1157
1158    #[test]
1159    fn test_property_names_are_camel_cased() {
1160        let source = r#"
1161use runar::prelude::*;
1162
1163#[runar::contract]
1164pub struct Test {
1165    pub_key_hash: Addr,
1166    my_value: bigint,
1167}
1168
1169#[runar::methods]
1170impl Test {
1171    #[public]
1172    fn check(&self, v: i64) {
1173        assert!(v == self.my_value);
1174    }
1175}
1176"#;
1177
1178        let result = parse_rust_dsl(source, Some("Test.runar.rs"));
1179        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1180        let contract = result.contract.unwrap();
1181        assert_eq!(contract.properties[0].name, "pubKeyHash");
1182        assert_eq!(contract.properties[1].name, "myValue");
1183    }
1184
1185    #[test]
1186    fn test_stateful_contract_attribute() {
1187        let source = r#"
1188use runar::prelude::*;
1189
1190#[runar::stateful_contract]
1191pub struct Counter {
1192    count: bigint,
1193}
1194
1195#[runar::methods]
1196impl Counter {
1197    #[public]
1198    fn increment(&mut self) {
1199        self.count = self.count + 1;
1200    }
1201}
1202"#;
1203
1204        let result = parse_rust_dsl(source, Some("Counter.runar.rs"));
1205        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1206        let contract = result.contract.unwrap();
1207        assert_eq!(contract.name, "Counter");
1208        assert_eq!(contract.parent_class, "StatefulSmartContract");
1209    }
1210
1211    #[test]
1212    fn test_constructor_auto_generated() {
1213        let source = r#"
1214use runar::prelude::*;
1215
1216#[runar::contract]
1217pub struct Test {
1218    a: bigint,
1219    b: PubKey,
1220}
1221
1222#[runar::methods]
1223impl Test {
1224    #[public]
1225    fn check(&self) {
1226        assert!(self.a > 0);
1227    }
1228}
1229"#;
1230
1231        let result = parse_rust_dsl(source, None);
1232        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1233        let contract = result.contract.unwrap();
1234        // Constructor should have params for each property
1235        assert_eq!(contract.constructor.params.len(), 2);
1236        // Constructor body: super(a, b) + this.a = a + this.b = b
1237        assert_eq!(contract.constructor.body.len(), 3);
1238    }
1239
1240    #[test]
1241    fn test_assert_eq_macro_maps_to_assert_strict_eq() {
1242        let source = r#"
1243use runar::prelude::*;
1244
1245#[runar::contract]
1246pub struct Test {
1247    #[readonly]
1248    x: bigint,
1249}
1250
1251#[runar::methods]
1252impl Test {
1253    #[public]
1254    fn check(&self, v: i64) {
1255        assert_eq!(self.x, v);
1256    }
1257}
1258"#;
1259
1260        let result = parse_rust_dsl(source, Some("Test.runar.rs"));
1261        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1262        let contract = result.contract.unwrap();
1263        let body = &contract.methods[0].body;
1264        assert_eq!(body.len(), 1);
1265
1266        // Should be assert(self.x === v)
1267        if let Statement::ExpressionStatement { expression, .. } = &body[0] {
1268            if let Expression::CallExpr { callee, args } = expression {
1269                if let Expression::Identifier { name } = callee.as_ref() {
1270                    assert_eq!(name, "assert");
1271                }
1272                if let Expression::BinaryExpr { op, .. } = &args[0] {
1273                    assert_eq!(*op, BinaryOp::StrictEq);
1274                } else {
1275                    panic!("Expected BinaryExpr inside assert_eq!, got {:?}", args[0]);
1276                }
1277            }
1278        }
1279    }
1280
1281    #[test]
1282    fn test_int_type_maps_to_bigint() {
1283        assert_eq!(map_rust_type("Int"), "bigint");
1284    }
1285
1286    #[test]
1287    fn test_bool_cast_maps_to_bool() {
1288        assert_eq!(map_rust_builtin("bool_cast"), "bool");
1289    }
1290
1291    #[test]
1292    fn test_verify_wots_mapping() {
1293        assert_eq!(map_rust_builtin("verify_wots"), "verifyWOTS");
1294    }
1295
1296    #[test]
1297    fn test_verify_slh_dsa_mappings() {
1298        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_128s"), "verifySLHDSA_SHA2_128s");
1299        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_128f"), "verifySLHDSA_SHA2_128f");
1300        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_192s"), "verifySLHDSA_SHA2_192s");
1301        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_192f"), "verifySLHDSA_SHA2_192f");
1302        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_256s"), "verifySLHDSA_SHA2_256s");
1303        assert_eq!(map_rust_builtin("verify_slh_dsa_sha2_256f"), "verifySLHDSA_SHA2_256f");
1304    }
1305
1306    #[test]
1307    fn test_extract_builtin_mappings() {
1308        assert_eq!(map_rust_builtin("extract_version"), "extractVersion");
1309        assert_eq!(map_rust_builtin("extract_hash_prevouts"), "extractHashPrevouts");
1310        assert_eq!(map_rust_builtin("extract_hash_sequence"), "extractHashSequence");
1311        assert_eq!(map_rust_builtin("extract_outpoint"), "extractOutpoint");
1312        assert_eq!(map_rust_builtin("extract_input_index"), "extractInputIndex");
1313        assert_eq!(map_rust_builtin("extract_script_code"), "extractScriptCode");
1314        assert_eq!(map_rust_builtin("extract_amount"), "extractAmount");
1315        assert_eq!(map_rust_builtin("extract_sequence"), "extractSequence");
1316        assert_eq!(map_rust_builtin("extract_output_hash"), "extractOutputHash");
1317        assert_eq!(map_rust_builtin("extract_outputs"), "extractOutputs");
1318        assert_eq!(map_rust_builtin("extract_locktime"), "extractLocktime");
1319        assert_eq!(map_rust_builtin("extract_sig_hash_type"), "extractSigHashType");
1320    }
1321
1322    #[test]
1323    fn test_byte_operation_builtin_mappings() {
1324        assert_eq!(map_rust_builtin("reverse_bytes"), "reverseBytes");
1325        assert_eq!(map_rust_builtin("bin2num"), "bin2num");
1326        assert_eq!(map_rust_builtin("bin_2_num"), "bin2num");
1327        assert_eq!(map_rust_builtin("int2str"), "int2str");
1328        assert_eq!(map_rust_builtin("int_2_str"), "int2str");
1329        assert_eq!(map_rust_builtin("to_byte_string"), "toByteString");
1330        assert_eq!(map_rust_builtin("add_output"), "addOutput");
1331    }
1332
1333    #[test]
1334    fn test_implicit_return_in_method() {
1335        let source = r#"
1336use runar::prelude::*;
1337#[runar::contract]
1338pub struct Foo { #[readonly] pub x: Bigint }
1339#[runar::methods(Foo)]
1340impl Foo {
1341    fn compute(&self, a: Bigint, b: Bigint) -> Bigint {
1342        let sum = a + b;
1343        sum
1344    }
1345    #[public]
1346    pub fn check(&self) {
1347        assert!(self.x > 0);
1348    }
1349}
1350"#;
1351        let result = parse_rust_dsl(source, Some("Foo.runar.rs"));
1352        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
1353        let contract = result.contract.unwrap();
1354        let compute = contract.methods.iter().find(|m| m.name == "compute").unwrap();
1355        assert_eq!(compute.body.len(), 2, "expected 2 statements (let + return)");
1356        // Last statement should be a ReturnStatement
1357        match &compute.body[1] {
1358            Statement::ReturnStatement { value: Some(_), .. } => {}
1359            other => panic!("expected ReturnStatement, got {:?}", other),
1360        }
1361    }
1362}