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