Skip to main content

runar_compiler_rust/frontend/
parser_python.rs

1//! Python parser for Rúnar contracts (.runar.py).
2//!
3//! Parses Python-style contract definitions using a hand-written tokenizer
4//! with INDENT/DEDENT tokens and recursive descent parser.
5//! Produces the same AST as the TypeScript parser.
6//!
7//! ## Expected format
8//!
9//! ```python
10//! from runar import SmartContract, Addr, Sig, PubKey, public, assert_, hash160, check_sig
11//!
12//! class P2PKH(SmartContract):
13//!     pub_key_hash: Addr
14//!
15//!     def __init__(self, pub_key_hash: Addr):
16//!         super().__init__(pub_key_hash)
17//!         self.pub_key_hash = pub_key_hash
18//!
19//!     @public
20//!     def unlock(self, sig: Sig, pub_key: PubKey):
21//!         assert_(hash160(pub_key) == self.pub_key_hash)
22//!         assert_(check_sig(sig, pub_key))
23//! ```
24//!
25//! Key mappings:
26//! - `class Foo(SmartContract):` -> contract
27//! - `@public` decorator -> Visibility::Public
28//! - `self.prop` -> PropertyAccess (like `this.prop`)
29//! - `assert_(expr)` or `assert expr` -> assert(expr)
30//! - `//` integer division -> Div in AST (OP_DIV)
31//! - `and`/`or`/`not` -> And/Or/Not operators
32//! - `==`/`!=` -> StrictEq/StrictNe
33//! - `Readonly[T]` -> readonly property
34//! - `for i in range(n):` -> ForStatement
35//! - snake_case identifiers -> camelCase in AST
36
37use super::ast::{
38    BinaryOp, ContractNode, Expression, MethodNode, ParamNode, PrimitiveTypeName, PropertyNode,
39    SourceLocation, Statement, TypeNode, UnaryOp, Visibility,
40};
41use super::diagnostic::Diagnostic;
42use super::parser::ParseResult;
43
44// ---------------------------------------------------------------------------
45// Public API
46// ---------------------------------------------------------------------------
47
48/// Parse a Python-format Rúnar contract source.
49pub fn parse_python(source: &str, file_name: Option<&str>) -> ParseResult {
50    let file = file_name.unwrap_or("contract.runar.py");
51    let mut errors: Vec<Diagnostic> = Vec::new();
52
53    let tokens = tokenize(source);
54    let mut parser = PyParser::new(tokens, file, &mut errors);
55
56    let contract = parser.parse_contract();
57
58    ParseResult { contract, errors }
59}
60
61// ---------------------------------------------------------------------------
62// Name conversion helpers
63// ---------------------------------------------------------------------------
64
65/// Convert snake_case to camelCase. Single words pass through unchanged.
66/// Strips trailing underscore (e.g. `sum_` -> `sum`, `assert_` -> `assert`).
67fn snake_to_camel(name: &str) -> String {
68    // Strip trailing underscore (e.g. assert_ -> assert)
69    let n = if name.ends_with('_') && name != "_" {
70        &name[..name.len() - 1]
71    } else {
72        name
73    };
74
75    let mut result = String::new();
76    let mut capitalize_next = false;
77
78    for ch in n.chars() {
79        if ch == '_' {
80            capitalize_next = true;
81        } else if capitalize_next {
82            result.push(ch.to_ascii_uppercase());
83            capitalize_next = false;
84        } else {
85            result.push(ch);
86        }
87    }
88
89    result
90}
91
92/// Map Python builtin function names to Rúnar AST callee names.
93fn map_builtin_name(name: &str) -> String {
94    // Exact-match special cases (names that don't follow simple snake_case -> camelCase)
95    match name {
96        "assert_" => return "assert".to_string(),
97        "verify_wots" => return "verifyWOTS".to_string(),
98        "verify_slh_dsa_sha2_128s" => return "verifySLHDSA_SHA2_128s".to_string(),
99        "verify_slh_dsa_sha2_128f" => return "verifySLHDSA_SHA2_128f".to_string(),
100        "verify_slh_dsa_sha2_192s" => return "verifySLHDSA_SHA2_192s".to_string(),
101        "verify_slh_dsa_sha2_192f" => return "verifySLHDSA_SHA2_192f".to_string(),
102        "verify_slh_dsa_sha2_256s" => return "verifySLHDSA_SHA2_256s".to_string(),
103        "verify_slh_dsa_sha2_256f" => return "verifySLHDSA_SHA2_256f".to_string(),
104        "verify_rabin_sig" => return "verifyRabinSig".to_string(),
105        "check_sig" => return "checkSig".to_string(),
106        "check_multi_sig" => return "checkMultiSig".to_string(),
107        "check_preimage" => return "checkPreimage".to_string(),
108        "hash160" => return "hash160".to_string(),
109        "hash256" => return "hash256".to_string(),
110        "sha256" => return "sha256".to_string(),
111        "ripemd160" => return "ripemd160".to_string(),
112        "num2bin" => return "num2bin".to_string(),
113        "reverse_bytes" => return "reverseBytes".to_string(),
114        "extract_locktime" => return "extractLocktime".to_string(),
115        "extract_output_hash" => return "extractOutputHash".to_string(),
116        "extract_amount" => return "extractAmount".to_string(),
117        "extract_version" => return "extractVersion".to_string(),
118        "extract_sequence" => return "extractSequence".to_string(),
119        "ec_add" => return "ecAdd".to_string(),
120        "ec_mul" => return "ecMul".to_string(),
121        "ec_mul_gen" => return "ecMulGen".to_string(),
122        "ec_negate" => return "ecNegate".to_string(),
123        "ec_on_curve" => return "ecOnCurve".to_string(),
124        "ec_mod_reduce" => return "ecModReduce".to_string(),
125        "ec_encode_compressed" => return "ecEncodeCompressed".to_string(),
126        "ec_make_point" => return "ecMakePoint".to_string(),
127        "ec_point_x" => return "ecPointX".to_string(),
128        "ec_point_y" => return "ecPointY".to_string(),
129        "mul_div" => return "mulDiv".to_string(),
130        "percent_of" => return "percentOf".to_string(),
131        "add_output" => return "addOutput".to_string(),
132        "get_state_script" => return "getStateScript".to_string(),
133        _ => {}
134    }
135
136    // Names that pass through unchanged
137    match name {
138        "bool" | "abs" | "min" | "max" | "len" | "pow" | "cat" | "within" | "safediv"
139        | "safemod" | "clamp" | "sign" | "sqrt" | "gcd" | "divmod" | "log2" | "substr" => {
140            return name.to_string();
141        }
142        _ => {}
143    }
144
145    // Default: snake_case -> camelCase
146    snake_to_camel(name)
147}
148
149/// Map Python type names to Rúnar AST types.
150fn map_py_type(name: &str) -> &str {
151    match name {
152        "Bigint" | "int" | "Int" => "bigint",
153        "bool" => "boolean",
154        "ByteString" | "bytes" => "ByteString",
155        "PubKey" => "PubKey",
156        "Sig" => "Sig",
157        "Addr" => "Addr",
158        "Sha256" => "Sha256",
159        "Ripemd160" => "Ripemd160",
160        "SigHashPreimage" => "SigHashPreimage",
161        "RabinSig" => "RabinSig",
162        "RabinPubKey" => "RabinPubKey",
163        "Point" => "Point",
164        _ => name,
165    }
166}
167
168// ---------------------------------------------------------------------------
169// Tokenizer
170// ---------------------------------------------------------------------------
171
172#[derive(Debug, Clone, PartialEq)]
173enum Token {
174    // Keywords
175    Class,
176    Def,
177    If,
178    Elif,
179    Else,
180    For,
181    In,
182    Range,
183    Return,
184    Pass,
185    TrueLit,
186    FalseLit,
187    NoneLit,
188    And,
189    Or,
190    Not,
191    SelfKw,
192    Super,
193    From,
194    Import,
195    Assert,
196
197    // Identifiers and literals
198    Ident(String),
199    NumberLit(i64),
200    HexStringLit(String),
201    StringLit(String),
202
203    // Decorators
204    At,
205
206    // Operators
207    Plus,
208    Minus,
209    Star,
210    Slash,
211    IntDiv,       // //
212    Percent,
213    DoubleStar,   // **
214    EqEq,         // ==
215    NotEq,        // !=
216    Lt,
217    Le,
218    Gt,
219    Ge,
220    LShift,       // <<
221    RShift,       // >>
222    BitAnd,       // &
223    BitOr,        // |
224    BitXor,       // ^
225    Tilde,        // ~
226    Bang,         // !
227    Eq,           // =
228    PlusEq,       // +=
229    MinusEq,      // -=
230    StarEq,       // *=
231    SlashEq,      // /=
232    IntDivEq,     // //=
233    PercentEq,    // %=
234    Arrow,        // ->
235
236    // Delimiters
237    LParen,
238    RParen,
239    LBracket,
240    RBracket,
241    Colon,
242    Comma,
243    Dot,
244
245    // Indentation
246    Indent,
247    Dedent,
248    Newline,
249
250    // Special
251    Eof,
252}
253
254fn tokenize(source: &str) -> Vec<Token> {
255    let mut tokens = Vec::new();
256    let lines: Vec<&str> = source.split('\n').collect();
257    let mut indent_stack: Vec<usize> = vec![0];
258    let mut paren_depth: usize = 0;
259
260    for raw_line in &lines {
261        // Strip trailing \r
262        let line = if raw_line.ends_with('\r') {
263            &raw_line[..raw_line.len() - 1]
264        } else {
265            raw_line
266        };
267
268        // Skip blank lines and comment-only lines (they don't affect indentation)
269        let stripped = line.trim_start();
270        if stripped.is_empty() || stripped.starts_with('#') {
271            continue;
272        }
273
274        let chars: Vec<char> = line.chars().collect();
275
276        // Compute indent level (only at paren depth 0)
277        if paren_depth == 0 {
278            let mut indent = 0usize;
279            for &ch in &chars {
280                if ch == ' ' {
281                    indent += 1;
282                } else if ch == '\t' {
283                    indent += 4;
284                } else {
285                    break;
286                }
287            }
288
289            let current_indent = *indent_stack.last().unwrap();
290            if indent > current_indent {
291                indent_stack.push(indent);
292                tokens.push(Token::Indent);
293            } else if indent < current_indent {
294                while indent_stack.len() > 1 && *indent_stack.last().unwrap() > indent {
295                    indent_stack.pop();
296                    tokens.push(Token::Dedent);
297                }
298            }
299        }
300
301        // Tokenize the content of this line
302        let start_offset = chars.len() - stripped.len();
303        let mut pos = start_offset;
304
305        while pos < chars.len() {
306            let ch = chars[pos];
307
308            // Whitespace within line
309            if ch == ' ' || ch == '\t' {
310                pos += 1;
311                continue;
312            }
313
314            // Comment
315            if ch == '#' {
316                break; // rest of line is comment
317            }
318
319            // Decorators
320            if ch == '@' {
321                pos += 1;
322                tokens.push(Token::At);
323                continue;
324            }
325
326            // Three-char operators: //=
327            if ch == '/'
328                && pos + 2 < chars.len()
329                && chars[pos + 1] == '/'
330                && chars[pos + 2] == '='
331            {
332                tokens.push(Token::IntDivEq);
333                pos += 3;
334                continue;
335            }
336
337            // Two-char operators: ** // == != <= >= << >> += -= *= /= %= ->
338            if ch == '*' && pos + 1 < chars.len() && chars[pos + 1] == '*' {
339                tokens.push(Token::DoubleStar);
340                pos += 2;
341                continue;
342            }
343            if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '/' {
344                tokens.push(Token::IntDiv);
345                pos += 2;
346                continue;
347            }
348            if ch == '=' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
349                tokens.push(Token::EqEq);
350                pos += 2;
351                continue;
352            }
353            if ch == '!' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
354                tokens.push(Token::NotEq);
355                pos += 2;
356                continue;
357            }
358            if ch == '<' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
359                tokens.push(Token::Le);
360                pos += 2;
361                continue;
362            }
363            if ch == '>' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
364                tokens.push(Token::Ge);
365                pos += 2;
366                continue;
367            }
368            if ch == '<' && pos + 1 < chars.len() && chars[pos + 1] == '<' {
369                tokens.push(Token::LShift);
370                pos += 2;
371                continue;
372            }
373            if ch == '>' && pos + 1 < chars.len() && chars[pos + 1] == '>' {
374                tokens.push(Token::RShift);
375                pos += 2;
376                continue;
377            }
378            if ch == '+' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
379                tokens.push(Token::PlusEq);
380                pos += 2;
381                continue;
382            }
383            if ch == '-' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
384                tokens.push(Token::MinusEq);
385                pos += 2;
386                continue;
387            }
388            if ch == '*' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
389                tokens.push(Token::StarEq);
390                pos += 2;
391                continue;
392            }
393            if ch == '/' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
394                tokens.push(Token::SlashEq);
395                pos += 2;
396                continue;
397            }
398            if ch == '%' && pos + 1 < chars.len() && chars[pos + 1] == '=' {
399                tokens.push(Token::PercentEq);
400                pos += 2;
401                continue;
402            }
403            if ch == '-' && pos + 1 < chars.len() && chars[pos + 1] == '>' {
404                tokens.push(Token::Arrow);
405                pos += 2;
406                continue;
407            }
408
409            // Parentheses (track depth for multi-line expressions)
410            if ch == '(' {
411                paren_depth += 1;
412                tokens.push(Token::LParen);
413                pos += 1;
414                continue;
415            }
416            if ch == ')' {
417                if paren_depth > 0 {
418                    paren_depth -= 1;
419                }
420                tokens.push(Token::RParen);
421                pos += 1;
422                continue;
423            }
424            if ch == '[' {
425                paren_depth += 1;
426                tokens.push(Token::LBracket);
427                pos += 1;
428                continue;
429            }
430            if ch == ']' {
431                if paren_depth > 0 {
432                    paren_depth -= 1;
433                }
434                tokens.push(Token::RBracket);
435                pos += 1;
436                continue;
437            }
438
439            // Single-char operators & delimiters
440            match ch {
441                '+' => {
442                    tokens.push(Token::Plus);
443                    pos += 1;
444                    continue;
445                }
446                '-' => {
447                    tokens.push(Token::Minus);
448                    pos += 1;
449                    continue;
450                }
451                '*' => {
452                    tokens.push(Token::Star);
453                    pos += 1;
454                    continue;
455                }
456                '/' => {
457                    tokens.push(Token::Slash);
458                    pos += 1;
459                    continue;
460                }
461                '%' => {
462                    tokens.push(Token::Percent);
463                    pos += 1;
464                    continue;
465                }
466                '<' => {
467                    tokens.push(Token::Lt);
468                    pos += 1;
469                    continue;
470                }
471                '>' => {
472                    tokens.push(Token::Gt);
473                    pos += 1;
474                    continue;
475                }
476                '!' => {
477                    tokens.push(Token::Bang);
478                    pos += 1;
479                    continue;
480                }
481                '~' => {
482                    tokens.push(Token::Tilde);
483                    pos += 1;
484                    continue;
485                }
486                '&' => {
487                    tokens.push(Token::BitAnd);
488                    pos += 1;
489                    continue;
490                }
491                '|' => {
492                    tokens.push(Token::BitOr);
493                    pos += 1;
494                    continue;
495                }
496                '^' => {
497                    tokens.push(Token::BitXor);
498                    pos += 1;
499                    continue;
500                }
501                '=' => {
502                    tokens.push(Token::Eq);
503                    pos += 1;
504                    continue;
505                }
506                ':' => {
507                    tokens.push(Token::Colon);
508                    pos += 1;
509                    continue;
510                }
511                ',' => {
512                    tokens.push(Token::Comma);
513                    pos += 1;
514                    continue;
515                }
516                '.' => {
517                    tokens.push(Token::Dot);
518                    pos += 1;
519                    continue;
520                }
521                _ => {}
522            }
523
524            // Hex byte string: b'\xde\xad' or b"\xde\xad"
525            if ch == 'b'
526                && pos + 1 < chars.len()
527                && (chars[pos + 1] == '\'' || chars[pos + 1] == '"')
528            {
529                let quote = chars[pos + 1];
530                pos += 2; // skip b and opening quote
531                let mut hex = String::new();
532                while pos < chars.len() && chars[pos] != quote {
533                    if chars[pos] == '\\'
534                        && pos + 1 < chars.len()
535                        && chars[pos + 1] == 'x'
536                        && pos + 3 < chars.len()
537                    {
538                        // \xHH
539                        hex.push(chars[pos + 2]);
540                        hex.push(chars[pos + 3]);
541                        pos += 4;
542                    } else {
543                        // Non-hex byte -- encode as hex
544                        let byte = chars[pos] as u32;
545                        hex.push_str(&format!("{:02x}", byte));
546                        pos += 1;
547                    }
548                }
549                if pos < chars.len() {
550                    pos += 1; // skip closing quote
551                }
552                tokens.push(Token::HexStringLit(hex));
553                continue;
554            }
555
556            // String literals (single or double quoted)
557            if ch == '\'' || ch == '"' {
558                let quote = ch;
559                pos += 1;
560                let mut val = String::new();
561                while pos < chars.len() && chars[pos] != quote {
562                    if chars[pos] == '\\' && pos + 1 < chars.len() {
563                        pos += 1; // skip backslash
564                        val.push(chars[pos]);
565                        pos += 1;
566                    } else {
567                        val.push(chars[pos]);
568                        pos += 1;
569                    }
570                }
571                if pos < chars.len() {
572                    pos += 1; // skip closing quote
573                }
574                tokens.push(Token::StringLit(val));
575                continue;
576            }
577
578            // Numbers (decimal and hex)
579            if ch.is_ascii_digit() {
580                let mut num_str = String::new();
581                if ch == '0'
582                    && pos + 1 < chars.len()
583                    && (chars[pos + 1] == 'x' || chars[pos + 1] == 'X')
584                {
585                    // Hex number
586                    num_str.push_str("0x");
587                    pos += 2;
588                    while pos < chars.len()
589                        && (chars[pos].is_ascii_hexdigit() || chars[pos] == '_')
590                    {
591                        if chars[pos] != '_' {
592                            num_str.push(chars[pos]);
593                        }
594                        pos += 1;
595                    }
596                } else {
597                    while pos < chars.len()
598                        && (chars[pos].is_ascii_digit() || chars[pos] == '_')
599                    {
600                        if chars[pos] != '_' {
601                            num_str.push(chars[pos]);
602                        }
603                        pos += 1;
604                    }
605                }
606                let val = if num_str.starts_with("0x") || num_str.starts_with("0X") {
607                    i64::from_str_radix(&num_str[2..], 16).unwrap_or(0)
608                } else {
609                    num_str.parse::<i64>().unwrap_or(0)
610                };
611                tokens.push(Token::NumberLit(val));
612                continue;
613            }
614
615            // Identifiers and keywords
616            if ch.is_ascii_alphabetic() || ch == '_' {
617                let start = pos;
618                while pos < chars.len() && (chars[pos].is_ascii_alphanumeric() || chars[pos] == '_')
619                {
620                    pos += 1;
621                }
622                let word: String = chars[start..pos].iter().collect();
623                let tok = match word.as_str() {
624                    "class" => Token::Class,
625                    "def" => Token::Def,
626                    "if" => Token::If,
627                    "elif" => Token::Elif,
628                    "else" => Token::Else,
629                    "for" => Token::For,
630                    "in" => Token::In,
631                    "range" => Token::Range,
632                    "return" => Token::Return,
633                    "pass" => Token::Pass,
634                    "True" => Token::TrueLit,
635                    "False" => Token::FalseLit,
636                    "None" => Token::NoneLit,
637                    "and" => Token::And,
638                    "or" => Token::Or,
639                    "not" => Token::Not,
640                    "self" => Token::SelfKw,
641                    "super" => Token::Super,
642                    "from" => Token::From,
643                    "import" => Token::Import,
644                    "assert" => Token::Assert,
645                    _ => Token::Ident(word),
646                };
647                tokens.push(tok);
648                continue;
649            }
650
651            // Skip unrecognized characters
652            pos += 1;
653        }
654
655        // Emit NEWLINE at end of significant line (only if not inside parens)
656        if paren_depth == 0 {
657            tokens.push(Token::Newline);
658        }
659    }
660
661    // Emit remaining DEDENTs
662    while indent_stack.len() > 1 {
663        indent_stack.pop();
664        tokens.push(Token::Dedent);
665    }
666
667    tokens.push(Token::Eof);
668    tokens
669}
670
671// ---------------------------------------------------------------------------
672// Parser
673// ---------------------------------------------------------------------------
674
675struct PyParser<'a> {
676    tokens: Vec<Token>,
677    pos: usize,
678    file: &'a str,
679    errors: &'a mut Vec<Diagnostic>,
680}
681
682impl<'a> PyParser<'a> {
683    fn new(tokens: Vec<Token>, file: &'a str, errors: &'a mut Vec<Diagnostic>) -> Self {
684        Self {
685            tokens,
686            pos: 0,
687            file,
688            errors,
689        }
690    }
691
692    fn peek(&self) -> &Token {
693        self.tokens.get(self.pos).unwrap_or(&Token::Eof)
694    }
695
696    fn advance(&mut self) -> Token {
697        let t = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof);
698        self.pos += 1;
699        t
700    }
701
702    fn expect(&mut self, expected: &Token) -> bool {
703        if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
704            self.advance();
705            true
706        } else {
707            self.errors.push(Diagnostic::error(format!(
708                "Expected {:?}, got {:?}",
709                expected,
710                self.peek()
711            ), None));
712            false
713        }
714    }
715
716    fn match_tok(&mut self, expected: &Token) -> bool {
717        if std::mem::discriminant(self.peek()) == std::mem::discriminant(expected) {
718            self.advance();
719            true
720        } else {
721            false
722        }
723    }
724
725    fn expect_ident(&mut self) -> String {
726        match self.advance() {
727            Token::Ident(name) => name,
728            other => {
729                self.errors
730                    .push(Diagnostic::error(format!("Expected identifier, got {:?}", other), None));
731                "_error".to_string()
732            }
733        }
734    }
735
736    fn loc(&self) -> SourceLocation {
737        SourceLocation {
738            file: self.file.to_string(),
739            line: 1,
740            column: 0,
741        }
742    }
743
744    /// Skip NEWLINE tokens.
745    fn skip_newlines(&mut self) {
746        while *self.peek() == Token::Newline {
747            self.advance();
748        }
749    }
750
751    // -----------------------------------------------------------------------
752    // Top-level parsing
753    // -----------------------------------------------------------------------
754
755    fn parse_contract(&mut self) -> Option<ContractNode> {
756        self.skip_newlines();
757
758        // Skip `from runar import ...` lines
759        while *self.peek() == Token::From || *self.peek() == Token::Import {
760            self.parse_import_line();
761            self.skip_newlines();
762        }
763
764        self.skip_newlines();
765
766        if *self.peek() != Token::Class {
767            self.errors
768                .push(Diagnostic::error("Expected 'class' declaration", None));
769            return None;
770        }
771
772        self.advance(); // consume 'class'
773        let contract_name = self.expect_ident();
774        self.expect(&Token::LParen);
775        let parent_class = self.expect_ident();
776        self.expect(&Token::RParen);
777        self.expect(&Token::Colon);
778        self.skip_newlines();
779        self.expect(&Token::Indent);
780        self.skip_newlines();
781
782        if parent_class != "SmartContract" && parent_class != "StatefulSmartContract" {
783            self.errors.push(Diagnostic::error(format!(
784                "Unknown parent class: {}",
785                parent_class
786            ), None));
787            return None;
788        }
789
790        let mut properties = Vec::new();
791        let mut methods = Vec::new();
792        let mut constructor: Option<MethodNode> = None;
793
794        while *self.peek() != Token::Dedent && *self.peek() != Token::Eof {
795            self.skip_newlines();
796            if *self.peek() == Token::Dedent || *self.peek() == Token::Eof {
797                break;
798            }
799
800            // Decorators
801            let mut decorators: Vec<String> = Vec::new();
802            while *self.peek() == Token::At {
803                self.advance(); // '@'
804                let dec_name = match self.advance() {
805                    Token::Ident(name) => name,
806                    other => {
807                        self.errors
808                            .push(Diagnostic::error(format!("Expected decorator name, got {:?}", other), None));
809                        String::new()
810                    }
811                };
812                decorators.push(dec_name);
813                self.skip_newlines();
814            }
815
816            // Method definition
817            if *self.peek() == Token::Def {
818                let method = self.parse_method_def(&decorators);
819                if method.name == "constructor" {
820                    constructor = Some(method);
821                } else {
822                    methods.push(method);
823                }
824                self.skip_newlines();
825                continue;
826            }
827
828            // Property: name: Type
829            if let Token::Ident(_) = self.peek().clone() {
830                if let Some(prop) = self.parse_property(&parent_class) {
831                    properties.push(prop);
832                }
833                self.skip_newlines();
834                continue;
835            }
836
837            // Skip unknown tokens
838            self.advance();
839        }
840
841        self.match_tok(&Token::Dedent);
842
843        // Auto-generate constructor if not provided
844        let constructor = constructor
845            .unwrap_or_else(|| build_constructor(&properties, self.file));
846
847        Some(ContractNode {
848            name: contract_name,
849            parent_class,
850            properties,
851            constructor,
852            methods,
853            source_file: self.file.to_string(),
854        })
855    }
856
857    fn parse_import_line(&mut self) {
858        // from X import Y, Z, ...
859        // or: import X
860        if *self.peek() == Token::From {
861            self.advance(); // 'from'
862            // consume module path
863            while *self.peek() != Token::Import
864                && *self.peek() != Token::Newline
865                && *self.peek() != Token::Eof
866            {
867                self.advance();
868            }
869            if self.match_tok(&Token::Import) {
870                // consume imported names
871                while *self.peek() != Token::Newline && *self.peek() != Token::Eof {
872                    self.advance();
873                }
874            }
875        } else if *self.peek() == Token::Import {
876            self.advance();
877            while *self.peek() != Token::Newline && *self.peek() != Token::Eof {
878                self.advance();
879            }
880        }
881        self.skip_newlines();
882    }
883
884    // -----------------------------------------------------------------------
885    // Properties
886    // -----------------------------------------------------------------------
887
888    fn parse_property(&mut self, parent_class: &str) -> Option<PropertyNode> {
889        let raw_name = self.expect_ident();
890
891        if *self.peek() != Token::Colon {
892            // Not a property -- might be a stray identifier; skip the line
893            while *self.peek() != Token::Newline && *self.peek() != Token::Eof {
894                self.advance();
895            }
896            return None;
897        }
898        self.advance(); // consume ':'
899
900        // Parse type (possibly Readonly[T])
901        let mut is_readonly = false;
902        let type_node;
903
904        if let Token::Ident(ref name) = *self.peek() {
905            if name == "Readonly" {
906                is_readonly = true;
907                self.advance(); // consume 'Readonly'
908                self.expect(&Token::LBracket);
909                type_node = self.parse_type();
910                self.expect(&Token::RBracket);
911            } else {
912                type_node = self.parse_type();
913            }
914        } else {
915            type_node = self.parse_type();
916        }
917
918        // In stateless contracts, all properties are readonly
919        if parent_class == "SmartContract" {
920            is_readonly = true;
921        }
922
923        // Parse optional initializer: = value
924        let initializer = if *self.peek() == Token::Eq {
925            self.advance(); // consume '='
926            Some(self.parse_expression())
927        } else {
928            None
929        };
930
931        // Skip rest of line
932        while *self.peek() != Token::Newline
933            && *self.peek() != Token::Eof
934            && *self.peek() != Token::Dedent
935        {
936            self.advance();
937        }
938
939        Some(PropertyNode {
940            name: snake_to_camel(&raw_name),
941            prop_type: type_node,
942            readonly: is_readonly,
943            initializer,
944            source_location: self.loc(),
945        })
946    }
947
948    // -----------------------------------------------------------------------
949    // Types
950    // -----------------------------------------------------------------------
951
952    fn parse_type(&mut self) -> TypeNode {
953        let raw_name = self.expect_ident();
954
955        // Check for FixedArray[T, N]
956        if raw_name == "FixedArray" && *self.peek() == Token::LBracket {
957            self.advance(); // '['
958            let element = self.parse_type();
959            self.expect(&Token::Comma);
960            let length = match self.advance() {
961                Token::NumberLit(n) => n as usize,
962                _ => {
963                    self.errors
964                        .push(Diagnostic::error("FixedArray requires numeric length", None));
965                    0
966                }
967            };
968            self.expect(&Token::RBracket);
969            return TypeNode::FixedArray {
970                element: Box::new(element),
971                length,
972            };
973        }
974
975        let mapped = map_py_type(&raw_name);
976        if let Some(prim) = PrimitiveTypeName::from_str(mapped) {
977            TypeNode::Primitive(prim)
978        } else {
979            TypeNode::Custom(mapped.to_string())
980        }
981    }
982
983    // -----------------------------------------------------------------------
984    // Method definitions
985    // -----------------------------------------------------------------------
986
987    fn parse_method_def(&mut self, decorators: &[String]) -> MethodNode {
988        self.expect(&Token::Def);
989
990        let raw_name = match self.advance() {
991            Token::Ident(name) => name,
992            other => {
993                self.errors
994                    .push(Diagnostic::error(format!("Expected method name, got {:?}", other), None));
995                "_error".to_string()
996            }
997        };
998
999        self.expect(&Token::LParen);
1000        let params = self.parse_params();
1001        self.expect(&Token::RParen);
1002
1003        // Optional return type annotation: -> Type
1004        if self.match_tok(&Token::Arrow) {
1005            self.parse_type(); // consume and discard return type
1006        }
1007
1008        self.expect(&Token::Colon);
1009        self.skip_newlines();
1010        self.expect(&Token::Indent);
1011
1012        let body = self.parse_statements();
1013
1014        self.match_tok(&Token::Dedent);
1015
1016        // Determine if this is the constructor
1017        if raw_name == "__init__" {
1018            return MethodNode {
1019                name: "constructor".to_string(),
1020                params,
1021                body,
1022                visibility: Visibility::Public,
1023                source_location: self.loc(),
1024            };
1025        }
1026
1027        let is_public = decorators.contains(&"public".to_string());
1028        let method_name = snake_to_camel(&raw_name);
1029
1030        MethodNode {
1031            name: method_name,
1032            params,
1033            body,
1034            visibility: if is_public {
1035                Visibility::Public
1036            } else {
1037                Visibility::Private
1038            },
1039            source_location: self.loc(),
1040        }
1041    }
1042
1043    fn parse_params(&mut self) -> Vec<ParamNode> {
1044        let mut params = Vec::new();
1045
1046        while *self.peek() != Token::RParen && *self.peek() != Token::Eof {
1047            // Skip 'self' parameter
1048            if *self.peek() == Token::SelfKw {
1049                self.advance();
1050                if *self.peek() == Token::Comma {
1051                    self.advance();
1052                }
1053                continue;
1054            }
1055
1056            let raw_name = self.expect_ident();
1057
1058            let param_type = if self.match_tok(&Token::Colon) {
1059                self.parse_type()
1060            } else {
1061                TypeNode::Custom("unknown".to_string())
1062            };
1063
1064            params.push(ParamNode {
1065                name: snake_to_camel(&raw_name),
1066                param_type,
1067            });
1068
1069            if !self.match_tok(&Token::Comma) {
1070                break;
1071            }
1072        }
1073
1074        params
1075    }
1076
1077    // -----------------------------------------------------------------------
1078    // Statements
1079    // -----------------------------------------------------------------------
1080
1081    fn parse_statements(&mut self) -> Vec<Statement> {
1082        let mut stmts = Vec::new();
1083
1084        while *self.peek() != Token::Dedent && *self.peek() != Token::Eof {
1085            self.skip_newlines();
1086            if *self.peek() == Token::Dedent || *self.peek() == Token::Eof {
1087                break;
1088            }
1089
1090            if let Some(stmt) = self.parse_statement() {
1091                stmts.push(stmt);
1092            }
1093            self.skip_newlines();
1094        }
1095
1096        stmts
1097    }
1098
1099    fn parse_statement(&mut self) -> Option<Statement> {
1100        match self.peek().clone() {
1101            // assert statement: assert expr
1102            Token::Assert => Some(self.parse_assert_statement()),
1103
1104            // if statement
1105            Token::If => Some(self.parse_if_statement()),
1106
1107            // for statement
1108            Token::For => Some(self.parse_for_statement()),
1109
1110            // return statement
1111            Token::Return => Some(self.parse_return_statement()),
1112
1113            // pass statement
1114            Token::Pass => {
1115                self.advance();
1116                None
1117            }
1118
1119            // super().__init__(...) -- constructor super call
1120            Token::Super => Some(self.parse_super_call()),
1121
1122            // self.prop = expr (assignment to property) or self.method(...)
1123            Token::SelfKw => Some(self.parse_self_statement()),
1124
1125            // Variable declaration or expression statement
1126            Token::Ident(_) => Some(self.parse_ident_statement()),
1127
1128            _ => {
1129                self.advance();
1130                None
1131            }
1132        }
1133    }
1134
1135    fn parse_assert_statement(&mut self) -> Statement {
1136        self.advance(); // consume 'assert'
1137        let expr = self.parse_expression();
1138        Statement::ExpressionStatement {
1139            expression: Expression::CallExpr {
1140                callee: Box::new(Expression::Identifier {
1141                    name: "assert".to_string(),
1142                }),
1143                args: vec![expr],
1144            },
1145            source_location: self.loc(),
1146        }
1147    }
1148
1149    fn parse_if_statement(&mut self) -> Statement {
1150        self.advance(); // consume 'if'
1151        let condition = self.parse_expression();
1152        self.expect(&Token::Colon);
1153        self.skip_newlines();
1154        self.expect(&Token::Indent);
1155        let then_branch = self.parse_statements();
1156        self.match_tok(&Token::Dedent);
1157        self.skip_newlines();
1158
1159        let else_branch = if *self.peek() == Token::Elif {
1160            // elif -> else { if ... }
1161            Some(vec![self.parse_elif_statement()])
1162        } else if *self.peek() == Token::Else {
1163            self.advance(); // 'else'
1164            self.expect(&Token::Colon);
1165            self.skip_newlines();
1166            self.expect(&Token::Indent);
1167            let stmts = self.parse_statements();
1168            self.match_tok(&Token::Dedent);
1169            Some(stmts)
1170        } else {
1171            None
1172        };
1173
1174        Statement::IfStatement {
1175            condition,
1176            then_branch,
1177            else_branch,
1178            source_location: self.loc(),
1179        }
1180    }
1181
1182    fn parse_elif_statement(&mut self) -> Statement {
1183        self.advance(); // consume 'elif'
1184        let condition = self.parse_expression();
1185        self.expect(&Token::Colon);
1186        self.skip_newlines();
1187        self.expect(&Token::Indent);
1188        let then_branch = self.parse_statements();
1189        self.match_tok(&Token::Dedent);
1190        self.skip_newlines();
1191
1192        let else_branch = if *self.peek() == Token::Elif {
1193            Some(vec![self.parse_elif_statement()])
1194        } else if *self.peek() == Token::Else {
1195            self.advance();
1196            self.expect(&Token::Colon);
1197            self.skip_newlines();
1198            self.expect(&Token::Indent);
1199            let stmts = self.parse_statements();
1200            self.match_tok(&Token::Dedent);
1201            Some(stmts)
1202        } else {
1203            None
1204        };
1205
1206        Statement::IfStatement {
1207            condition,
1208            then_branch,
1209            else_branch,
1210            source_location: self.loc(),
1211        }
1212    }
1213
1214    fn parse_for_statement(&mut self) -> Statement {
1215        self.advance(); // consume 'for'
1216
1217        let raw_var = self.expect_ident();
1218        let var_name = snake_to_camel(&raw_var);
1219
1220        self.expect(&Token::In);
1221        self.expect(&Token::Range);
1222        self.expect(&Token::LParen);
1223
1224        // range(n) or range(a, b)
1225        let first_arg = self.parse_expression();
1226        let (start_expr, end_expr) = if self.match_tok(&Token::Comma) {
1227            let second_arg = self.parse_expression();
1228            (first_arg, second_arg)
1229        } else {
1230            (Expression::BigIntLiteral { value: 0 }, first_arg)
1231        };
1232
1233        self.expect(&Token::RParen);
1234        self.expect(&Token::Colon);
1235        self.skip_newlines();
1236        self.expect(&Token::Indent);
1237        let body = self.parse_statements();
1238        self.match_tok(&Token::Dedent);
1239
1240        // Construct a C-style for loop AST node:
1241        // for (let varName: bigint = startExpr; varName < endExpr; varName++)
1242        let init = Statement::VariableDecl {
1243            name: var_name.clone(),
1244            var_type: Some(TypeNode::Primitive(PrimitiveTypeName::Bigint)),
1245            mutable: true,
1246            init: start_expr,
1247            source_location: self.loc(),
1248        };
1249
1250        let condition = Expression::BinaryExpr {
1251            op: BinaryOp::Lt,
1252            left: Box::new(Expression::Identifier {
1253                name: var_name.clone(),
1254            }),
1255            right: Box::new(end_expr),
1256        };
1257
1258        let update = Statement::ExpressionStatement {
1259            expression: Expression::IncrementExpr {
1260                operand: Box::new(Expression::Identifier { name: var_name }),
1261                prefix: false,
1262            },
1263            source_location: self.loc(),
1264        };
1265
1266        Statement::ForStatement {
1267            init: Box::new(init),
1268            condition,
1269            update: Box::new(update),
1270            body,
1271            source_location: self.loc(),
1272        }
1273    }
1274
1275    fn parse_return_statement(&mut self) -> Statement {
1276        self.advance(); // consume 'return'
1277        let value = if *self.peek() != Token::Newline
1278            && *self.peek() != Token::Dedent
1279            && *self.peek() != Token::Eof
1280        {
1281            Some(self.parse_expression())
1282        } else {
1283            None
1284        };
1285        Statement::ReturnStatement {
1286            value,
1287            source_location: self.loc(),
1288        }
1289    }
1290
1291    fn parse_super_call(&mut self) -> Statement {
1292        // super().__init__(...) -> super(args) in AST
1293        self.advance(); // 'super'
1294        self.expect(&Token::LParen);
1295        self.expect(&Token::RParen);
1296        self.expect(&Token::Dot);
1297
1298        // Expect __init__
1299        let method_name = self.expect_ident();
1300        if method_name != "__init__" {
1301            self.errors.push(Diagnostic::error(format!(
1302                "Expected __init__ after super(), got '{}'",
1303                method_name
1304            ), None));
1305        }
1306
1307        self.expect(&Token::LParen);
1308        let mut args = Vec::new();
1309        while *self.peek() != Token::RParen && *self.peek() != Token::Eof {
1310            args.push(self.parse_expression());
1311            if !self.match_tok(&Token::Comma) {
1312                break;
1313            }
1314        }
1315        self.expect(&Token::RParen);
1316
1317        Statement::ExpressionStatement {
1318            expression: Expression::CallExpr {
1319                callee: Box::new(Expression::Identifier {
1320                    name: "super".to_string(),
1321                }),
1322                args,
1323            },
1324            source_location: self.loc(),
1325        }
1326    }
1327
1328    fn parse_self_statement(&mut self) -> Statement {
1329        // self.prop = expr  or  self.prop += expr  or  self.method(...)
1330        let expr = self.parse_expression();
1331
1332        // Simple assignment: self.x = expr
1333        if self.match_tok(&Token::Eq) {
1334            let value = self.parse_expression();
1335            return Statement::Assignment {
1336                target: expr,
1337                value,
1338                source_location: self.loc(),
1339            };
1340        }
1341
1342        // Compound assignments
1343        if *self.peek() == Token::PlusEq {
1344            self.advance();
1345            let rhs = self.parse_expression();
1346            let value = Expression::BinaryExpr {
1347                op: BinaryOp::Add,
1348                left: Box::new(expr.clone()),
1349                right: Box::new(rhs),
1350            };
1351            return Statement::Assignment {
1352                target: expr,
1353                value,
1354                source_location: self.loc(),
1355            };
1356        }
1357        if *self.peek() == Token::MinusEq {
1358            self.advance();
1359            let rhs = self.parse_expression();
1360            let value = Expression::BinaryExpr {
1361                op: BinaryOp::Sub,
1362                left: Box::new(expr.clone()),
1363                right: Box::new(rhs),
1364            };
1365            return Statement::Assignment {
1366                target: expr,
1367                value,
1368                source_location: self.loc(),
1369            };
1370        }
1371        if *self.peek() == Token::StarEq {
1372            self.advance();
1373            let rhs = self.parse_expression();
1374            let value = Expression::BinaryExpr {
1375                op: BinaryOp::Mul,
1376                left: Box::new(expr.clone()),
1377                right: Box::new(rhs),
1378            };
1379            return Statement::Assignment {
1380                target: expr,
1381                value,
1382                source_location: self.loc(),
1383            };
1384        }
1385        if *self.peek() == Token::SlashEq || *self.peek() == Token::IntDivEq {
1386            self.advance();
1387            let rhs = self.parse_expression();
1388            let value = Expression::BinaryExpr {
1389                op: BinaryOp::Div,
1390                left: Box::new(expr.clone()),
1391                right: Box::new(rhs),
1392            };
1393            return Statement::Assignment {
1394                target: expr,
1395                value,
1396                source_location: self.loc(),
1397            };
1398        }
1399        if *self.peek() == Token::PercentEq {
1400            self.advance();
1401            let rhs = self.parse_expression();
1402            let value = Expression::BinaryExpr {
1403                op: BinaryOp::Mod,
1404                left: Box::new(expr.clone()),
1405                right: Box::new(rhs),
1406            };
1407            return Statement::Assignment {
1408                target: expr,
1409                value,
1410                source_location: self.loc(),
1411            };
1412        }
1413
1414        // Expression statement (method call)
1415        Statement::ExpressionStatement {
1416            expression: expr,
1417            source_location: self.loc(),
1418        }
1419    }
1420
1421    fn parse_ident_statement(&mut self) -> Statement {
1422        // Could be:
1423        // 1. name: Type = expr  (variable declaration with type annotation)
1424        // 2. name = expr  (variable declaration without type)
1425        // 3. name(...)  (expression statement / function call)
1426        // 4. name += expr (compound assignment)
1427
1428        let raw_name = match self.peek().clone() {
1429            Token::Ident(ref name) => name.clone(),
1430            _ => "_error".to_string(),
1431        };
1432
1433        // Look ahead: if next token after ident is ':', it's a typed variable decl
1434        if self.tokens.get(self.pos + 1).map_or(false, |t| *t == Token::Colon) {
1435            self.advance(); // consume ident
1436            self.advance(); // consume ':'
1437
1438            // Check if it's Readonly[T] -- that would be a property, not a var decl,
1439            // but at statement level it means typed var decl with Readonly as a type name.
1440            let type_node = self.parse_type();
1441
1442            let init = if self.match_tok(&Token::Eq) {
1443                self.parse_expression()
1444            } else {
1445                Expression::BigIntLiteral { value: 0 }
1446            };
1447
1448            return Statement::VariableDecl {
1449                name: snake_to_camel(&raw_name),
1450                var_type: Some(type_node),
1451                mutable: true,
1452                init,
1453                source_location: self.loc(),
1454            };
1455        }
1456
1457        // Check for simple name = expr pattern (no type annotation)
1458        if self.tokens.get(self.pos + 1).map_or(false, |t| *t == Token::Eq) {
1459            self.advance(); // consume ident
1460            self.advance(); // consume '='
1461            let value = self.parse_expression();
1462            return Statement::VariableDecl {
1463                name: snake_to_camel(&raw_name),
1464                var_type: None,
1465                mutable: true,
1466                init: value,
1467                source_location: self.loc(),
1468            };
1469        }
1470
1471        // Parse as expression first
1472        let expr = self.parse_expression();
1473
1474        // Simple assignment (for a.b = expr)
1475        if self.match_tok(&Token::Eq) {
1476            let value = self.parse_expression();
1477            return Statement::Assignment {
1478                target: expr,
1479                value,
1480                source_location: self.loc(),
1481            };
1482        }
1483
1484        // Compound assignments
1485        if *self.peek() == Token::PlusEq {
1486            self.advance();
1487            let rhs = self.parse_expression();
1488            let value = Expression::BinaryExpr {
1489                op: BinaryOp::Add,
1490                left: Box::new(expr.clone()),
1491                right: Box::new(rhs),
1492            };
1493            return Statement::Assignment {
1494                target: expr,
1495                value,
1496                source_location: self.loc(),
1497            };
1498        }
1499        if *self.peek() == Token::MinusEq {
1500            self.advance();
1501            let rhs = self.parse_expression();
1502            let value = Expression::BinaryExpr {
1503                op: BinaryOp::Sub,
1504                left: Box::new(expr.clone()),
1505                right: Box::new(rhs),
1506            };
1507            return Statement::Assignment {
1508                target: expr,
1509                value,
1510                source_location: self.loc(),
1511            };
1512        }
1513        if *self.peek() == Token::StarEq {
1514            self.advance();
1515            let rhs = self.parse_expression();
1516            let value = Expression::BinaryExpr {
1517                op: BinaryOp::Mul,
1518                left: Box::new(expr.clone()),
1519                right: Box::new(rhs),
1520            };
1521            return Statement::Assignment {
1522                target: expr,
1523                value,
1524                source_location: self.loc(),
1525            };
1526        }
1527        if *self.peek() == Token::SlashEq || *self.peek() == Token::IntDivEq {
1528            self.advance();
1529            let rhs = self.parse_expression();
1530            let value = Expression::BinaryExpr {
1531                op: BinaryOp::Div,
1532                left: Box::new(expr.clone()),
1533                right: Box::new(rhs),
1534            };
1535            return Statement::Assignment {
1536                target: expr,
1537                value,
1538                source_location: self.loc(),
1539            };
1540        }
1541        if *self.peek() == Token::PercentEq {
1542            self.advance();
1543            let rhs = self.parse_expression();
1544            let value = Expression::BinaryExpr {
1545                op: BinaryOp::Mod,
1546                left: Box::new(expr.clone()),
1547                right: Box::new(rhs),
1548            };
1549            return Statement::Assignment {
1550                target: expr,
1551                value,
1552                source_location: self.loc(),
1553            };
1554        }
1555
1556        // Expression statement
1557        Statement::ExpressionStatement {
1558            expression: expr,
1559            source_location: self.loc(),
1560        }
1561    }
1562
1563    // -----------------------------------------------------------------------
1564    // Expressions (precedence climbing)
1565    // -----------------------------------------------------------------------
1566
1567    fn parse_expression(&mut self) -> Expression {
1568        self.parse_ternary()
1569    }
1570
1571    /// Python conditional expression: `x if cond else y`
1572    /// Parsed as postfix: parse or-expr first, then check for 'if'.
1573    fn parse_ternary(&mut self) -> Expression {
1574        let expr = self.parse_or();
1575
1576        if *self.peek() == Token::If {
1577            self.advance(); // 'if'
1578            let condition = self.parse_or();
1579            self.expect(&Token::Else);
1580            let alternate = self.parse_ternary();
1581            Expression::TernaryExpr {
1582                condition: Box::new(condition),
1583                consequent: Box::new(expr),
1584                alternate: Box::new(alternate),
1585            }
1586        } else {
1587            expr
1588        }
1589    }
1590
1591    fn parse_or(&mut self) -> Expression {
1592        let mut left = self.parse_and();
1593        while *self.peek() == Token::Or {
1594            self.advance();
1595            let right = self.parse_and();
1596            left = Expression::BinaryExpr {
1597                op: BinaryOp::Or,
1598                left: Box::new(left),
1599                right: Box::new(right),
1600            };
1601        }
1602        left
1603    }
1604
1605    fn parse_and(&mut self) -> Expression {
1606        let mut left = self.parse_not();
1607        while *self.peek() == Token::And {
1608            self.advance();
1609            let right = self.parse_not();
1610            left = Expression::BinaryExpr {
1611                op: BinaryOp::And,
1612                left: Box::new(left),
1613                right: Box::new(right),
1614            };
1615        }
1616        left
1617    }
1618
1619    fn parse_not(&mut self) -> Expression {
1620        if *self.peek() == Token::Not {
1621            self.advance();
1622            let operand = self.parse_not();
1623            Expression::UnaryExpr {
1624                op: UnaryOp::Not,
1625                operand: Box::new(operand),
1626            }
1627        } else {
1628            self.parse_bit_or()
1629        }
1630    }
1631
1632    fn parse_bit_or(&mut self) -> Expression {
1633        let mut left = self.parse_bit_xor();
1634        while *self.peek() == Token::BitOr {
1635            self.advance();
1636            let right = self.parse_bit_xor();
1637            left = Expression::BinaryExpr {
1638                op: BinaryOp::BitOr,
1639                left: Box::new(left),
1640                right: Box::new(right),
1641            };
1642        }
1643        left
1644    }
1645
1646    fn parse_bit_xor(&mut self) -> Expression {
1647        let mut left = self.parse_bit_and();
1648        while *self.peek() == Token::BitXor {
1649            self.advance();
1650            let right = self.parse_bit_and();
1651            left = Expression::BinaryExpr {
1652                op: BinaryOp::BitXor,
1653                left: Box::new(left),
1654                right: Box::new(right),
1655            };
1656        }
1657        left
1658    }
1659
1660    fn parse_bit_and(&mut self) -> Expression {
1661        let mut left = self.parse_equality();
1662        while *self.peek() == Token::BitAnd {
1663            self.advance();
1664            let right = self.parse_equality();
1665            left = Expression::BinaryExpr {
1666                op: BinaryOp::BitAnd,
1667                left: Box::new(left),
1668                right: Box::new(right),
1669            };
1670        }
1671        left
1672    }
1673
1674    fn parse_equality(&mut self) -> Expression {
1675        let mut left = self.parse_comparison();
1676        loop {
1677            match self.peek() {
1678                Token::EqEq => {
1679                    self.advance();
1680                    let right = self.parse_comparison();
1681                    // == maps to ===
1682                    left = Expression::BinaryExpr {
1683                        op: BinaryOp::StrictEq,
1684                        left: Box::new(left),
1685                        right: Box::new(right),
1686                    };
1687                }
1688                Token::NotEq => {
1689                    self.advance();
1690                    let right = self.parse_comparison();
1691                    // != maps to !==
1692                    left = Expression::BinaryExpr {
1693                        op: BinaryOp::StrictNe,
1694                        left: Box::new(left),
1695                        right: Box::new(right),
1696                    };
1697                }
1698                _ => break,
1699            }
1700        }
1701        left
1702    }
1703
1704    fn parse_comparison(&mut self) -> Expression {
1705        let mut left = self.parse_shift();
1706        loop {
1707            match self.peek() {
1708                Token::Lt => {
1709                    self.advance();
1710                    let right = self.parse_shift();
1711                    left = Expression::BinaryExpr {
1712                        op: BinaryOp::Lt,
1713                        left: Box::new(left),
1714                        right: Box::new(right),
1715                    };
1716                }
1717                Token::Le => {
1718                    self.advance();
1719                    let right = self.parse_shift();
1720                    left = Expression::BinaryExpr {
1721                        op: BinaryOp::Le,
1722                        left: Box::new(left),
1723                        right: Box::new(right),
1724                    };
1725                }
1726                Token::Gt => {
1727                    self.advance();
1728                    let right = self.parse_shift();
1729                    left = Expression::BinaryExpr {
1730                        op: BinaryOp::Gt,
1731                        left: Box::new(left),
1732                        right: Box::new(right),
1733                    };
1734                }
1735                Token::Ge => {
1736                    self.advance();
1737                    let right = self.parse_shift();
1738                    left = Expression::BinaryExpr {
1739                        op: BinaryOp::Ge,
1740                        left: Box::new(left),
1741                        right: Box::new(right),
1742                    };
1743                }
1744                _ => break,
1745            }
1746        }
1747        left
1748    }
1749
1750    fn parse_shift(&mut self) -> Expression {
1751        let mut left = self.parse_additive();
1752        loop {
1753            match self.peek() {
1754                Token::LShift => {
1755                    self.advance();
1756                    let right = self.parse_additive();
1757                    left = Expression::BinaryExpr {
1758                        op: BinaryOp::Shl,
1759                        left: Box::new(left),
1760                        right: Box::new(right),
1761                    };
1762                }
1763                Token::RShift => {
1764                    self.advance();
1765                    let right = self.parse_additive();
1766                    left = Expression::BinaryExpr {
1767                        op: BinaryOp::Shr,
1768                        left: Box::new(left),
1769                        right: Box::new(right),
1770                    };
1771                }
1772                _ => break,
1773            }
1774        }
1775        left
1776    }
1777
1778    fn parse_additive(&mut self) -> Expression {
1779        let mut left = self.parse_multiplicative();
1780        loop {
1781            match self.peek() {
1782                Token::Plus => {
1783                    self.advance();
1784                    let right = self.parse_multiplicative();
1785                    left = Expression::BinaryExpr {
1786                        op: BinaryOp::Add,
1787                        left: Box::new(left),
1788                        right: Box::new(right),
1789                    };
1790                }
1791                Token::Minus => {
1792                    self.advance();
1793                    let right = self.parse_multiplicative();
1794                    left = Expression::BinaryExpr {
1795                        op: BinaryOp::Sub,
1796                        left: Box::new(left),
1797                        right: Box::new(right),
1798                    };
1799                }
1800                _ => break,
1801            }
1802        }
1803        left
1804    }
1805
1806    fn parse_multiplicative(&mut self) -> Expression {
1807        let mut left = self.parse_unary();
1808        loop {
1809            match self.peek() {
1810                Token::Star => {
1811                    self.advance();
1812                    let right = self.parse_unary();
1813                    left = Expression::BinaryExpr {
1814                        op: BinaryOp::Mul,
1815                        left: Box::new(left),
1816                        right: Box::new(right),
1817                    };
1818                }
1819                Token::IntDiv => {
1820                    // Python integer division // maps to / in AST (OP_DIV)
1821                    self.advance();
1822                    let right = self.parse_unary();
1823                    left = Expression::BinaryExpr {
1824                        op: BinaryOp::Div,
1825                        left: Box::new(left),
1826                        right: Box::new(right),
1827                    };
1828                }
1829                Token::Slash => {
1830                    self.advance();
1831                    let right = self.parse_unary();
1832                    left = Expression::BinaryExpr {
1833                        op: BinaryOp::Div,
1834                        left: Box::new(left),
1835                        right: Box::new(right),
1836                    };
1837                }
1838                Token::Percent => {
1839                    self.advance();
1840                    let right = self.parse_unary();
1841                    left = Expression::BinaryExpr {
1842                        op: BinaryOp::Mod,
1843                        left: Box::new(left),
1844                        right: Box::new(right),
1845                    };
1846                }
1847                _ => break,
1848            }
1849        }
1850        left
1851    }
1852
1853    fn parse_unary(&mut self) -> Expression {
1854        match self.peek() {
1855            Token::Minus => {
1856                self.advance();
1857                let operand = self.parse_unary();
1858                Expression::UnaryExpr {
1859                    op: UnaryOp::Neg,
1860                    operand: Box::new(operand),
1861                }
1862            }
1863            Token::Tilde => {
1864                self.advance();
1865                let operand = self.parse_unary();
1866                Expression::UnaryExpr {
1867                    op: UnaryOp::BitNot,
1868                    operand: Box::new(operand),
1869                }
1870            }
1871            Token::Bang => {
1872                self.advance();
1873                let operand = self.parse_unary();
1874                Expression::UnaryExpr {
1875                    op: UnaryOp::Not,
1876                    operand: Box::new(operand),
1877                }
1878            }
1879            _ => self.parse_postfix(),
1880        }
1881    }
1882
1883    fn parse_postfix(&mut self) -> Expression {
1884        let mut expr = self.parse_primary();
1885
1886        loop {
1887            match self.peek().clone() {
1888                Token::Dot => {
1889                    self.advance(); // '.'
1890                    let raw_prop = self.expect_ident();
1891                    let prop = snake_to_camel(&raw_prop);
1892
1893                    // Check if it's a method call
1894                    if *self.peek() == Token::LParen {
1895                        let args = self.parse_call_args();
1896                        // Handle self.method(...) -> this.method(...)
1897                        if matches!(&expr, Expression::Identifier { name } if name == "this") {
1898                            expr = Expression::CallExpr {
1899                                callee: Box::new(Expression::MemberExpr {
1900                                    object: Box::new(Expression::Identifier {
1901                                        name: "this".to_string(),
1902                                    }),
1903                                    property: prop,
1904                                }),
1905                                args,
1906                            };
1907                        } else {
1908                            expr = Expression::CallExpr {
1909                                callee: Box::new(Expression::MemberExpr {
1910                                    object: Box::new(expr),
1911                                    property: prop,
1912                                }),
1913                                args,
1914                            };
1915                        }
1916                    } else {
1917                        // Property access
1918                        if matches!(&expr, Expression::Identifier { name } if name == "this") {
1919                            expr = Expression::PropertyAccess { property: prop };
1920                        } else {
1921                            expr = Expression::MemberExpr {
1922                                object: Box::new(expr),
1923                                property: prop,
1924                            };
1925                        }
1926                    }
1927                }
1928                Token::LParen => {
1929                    let args = self.parse_call_args();
1930                    expr = Expression::CallExpr {
1931                        callee: Box::new(expr),
1932                        args,
1933                    };
1934                }
1935                Token::LBracket => {
1936                    self.advance();
1937                    let index = self.parse_expression();
1938                    self.expect(&Token::RBracket);
1939                    expr = Expression::IndexAccess {
1940                        object: Box::new(expr),
1941                        index: Box::new(index),
1942                    };
1943                }
1944                _ => break,
1945            }
1946        }
1947
1948        expr
1949    }
1950
1951    fn parse_primary(&mut self) -> Expression {
1952        match self.advance() {
1953            Token::NumberLit(v) => Expression::BigIntLiteral { value: v },
1954            Token::TrueLit => Expression::BoolLiteral { value: true },
1955            Token::FalseLit => Expression::BoolLiteral { value: false },
1956            Token::NoneLit => Expression::BigIntLiteral { value: 0 },
1957            Token::HexStringLit(v) => Expression::ByteStringLiteral { value: v },
1958            Token::StringLit(v) => Expression::ByteStringLiteral { value: v },
1959            Token::SelfKw => {
1960                // self -> this
1961                Expression::Identifier {
1962                    name: "this".to_string(),
1963                }
1964            }
1965            Token::Ident(name) => {
1966                // Check for bytes.fromhex("...")
1967                if name == "bytes" && *self.peek() == Token::Dot {
1968                    if let Some(Token::Ident(ref next_name)) = self.tokens.get(self.pos + 1) {
1969                        if next_name == "fromhex" {
1970                            self.advance(); // '.'
1971                            self.advance(); // 'fromhex'
1972                            self.expect(&Token::LParen);
1973                            let val = match self.advance() {
1974                                Token::StringLit(s) => s,
1975                                _ => {
1976                                    self.errors
1977                                        .push(Diagnostic::error("Expected string in bytes.fromhex()", None));
1978                                    String::new()
1979                                }
1980                            };
1981                            self.expect(&Token::RParen);
1982                            return Expression::ByteStringLiteral { value: val };
1983                        }
1984                    }
1985                }
1986
1987                let mapped = map_builtin_name(&name);
1988                Expression::Identifier { name: mapped }
1989            }
1990            Token::Assert => {
1991                // assert used as expression (unusual but handle it)
1992                // In Python, `assert_(expr)` is an ident, but `assert expr` uses the keyword.
1993                // If we get here, it means `assert` keyword was used as an expression callee.
1994                Expression::Identifier {
1995                    name: "assert".to_string(),
1996                }
1997            }
1998            Token::LParen => {
1999                let expr = self.parse_expression();
2000                self.expect(&Token::RParen);
2001                expr
2002            }
2003            other => {
2004                self.errors
2005                    .push(Diagnostic::error(format!("Unexpected token in expression: {:?}", other), None));
2006                Expression::BigIntLiteral { value: 0 }
2007            }
2008        }
2009    }
2010
2011    fn parse_call_args(&mut self) -> Vec<Expression> {
2012        self.expect(&Token::LParen);
2013        let mut args = Vec::new();
2014        while *self.peek() != Token::RParen && *self.peek() != Token::Eof {
2015            args.push(self.parse_expression());
2016            if !self.match_tok(&Token::Comma) {
2017                break;
2018            }
2019        }
2020        self.expect(&Token::RParen);
2021        args
2022    }
2023}
2024
2025// ---------------------------------------------------------------------------
2026// Constructor builder
2027// ---------------------------------------------------------------------------
2028
2029fn build_constructor(properties: &[PropertyNode], file: &str) -> MethodNode {
2030    // Only include properties without initializers as constructor params
2031    let uninit_props: Vec<&PropertyNode> = properties
2032        .iter()
2033        .filter(|p| p.initializer.is_none())
2034        .collect();
2035
2036    let params: Vec<ParamNode> = uninit_props
2037        .iter()
2038        .map(|p| ParamNode {
2039            name: p.name.clone(),
2040            param_type: p.prop_type.clone(),
2041        })
2042        .collect();
2043
2044    let mut body: Vec<Statement> = Vec::new();
2045
2046    // super(...) call — only non-initialized property names as args
2047    let super_args: Vec<Expression> = uninit_props
2048        .iter()
2049        .map(|p| Expression::Identifier {
2050            name: p.name.clone(),
2051        })
2052        .collect();
2053    body.push(Statement::ExpressionStatement {
2054        expression: Expression::CallExpr {
2055            callee: Box::new(Expression::Identifier {
2056                name: "super".to_string(),
2057            }),
2058            args: super_args,
2059        },
2060        source_location: SourceLocation {
2061            file: file.to_string(),
2062            line: 1,
2063            column: 0,
2064        },
2065    });
2066
2067    // this.x = x for each non-initialized property
2068    for p in &uninit_props {
2069        body.push(Statement::Assignment {
2070            target: Expression::PropertyAccess {
2071                property: p.name.clone(),
2072            },
2073            value: Expression::Identifier {
2074                name: p.name.clone(),
2075            },
2076            source_location: SourceLocation {
2077                file: file.to_string(),
2078                line: 1,
2079                column: 0,
2080            },
2081        });
2082    }
2083
2084    MethodNode {
2085        name: "constructor".to_string(),
2086        params,
2087        body,
2088        visibility: Visibility::Public,
2089        source_location: SourceLocation {
2090            file: file.to_string(),
2091            line: 1,
2092            column: 0,
2093        },
2094    }
2095}
2096
2097// ---------------------------------------------------------------------------
2098// Tests
2099// ---------------------------------------------------------------------------
2100
2101#[cfg(test)]
2102mod tests {
2103    use super::*;
2104
2105    #[test]
2106    fn test_snake_to_camel() {
2107        assert_eq!(snake_to_camel("hello_world"), "helloWorld");
2108        assert_eq!(snake_to_camel("check_sig"), "checkSig");
2109        assert_eq!(snake_to_camel("already"), "already");
2110        assert_eq!(snake_to_camel("a_b_c"), "aBC");
2111        assert_eq!(snake_to_camel("pub_key_hash"), "pubKeyHash");
2112        assert_eq!(snake_to_camel("sum_"), "sum");
2113        assert_eq!(snake_to_camel("assert_"), "assert");
2114    }
2115
2116    #[test]
2117    fn test_builtin_name_mapping() {
2118        assert_eq!(map_builtin_name("assert_"), "assert");
2119        assert_eq!(map_builtin_name("check_sig"), "checkSig");
2120        assert_eq!(map_builtin_name("hash160"), "hash160");
2121        assert_eq!(map_builtin_name("verify_wots"), "verifyWOTS");
2122        assert_eq!(
2123            map_builtin_name("verify_slh_dsa_sha2_128s"),
2124            "verifySLHDSA_SHA2_128s"
2125        );
2126        assert_eq!(map_builtin_name("ec_add"), "ecAdd");
2127        assert_eq!(map_builtin_name("add_output"), "addOutput");
2128        assert_eq!(map_builtin_name("abs"), "abs");
2129        assert_eq!(map_builtin_name("some_func"), "someFunc");
2130    }
2131
2132    #[test]
2133    fn test_parse_simple_python_contract() {
2134        let source = r#"
2135from runar import SmartContract, Addr, Sig, PubKey, public, assert_, hash160, check_sig
2136
2137class P2PKH(SmartContract):
2138    pub_key_hash: Addr
2139
2140    def __init__(self, pub_key_hash: Addr):
2141        super().__init__(pub_key_hash)
2142        self.pub_key_hash = pub_key_hash
2143
2144    @public
2145    def unlock(self, sig: Sig, pub_key: PubKey):
2146        assert_(hash160(pub_key) == self.pub_key_hash)
2147        assert_(check_sig(sig, pub_key))
2148"#;
2149
2150        let result = parse_python(source, Some("P2PKH.runar.py"));
2151        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2152        let contract = result.contract.unwrap();
2153        assert_eq!(contract.name, "P2PKH");
2154        assert_eq!(contract.parent_class, "SmartContract");
2155        assert_eq!(contract.properties.len(), 1);
2156        assert_eq!(contract.properties[0].name, "pubKeyHash");
2157        assert!(contract.properties[0].readonly);
2158        assert_eq!(contract.methods.len(), 1);
2159        assert_eq!(contract.methods[0].name, "unlock");
2160        assert_eq!(contract.methods[0].visibility, Visibility::Public);
2161        // self param should be excluded
2162        assert_eq!(contract.methods[0].params.len(), 2);
2163        assert_eq!(contract.methods[0].params[0].name, "sig");
2164        assert_eq!(contract.methods[0].params[1].name, "pubKey");
2165    }
2166
2167    #[test]
2168    fn test_parse_stateful_python_contract() {
2169        let source = r#"
2170from runar import StatefulSmartContract, Bigint, Readonly, public, assert_
2171
2172class Stateful(StatefulSmartContract):
2173    count: Bigint
2174    max_count: Readonly[Bigint]
2175
2176    def __init__(self, count: Bigint, max_count: Bigint):
2177        super().__init__(count, max_count)
2178        self.count = count
2179        self.max_count = max_count
2180
2181    @public
2182    def increment(self, amount: Bigint):
2183        self.count = self.count + amount
2184        assert_(self.count <= self.max_count)
2185
2186    @public
2187    def reset(self):
2188        self.count = 0
2189"#;
2190
2191        let result = parse_python(source, Some("Stateful.runar.py"));
2192        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2193        let contract = result.contract.unwrap();
2194        assert_eq!(contract.name, "Stateful");
2195        assert_eq!(contract.parent_class, "StatefulSmartContract");
2196        assert_eq!(contract.properties.len(), 2);
2197        assert_eq!(contract.properties[0].name, "count");
2198        assert!(!contract.properties[0].readonly);
2199        assert_eq!(contract.properties[1].name, "maxCount");
2200        assert!(contract.properties[1].readonly);
2201        assert_eq!(contract.methods.len(), 2);
2202    }
2203
2204    #[test]
2205    fn test_parse_for_loop() {
2206        let source = r#"
2207from runar import SmartContract, Bigint, public, assert_
2208
2209class BoundedLoop(SmartContract):
2210    expected_sum: Bigint
2211
2212    def __init__(self, expected_sum: Bigint):
2213        super().__init__(expected_sum)
2214        self.expected_sum = expected_sum
2215
2216    @public
2217    def verify(self, start: Bigint):
2218        sum_: Bigint = 0
2219        for i in range(5):
2220            sum_ = sum_ + start + i
2221        assert_(sum_ == self.expected_sum)
2222"#;
2223
2224        let result = parse_python(source, Some("BoundedLoop.runar.py"));
2225        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2226        let contract = result.contract.unwrap();
2227        assert_eq!(contract.methods[0].name, "verify");
2228        // body: let sum, for, assert
2229        assert_eq!(contract.methods[0].body.len(), 3);
2230    }
2231
2232    #[test]
2233    fn test_parse_if_else() {
2234        let source = r#"
2235from runar import SmartContract, Bigint, public, assert_
2236
2237class IfElse(SmartContract):
2238    limit: Bigint
2239
2240    def __init__(self, limit: Bigint):
2241        super().__init__(limit)
2242        self.limit = limit
2243
2244    @public
2245    def check(self, value: Bigint, mode: bool):
2246        result: Bigint = 0
2247        if mode:
2248            result = value + self.limit
2249        else:
2250            result = value - self.limit
2251        assert_(result > 0)
2252"#;
2253
2254        let result = parse_python(source, Some("IfElse.runar.py"));
2255        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2256        let contract = result.contract.unwrap();
2257        assert_eq!(contract.methods[0].body.len(), 3); // let result, if/else, assert
2258    }
2259
2260    #[test]
2261    fn test_eq_maps_to_strict_eq() {
2262        let source = r#"
2263from runar import SmartContract, Bigint, public, assert_
2264
2265class Test(SmartContract):
2266    x: Bigint
2267
2268    @public
2269    def check(self, y: Bigint):
2270        assert_(self.x == y)
2271        assert_(self.x != y)
2272"#;
2273
2274        let result = parse_python(source, Some("Test.runar.py"));
2275        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2276        let contract = result.contract.unwrap();
2277        let body = &contract.methods[0].body;
2278        assert_eq!(body.len(), 2);
2279
2280        // First assert: StrictEq
2281        if let Statement::ExpressionStatement { expression, .. } = &body[0] {
2282            if let Expression::CallExpr { args, .. } = expression {
2283                if let Expression::BinaryExpr { op, .. } = &args[0] {
2284                    assert_eq!(*op, BinaryOp::StrictEq);
2285                } else {
2286                    panic!("Expected BinaryExpr inside assert");
2287                }
2288            } else {
2289                panic!("Expected CallExpr for assert");
2290            }
2291        }
2292
2293        // Second assert: StrictNe
2294        if let Statement::ExpressionStatement { expression, .. } = &body[1] {
2295            if let Expression::CallExpr { args, .. } = expression {
2296                if let Expression::BinaryExpr { op, .. } = &args[0] {
2297                    assert_eq!(*op, BinaryOp::StrictNe);
2298                } else {
2299                    panic!("Expected BinaryExpr inside assert");
2300                }
2301            }
2302        }
2303    }
2304
2305    #[test]
2306    fn test_self_to_this_conversion() {
2307        let source = r#"
2308from runar import StatefulSmartContract, Bigint, public
2309
2310class Example(StatefulSmartContract):
2311    value: Bigint
2312
2313    @public
2314    def set_value(self, new_value: Bigint):
2315        self.value = new_value
2316"#;
2317
2318        let result = parse_python(source, Some("Example.runar.py"));
2319        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2320        let contract = result.contract.unwrap();
2321        let body = &contract.methods[0].body;
2322        assert_eq!(body.len(), 1);
2323
2324        // Should be this.value = newValue (with PropertyAccess)
2325        if let Statement::Assignment { target, .. } = &body[0] {
2326            match target {
2327                Expression::PropertyAccess { property } => {
2328                    assert_eq!(property, "value");
2329                }
2330                _ => panic!("Expected PropertyAccess, got {:?}", target),
2331            }
2332        }
2333        // Method name and param should be camelCase
2334        assert_eq!(contract.methods[0].name, "setValue");
2335        assert_eq!(contract.methods[0].params[0].name, "newValue");
2336    }
2337
2338    #[test]
2339    fn test_constructor_auto_generated() {
2340        let source = r#"
2341from runar import SmartContract, Bigint, PubKey, public, assert_
2342
2343class Test(SmartContract):
2344    a: Bigint
2345    b: PubKey
2346
2347    @public
2348    def check(self):
2349        assert_(self.a > 0)
2350"#;
2351
2352        let result = parse_python(source, None);
2353        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2354        let contract = result.contract.unwrap();
2355        // Constructor should have params for each property
2356        assert_eq!(contract.constructor.params.len(), 2);
2357        // Constructor body: super(a, b) + this.a = a + this.b = b
2358        assert_eq!(contract.constructor.body.len(), 3);
2359    }
2360
2361    #[test]
2362    fn test_python_integer_division() {
2363        let source = r#"
2364from runar import SmartContract, Bigint, public, assert_
2365
2366class DivTest(SmartContract):
2367    x: Bigint
2368
2369    @public
2370    def check(self, y: Bigint):
2371        result: Bigint = self.x // y
2372        assert_(result > 0)
2373"#;
2374
2375        let result = parse_python(source, Some("DivTest.runar.py"));
2376        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2377        let contract = result.contract.unwrap();
2378        let body = &contract.methods[0].body;
2379
2380        // First statement should be a variable decl with init being BinaryExpr Div
2381        if let Statement::VariableDecl { init, .. } = &body[0] {
2382            if let Expression::BinaryExpr { op, .. } = init {
2383                assert_eq!(*op, BinaryOp::Div);
2384            } else {
2385                panic!("Expected BinaryExpr Div, got {:?}", init);
2386            }
2387        }
2388    }
2389
2390    #[test]
2391    fn test_ternary_expression() {
2392        let source = r#"
2393from runar import SmartContract, Bigint, public, assert_
2394
2395class TernTest(SmartContract):
2396    x: Bigint
2397
2398    @public
2399    def check(self, cond: bool):
2400        result: Bigint = 1 if cond else 0
2401        assert_(result == self.x)
2402"#;
2403
2404        let result = parse_python(source, Some("TernTest.runar.py"));
2405        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2406        let contract = result.contract.unwrap();
2407        let body = &contract.methods[0].body;
2408
2409        // First statement should be variable decl with ternary init
2410        if let Statement::VariableDecl { init, .. } = &body[0] {
2411            match init {
2412                Expression::TernaryExpr { .. } => {} // OK
2413                _ => panic!("Expected TernaryExpr, got {:?}", init),
2414            }
2415        }
2416    }
2417
2418    #[test]
2419    fn test_assert_keyword_as_statement() {
2420        let source = r#"
2421from runar import SmartContract, Bigint, public
2422
2423class AssertTest(SmartContract):
2424    x: Bigint
2425
2426    @public
2427    def check(self, y: Bigint):
2428        assert self.x == y
2429"#;
2430
2431        let result = parse_python(source, Some("AssertTest.runar.py"));
2432        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2433        let contract = result.contract.unwrap();
2434        let body = &contract.methods[0].body;
2435        assert_eq!(body.len(), 1);
2436
2437        if let Statement::ExpressionStatement { expression, .. } = &body[0] {
2438            if let Expression::CallExpr { callee, .. } = expression {
2439                if let Expression::Identifier { name } = callee.as_ref() {
2440                    assert_eq!(name, "assert");
2441                }
2442            }
2443        }
2444    }
2445
2446    #[test]
2447    fn test_hex_byte_string() {
2448        let source = r#"
2449from runar import SmartContract, ByteString, public, assert_
2450
2451class HexTest(SmartContract):
2452    data: ByteString
2453
2454    @public
2455    def check(self):
2456        val: ByteString = b'\xde\xad'
2457        assert_(val == self.data)
2458"#;
2459
2460        let result = parse_python(source, Some("HexTest.runar.py"));
2461        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2462        let contract = result.contract.unwrap();
2463        let body = &contract.methods[0].body;
2464
2465        if let Statement::VariableDecl { init, .. } = &body[0] {
2466            if let Expression::ByteStringLiteral { value } = init {
2467                assert_eq!(value, "dead");
2468            } else {
2469                panic!("Expected ByteStringLiteral, got {:?}", init);
2470            }
2471        }
2472    }
2473
2474    #[test]
2475    fn test_shift_operators() {
2476        let source = r#"
2477from runar import SmartContract, Bigint, public, assert_
2478
2479class ShiftTest(SmartContract):
2480    x: Bigint
2481
2482    @public
2483    def check(self, n: Bigint):
2484        a: Bigint = self.x << n
2485        b: Bigint = self.x >> n
2486        assert_(a + b > 0)
2487"#;
2488
2489        let result = parse_python(source, Some("ShiftTest.runar.py"));
2490        assert!(result.errors.is_empty(), "errors: {:?}", result.errors);
2491        let contract = result.contract.unwrap();
2492        let body = &contract.methods[0].body;
2493
2494        // First var decl should have Shl
2495        if let Statement::VariableDecl { init, .. } = &body[0] {
2496            if let Expression::BinaryExpr { op, .. } = init {
2497                assert_eq!(*op, BinaryOp::Shl);
2498            }
2499        }
2500        // Second var decl should have Shr
2501        if let Statement::VariableDecl { init, .. } = &body[1] {
2502            if let Expression::BinaryExpr { op, .. } = init {
2503                assert_eq!(*op, BinaryOp::Shr);
2504            }
2505        }
2506    }
2507}