Skip to main content

pine_parser/
lib.rs

1pub use pine_ast::{Argument, BinOp, Expr, Literal, Loc, Program, Stmt, UnOp, VarKind};
2use pine_lexer::{Token, TokenType};
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum ParserError {
7    #[error("Unexpected token: {0:?} at line {1}")]
8    UnexpectedToken(TokenType, usize),
9
10    #[error("Expected {expected} but found {found:?} at line {line}")]
11    ExpectedToken {
12        expected: String,
13        found: TokenType,
14        line: usize,
15    },
16
17    #[error("Expected variable name at line {0}")]
18    ExpectedVariableName(usize),
19
20    #[error("Expected parameter name at line {0}")]
21    ExpectedParameterName(usize),
22
23    #[error("Can only call identifiers or member access at line {0}")]
24    InvalidCallTarget(usize),
25
26    #[error("Expected identifier after '.' at line {0}")]
27    ExpectedIdentifierAfterDot(usize),
28
29    #[error(transparent)]
30    Lexer(#[from] pine_lexer::LexerError),
31}
32
33impl From<ParserError> for String {
34    fn from(err: ParserError) -> String {
35        err.to_string()
36    }
37}
38
39/// Helper trait to convert TokenType to operators
40trait TokenTypeExt {
41    fn to_binop(&self) -> Option<BinOp>;
42}
43
44impl TokenTypeExt for TokenType {
45    /// Convert token type to binary operator, if applicable
46    fn to_binop(&self) -> Option<BinOp> {
47        match self {
48            TokenType::Plus => Some(BinOp::Add),
49            TokenType::Minus => Some(BinOp::Sub),
50            TokenType::Star => Some(BinOp::Mul),
51            TokenType::Slash => Some(BinOp::Div),
52            TokenType::Percent => Some(BinOp::Mod),
53            TokenType::Equal => Some(BinOp::Eq),
54            TokenType::NotEqual => Some(BinOp::NotEq),
55            TokenType::Less => Some(BinOp::Less),
56            TokenType::Greater => Some(BinOp::Greater),
57            TokenType::LessEqual => Some(BinOp::LessEq),
58            TokenType::GreaterEqual => Some(BinOp::GreaterEq),
59            TokenType::And => Some(BinOp::And),
60            TokenType::Or => Some(BinOp::Or),
61            TokenType::PlusAssign => Some(BinOp::Add),
62            TokenType::MinusAssign => Some(BinOp::Sub),
63            TokenType::StarAssign => Some(BinOp::Mul),
64            TokenType::SlashAssign => Some(BinOp::Div),
65            _ => None,
66        }
67    }
68}
69
70pub struct Parser {
71    tokens: Vec<Token>,
72    current: usize,
73    next_call_id: u32,
74}
75
76impl Parser {
77    pub fn new(tokens: Vec<Token>) -> Self {
78        let tokens = tokens
79            .into_iter()
80            .filter(|t| !matches!(t.typ, TokenType::Comment(_) | TokenType::BlankLine))
81            .collect();
82        Self {
83            tokens,
84            current: 0,
85            next_call_id: 1,
86        }
87    }
88
89    /// Lex and parse `source` into a program in one step.
90    pub fn parse_source(source: &str) -> Result<Program, ParserError> {
91        let tokens = pine_lexer::Lexer::new(source).tokenize()?;
92        Ok(Program::new(Self::new(tokens).parse()?))
93    }
94
95    fn next_call_id(&mut self) -> u32 {
96        let id = self.next_call_id;
97        self.next_call_id += 1;
98        id
99    }
100
101    fn peek(&self) -> &Token {
102        &self.tokens[self.current]
103    }
104
105    fn is_at_end(&self) -> bool {
106        matches!(self.peek().typ, TokenType::Eof)
107    }
108
109    fn advance(&mut self) -> &Token {
110        if !self.is_at_end() {
111            self.current += 1;
112        }
113        &self.tokens[self.current - 1]
114    }
115
116    fn check(&self, typ: &TokenType) -> bool {
117        !self.is_at_end() && &self.peek().typ == typ
118    }
119
120    fn match_token(&mut self, types: &[TokenType]) -> bool {
121        for typ in types {
122            if self.check(typ) {
123                self.advance();
124                return true;
125            }
126        }
127        false
128    }
129
130    /// Try to parse something speculatively. If parsing fails, restore position and return None.
131    /// This is useful for lookahead/backtracking scenarios.
132    fn try_parse<T, F>(&mut self, f: F) -> Option<T>
133    where
134        F: FnOnce(&mut Self) -> Result<T, ParserError>,
135    {
136        let saved_pos = self.current;
137        match f(self) {
138            Ok(val) => Some(val),
139            Err(_) => {
140                self.current = saved_pos;
141                None
142            }
143        }
144    }
145
146    /// Try to parse type arguments: <type1, type2, ...>
147    /// Returns None if this isn't actually type arguments (e.g., it's a comparison)
148    fn try_parse_type_args(&mut self) -> Option<Vec<String>> {
149        self.try_parse(|p| {
150            p.consume(TokenType::Less, "Expected '<'")?;
151
152            let mut type_args = vec![];
153
154            loop {
155                // Parse type name (identifier or type keyword like int/float)
156                let type_name = match &p.peek().typ {
157                    TokenType::Ident(name) => name.clone(),
158                    TokenType::Int => "int".to_string(),
159                    TokenType::Float => "float".to_string(),
160                    _ => {
161                        return Err(ParserError::UnexpectedToken(
162                            p.peek().typ.clone(),
163                            p.peek().line,
164                        ))
165                    }
166                };
167                p.advance();
168                type_args.push(type_name);
169
170                // Check for comma (more types) or end
171                if p.match_token(&[TokenType::Comma]) {
172                    continue;
173                } else if p.match_token(&[TokenType::Greater]) {
174                    break;
175                } else {
176                    return Err(ParserError::UnexpectedToken(
177                        p.peek().typ.clone(),
178                        p.peek().line,
179                    ));
180                }
181            }
182
183            Ok(type_args)
184        })
185    }
186
187    /// Skip any newline tokens
188    fn skip_newlines(&mut self) {
189        while self.match_token(&[TokenType::Newline]) {}
190    }
191
192    /// Skip newlines, indents, and dedents (whitespace tokens)
193    fn skip_whitespace(&mut self) {
194        while self.match_token(&[TokenType::Newline, TokenType::Indent, TokenType::Dedent]) {}
195    }
196
197    /// Parse an optional type suffix: an array `[]`, or — for a collection type
198    /// (`array`/`matrix`/`map`) — a generic `<...>` argument list. Returns the
199    /// type's textual form (e.g. `float[]`, `array<Point>`, `map<string, int>`).
200    /// The generic form is limited to collection bases so a bare `x < y > z`
201    /// comparison is never mistaken for a type.
202    fn parse_type_suffix(&mut self, type_name: String) -> Result<String, ParserError> {
203        if self.match_token(&[TokenType::LBracket]) {
204            self.consume(TokenType::RBracket, "Expected ']' after '[' in array type")?;
205            Ok(format!("{type_name}[]"))
206        } else if matches!(type_name.as_str(), "array" | "matrix" | "map")
207            && self.match_token(&[TokenType::Less])
208        {
209            let mut args = Vec::new();
210            loop {
211                args.push(self.parse_type()?);
212                if !self.match_token(&[TokenType::Comma]) {
213                    break;
214                }
215            }
216            self.consume(
217                TokenType::Greater,
218                "Expected '>' after generic type arguments",
219            )?;
220            Ok(format!("{type_name}<{}>", args.join(", ")))
221        } else {
222            Ok(type_name)
223        }
224    }
225
226    /// Parse a type name (`int`/`float`/identifier) with its optional suffix.
227    fn parse_type(&mut self) -> Result<String, ParserError> {
228        let base = match &self.peek().typ {
229            TokenType::Int => "int".to_string(),
230            TokenType::Float => "float".to_string(),
231            TokenType::Ident(name) => name.clone(),
232            other => {
233                return Err(ParserError::UnexpectedToken(
234                    other.clone(),
235                    self.peek().line,
236                ))
237            }
238        };
239        self.advance();
240        self.parse_type_suffix(base)
241    }
242
243    /// Parse an expression that may be on an indented continuation line.
244    /// Handles: newlines + optional indent + expression + optional dedent
245    fn parse_indented_expression(&mut self) -> Result<Expr, ParserError> {
246        self.skip_newlines();
247
248        // Check if expression is on an indented line
249        let has_indent = self.match_token(&[TokenType::Indent]);
250
251        let expr = self.expression()?;
252
253        // Consume dedent if we had indent
254        if has_indent {
255            self.match_token(&[TokenType::Dedent]);
256        }
257
258        Ok(expr)
259    }
260
261    fn consume(&mut self, typ: TokenType, message: &str) -> Result<&Token, ParserError> {
262        if self.check(&typ) {
263            Ok(self.advance())
264        } else {
265            Err(ParserError::ExpectedToken {
266                expected: message.to_string(),
267                found: self.peek().typ.clone(),
268                line: self.peek().line,
269            })
270        }
271    }
272
273    /// Helper to extract an identifier from the current token and advance
274    fn expect_identifier(&mut self) -> Result<String, ParserError> {
275        if let TokenType::Ident(name) = &self.peek().typ {
276            let name = name.clone();
277            self.advance();
278            Ok(name)
279        } else {
280            Err(ParserError::ExpectedVariableName(self.peek().line))
281        }
282    }
283
284    /// Generic helper to parse indented field blocks
285    fn parse_indented_fields<T, F>(&mut self, parse_field: F) -> Result<Vec<T>, ParserError>
286    where
287        F: Fn(&mut Self) -> Result<T, ParserError>,
288    {
289        let mut fields = Vec::new();
290
291        loop {
292            // Skip newlines between fields
293            self.skip_newlines();
294
295            // Check for dedent (end of field block)
296            if self.check(&TokenType::Dedent) {
297                self.advance();
298                break;
299            }
300
301            // Check for end of file
302            if self.is_at_end() {
303                break;
304            }
305
306            // Parse a field using the provided parser
307            fields.push(parse_field(self)?);
308        }
309
310        Ok(fields)
311    }
312
313    /// Helper to skip newlines and optionally match indent
314    fn skip_newlines_and_indent(&mut self) {
315        self.skip_newlines();
316        self.match_token(&[TokenType::Indent]);
317    }
318
319    /// Helper to skip newlines and optionally match dedent
320    fn skip_newlines_and_dedent(&mut self) {
321        self.skip_newlines();
322        self.match_token(&[TokenType::Dedent]);
323    }
324
325    /// Helper to speculatively consume indent only if followed by expected token
326    fn try_consume_indent_if_followed_by(&mut self, expected: &TokenType) {
327        if self.check(&TokenType::Indent) {
328            self.try_parse(|p| {
329                p.advance(); // consume indent
330                if p.check(expected) {
331                    Ok(())
332                } else {
333                    Err(ParserError::UnexpectedToken(
334                        p.peek().typ.clone(),
335                        p.peek().line,
336                    ))
337                }
338            });
339        }
340    }
341
342    /// Helper to speculatively consume indent/dedent only if followed by one of the expected operators
343    fn try_consume_layout_token_if_followed_by(&mut self, expected: &[TokenType]) {
344        if self.check(&TokenType::Indent) || self.check(&TokenType::Dedent) {
345            self.try_parse(|p| {
346                p.advance(); // consume indent or dedent
347                for typ in expected {
348                    if p.check(typ) {
349                        return Ok(());
350                    }
351                }
352                Err(ParserError::UnexpectedToken(
353                    p.peek().typ.clone(),
354                    p.peek().line,
355                ))
356            });
357        }
358    }
359
360    /// Helper to parse optional type qualifier (const, input, simple, series)
361    fn parse_optional_type_qualifier(&mut self) -> Option<pine_ast::TypeQualifier> {
362        use pine_ast::TypeQualifier;
363        if self.match_token(&[TokenType::Const]) {
364            Some(TypeQualifier::Const)
365        } else if let TokenType::Ident(name) = &self.peek().typ {
366            match name.as_str() {
367                "input" => {
368                    self.advance();
369                    Some(TypeQualifier::Input)
370                }
371                "simple" => {
372                    self.advance();
373                    Some(TypeQualifier::Simple)
374                }
375                "series" => {
376                    self.advance();
377                    Some(TypeQualifier::Series)
378                }
379                _ => None,
380            }
381        } else {
382            None
383        }
384    }
385
386    /// Helper to parse optional type annotation with array suffix
387    /// Returns None if no type annotation is found
388    /// Supports: int, float, or custom identifier types with optional [] suffix
389    fn parse_optional_type_annotation(&mut self) -> Option<String> {
390        if self.match_token(&[TokenType::Int, TokenType::Float]) {
391            let type_name = self.tokens[self.current - 1].lexeme.clone();
392            // Check for array type: int[] or float[]
393            self.parse_type_suffix(type_name).ok()
394        } else if let TokenType::Ident(type_name) = &self.peek().typ {
395            let type_name = type_name.clone();
396            self.try_parse(|p| {
397                p.advance(); // consume potential type name
398
399                // Check for an array `[]` or generic `<...>` suffix.
400                let final_type = p.parse_type_suffix(type_name.clone())?;
401
402                // Must be followed by identifier to be a type annotation
403                if !matches!(p.peek().typ, TokenType::Ident(_)) {
404                    return Err(ParserError::ExpectedVariableName(p.peek().line));
405                }
406
407                Ok(final_type)
408            })
409        } else {
410            None
411        }
412    }
413
414    /// Generic helper to parse comma-separated lists
415    /// Handles newlines and optional indentation around commas
416    fn parse_comma_separated<T, F>(
417        &mut self,
418        closing_delimiter: &TokenType,
419        parse_item: F,
420    ) -> Result<Vec<T>, ParserError>
421    where
422        F: Fn(&mut Self) -> Result<T, ParserError>,
423    {
424        let mut items = vec![];
425
426        if !self.check(closing_delimiter) {
427            loop {
428                items.push(parse_item(self)?);
429
430                // Skip newlines after each item
431                self.skip_newlines();
432
433                if !self.match_token(&[TokenType::Comma]) {
434                    break;
435                }
436
437                // Skip newlines after comma
438                self.skip_newlines_and_indent();
439            }
440        }
441
442        Ok(items)
443    }
444
445    // Parse a program (top-level)
446    pub fn parse(&mut self) -> Result<Vec<Stmt>, ParserError> {
447        let mut statements = vec![];
448
449        while !self.is_at_end() {
450            // Skip any leading newlines and dedents (dedents at top level are from EOF)
451            self.skip_whitespace();
452
453            // Check if we reached EOF after skipping
454            if self.is_at_end() {
455                break;
456            }
457
458            statements.push(self.declaration()?);
459        }
460
461        Ok(statements)
462    }
463
464    // Declarations (var declarations, assignments, etc.)
465    /// The position of the current token, for attaching to a declaration node.
466    fn cur_loc(&self) -> Loc {
467        let token = self.peek();
468        Loc::new(token.line as u32, token.column as u32)
469    }
470
471    /// The position of the most recently consumed token.
472    fn prev_loc(&self) -> Loc {
473        let token = &self.tokens[self.current.saturating_sub(1)];
474        Loc::new(token.line as u32, token.column as u32)
475    }
476
477    fn declaration(&mut self) -> Result<Stmt, ParserError> {
478        // Check for type qualifier first (const, input, simple, series)
479        let type_qualifier = self.parse_optional_type_qualifier();
480
481        // Check for var or varip keyword (can be followed by type annotation)
482        let var_kind = if self.match_token(&[TokenType::Varip]) {
483            VarKind::Varip
484        } else if self.match_token(&[TokenType::Var]) {
485            VarKind::Var
486        } else if type_qualifier.is_some() {
487            // If we have a type qualifier but no var/varip, it's still a variable declaration
488            // e.g., const int x = 5
489            VarKind::Plain
490        } else {
491            // Not a var/varip declaration, continue to other statement types
492            return self.check_type_annotated_declaration();
493        };
494
495        // Check if followed by type annotation: var int x = ..., var float y = ..., var label l = ...
496        let type_annotation = self.parse_optional_type_annotation();
497        self.typed_var_declaration_with_qualifier(type_qualifier, type_annotation, var_kind)
498    }
499
500    fn check_type_annotated_declaration(&mut self) -> Result<Stmt, ParserError> {
501        // Check for type declaration: type TypeName
502        if self.match_token(&[TokenType::Type]) {
503            return self.type_declaration(false);
504        }
505
506        // Check for enum declaration: enum EnumName
507        if self.match_token(&[TokenType::Enum]) {
508            return self.enum_declaration(false);
509        }
510
511        // Check for method declaration: method methodName(params) =>
512        if self.match_token(&[TokenType::Method]) {
513            return self.method_declaration(false);
514        }
515
516        // Check for type-annotated declaration without var: int x = ..., float y = ..., int[] x = ...
517        if self.match_token(&[TokenType::Int, TokenType::Float]) {
518            let type_name = self.tokens[self.current - 1].lexeme.clone();
519            // Check for array type: int[] or float[]
520            let type_name = self.parse_type_suffix(type_name)?;
521            return self.typed_var_declaration(Some(type_name), VarKind::Plain);
522        }
523
524        // Check for identifier type with optional []: string x = ..., string[] x = ...
525        if let Some(type_annotation) = self.parse_optional_type_annotation() {
526            return self.typed_var_declaration(Some(type_annotation), VarKind::Plain);
527        }
528
529        self.statement()
530    }
531
532    fn type_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
533        // Parse type name
534        let loc = self.cur_loc();
535        let type_name = self.expect_identifier()?;
536
537        // Expect newline before fields
538        self.consume(TokenType::Newline, "Expected newline after type name")?;
539
540        // Expect indent to start field block
541        self.consume(TokenType::Indent, "Expected indent for type fields")?;
542
543        // Parse fields using generic helper
544        let fields = self.parse_indented_fields(|p| {
545            // Parse optional type qualifier (const, input, simple, series)
546            let type_qualifier = p.parse_optional_type_qualifier();
547
548            // Parse field: type_annotation field_name [= default_value]
549            // First, get the type annotation (int, float, or identifier)
550            let field_type = if p.match_token(&[TokenType::Int, TokenType::Float]) {
551                p.tokens[p.current - 1].lexeme.clone()
552            } else if let TokenType::Ident(type_name) = &p.peek().typ {
553                let type_name = type_name.clone();
554                p.advance();
555                type_name
556            } else {
557                return Err(ParserError::UnexpectedToken(
558                    p.peek().typ.clone(),
559                    p.peek().line,
560                ));
561            };
562
563            // Parse field name
564            let field_loc = p.cur_loc();
565            let field_name = p.expect_identifier()?;
566
567            // Parse optional default value
568            let default_value = if p.match_token(&[TokenType::Assign]) {
569                Some(p.expression()?)
570            } else {
571                None
572            };
573
574            Ok(pine_ast::TypeField {
575                name: field_name,
576                type_qualifier,
577                type_annotation: field_type,
578                default_value,
579                loc: field_loc,
580            })
581        })?;
582
583        Ok(Stmt::TypeDecl {
584            name: type_name,
585            fields,
586            export,
587            loc,
588        })
589    }
590
591    fn enum_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
592        // Parse enum name
593        let loc = self.cur_loc();
594        let enum_name = self.expect_identifier()?;
595
596        // Expect newline before fields
597        self.consume(TokenType::Newline, "Expected newline after enum name")?;
598
599        // Expect indent to start field block
600        self.consume(TokenType::Indent, "Expected indent for enum fields")?;
601
602        // Parse fields using generic helper
603        let fields = self.parse_indented_fields(|p| {
604            // Parse field: field_name [= "title"]
605            let field_loc = p.cur_loc();
606            let field_name = p.expect_identifier()?;
607
608            // Parse optional title
609            let title = if p.match_token(&[TokenType::Assign]) {
610                // Expect a string literal for the title
611                if let TokenType::String(s) = &p.peek().typ {
612                    let s = s.clone();
613                    p.advance();
614                    Some(s)
615                } else {
616                    return Err(ParserError::UnexpectedToken(
617                        p.peek().typ.clone(),
618                        p.peek().line,
619                    ));
620                }
621            } else {
622                None
623            };
624
625            Ok(pine_ast::EnumField {
626                name: field_name,
627                title,
628                loc: field_loc,
629            })
630        })?;
631
632        Ok(Stmt::EnumDecl {
633            name: enum_name,
634            fields,
635            export,
636            loc,
637        })
638    }
639
640    fn export_statement(&mut self) -> Result<Stmt, ParserError> {
641        // export type typename - delegate to type_declaration
642        if self.match_token(&[TokenType::Type]) {
643            return self.type_declaration(true);
644        }
645
646        // export enum enumname - delegate to enum_declaration
647        if self.match_token(&[TokenType::Enum]) {
648            return self.enum_declaration(true);
649        }
650
651        // export [method] functionname(params) => body
652        // Check if it's a method
653        let is_method = self.match_token(&[TokenType::Method]);
654
655        if is_method {
656            return self.method_declaration(true);
657        }
658
659        // Parse function name
660        let loc = self.cur_loc();
661        let func_name = self.expect_identifier()?;
662
663        // Check if this is a function declaration (followed by '(')
664        if self.check(&TokenType::LParen) {
665            // export functionname(params) => body
666            self.advance(); // consume '('
667
668            let params = self.function_params()?;
669            self.consume(TokenType::RParen, "Expected ')' after function parameters")?;
670            self.consume(TokenType::Arrow, "Expected '=>'")?;
671
672            // Skip optional newline after =>
673            self.match_token(&[TokenType::Newline]);
674
675            // Parse function body (can be a block or single expression)
676            let body = self.parse_block()?;
677
678            Ok(Stmt::FunctionDecl {
679                name: func_name,
680                params,
681                body,
682                export: true,
683                loc,
684            })
685        } else {
686            // Just export functionname (old style - keeping for backward compatibility)
687            Ok(Stmt::Export {
688                item: pine_ast::ExportItem::Type(func_name),
689            })
690        }
691    }
692
693    fn import_statement(&mut self) -> Result<Stmt, ParserError> {
694        // import userName/libraryName/version as alias
695        let path = if let TokenType::Ident(p) = &self.peek().typ {
696            let mut path_parts = vec![p.clone()];
697            self.advance();
698
699            // Parse path segments separated by /
700            while self.match_token(&[TokenType::Slash]) {
701                if let TokenType::Ident(part) = &self.peek().typ {
702                    path_parts.push(part.clone());
703                    self.advance();
704                } else if let TokenType::IntLiteral(n) = &self.peek().typ {
705                    // Version number (an integer path segment)
706                    path_parts.push(n.to_string());
707                    self.advance();
708                } else {
709                    return Err(ParserError::UnexpectedToken(
710                        self.peek().typ.clone(),
711                        self.peek().line,
712                    ));
713                }
714            }
715
716            path_parts.join("/")
717        } else {
718            return Err(ParserError::ExpectedVariableName(self.peek().line));
719        };
720
721        // Expect 'as' keyword - for now we'll check for an identifier "as"
722        if let TokenType::Ident(kw) = &self.peek().typ {
723            if kw != "as" {
724                return Err(ParserError::UnexpectedToken(
725                    self.peek().typ.clone(),
726                    self.peek().line,
727                ));
728            }
729            self.advance();
730        } else {
731            return Err(ParserError::UnexpectedToken(
732                self.peek().typ.clone(),
733                self.peek().line,
734            ));
735        }
736
737        // Parse alias
738        let loc = self.cur_loc();
739        let alias = self.expect_identifier()?;
740
741        Ok(Stmt::Import { path, alias, loc })
742    }
743
744    fn method_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
745        // Parse method name
746        let loc = self.cur_loc();
747        let method_name = self.expect_identifier()?;
748
749        // Expect '('
750        self.consume(TokenType::LParen, "Expected '(' after method name")?;
751
752        // Parse parameters
753        let mut params = Vec::new();
754
755        if !self.check(&TokenType::RParen) {
756            loop {
757                // Parse optional type qualifier (const, input, simple, series)
758                let type_qualifier = self.parse_optional_type_qualifier();
759
760                // Parse optional type annotation
761                let type_annotation = self.parse_optional_type_annotation();
762
763                // Parse parameter name
764                let param_loc = self.cur_loc();
765                let param_name = self.expect_identifier()?;
766
767                // Parse optional default value
768                let default_value = if self.match_token(&[TokenType::Assign]) {
769                    Some(self.expression()?)
770                } else {
771                    None
772                };
773
774                params.push(pine_ast::MethodParam {
775                    type_qualifier,
776                    type_annotation,
777                    name: param_name,
778                    default_value,
779                    loc: param_loc,
780                });
781
782                if !self.match_token(&[TokenType::Comma]) {
783                    break;
784                }
785            }
786        }
787
788        self.consume(TokenType::RParen, "Expected ')' after parameters")?;
789
790        // Expect '=>'
791        self.consume(TokenType::Arrow, "Expected '=>' after method parameters")?;
792
793        // Skip optional newline after =>
794        self.match_token(&[TokenType::Newline]);
795
796        // Parse method body (can be a block or single expression)
797        let body = self.parse_block()?;
798
799        Ok(Stmt::MethodDecl {
800            name: method_name,
801            params,
802            body,
803            export,
804            loc,
805        })
806    }
807
808    fn typed_var_declaration(
809        &mut self,
810        type_annotation: Option<String>,
811        var_kind: VarKind,
812    ) -> Result<Stmt, ParserError> {
813        self.typed_var_declaration_with_qualifier(None, type_annotation, var_kind)
814    }
815
816    fn typed_var_declaration_with_qualifier(
817        &mut self,
818        type_qualifier: Option<pine_ast::TypeQualifier>,
819        type_annotation: Option<String>,
820        var_kind: VarKind,
821    ) -> Result<Stmt, ParserError> {
822        let loc = self.cur_loc();
823        let name = self.expect_identifier()?;
824
825        let initializer = if self.match_token(&[TokenType::Assign]) {
826            Some(self.parse_indented_expression()?)
827        } else {
828            None
829        };
830
831        Ok(Stmt::VarDecl {
832            name,
833            type_qualifier,
834            type_annotation,
835            initializer,
836            var_kind,
837            loc,
838        })
839    }
840
841    fn statement(&mut self) -> Result<Stmt, ParserError> {
842        // Check for export statement
843        if self.match_token(&[TokenType::Export]) {
844            return self.export_statement();
845        }
846
847        // Check for import statement
848        if self.match_token(&[TokenType::Import]) {
849            return self.import_statement();
850        }
851
852        // Check for if statement
853        if self.match_token(&[TokenType::If]) {
854            return self.if_statement();
855        }
856
857        // Check for for loop
858        if self.match_token(&[TokenType::For]) {
859            return self.for_statement();
860        }
861
862        // Check for while loop
863        if self.match_token(&[TokenType::While]) {
864            return self.while_statement();
865        }
866
867        // Check for break
868        if self.match_token(&[TokenType::Break]) {
869            return Ok(Stmt::Break {
870                loc: self.prev_loc(),
871            });
872        }
873
874        // Check for continue
875        if self.match_token(&[TokenType::Continue]) {
876            return Ok(Stmt::Continue {
877                loc: self.prev_loc(),
878            });
879        }
880
881        // Check for tuple destructuring: [a, b, c] = func()
882        // But only if followed by = (otherwise it's an array literal)
883        if self.check(&TokenType::LBracket) {
884            let tuple_loc = self.cur_loc();
885            if let Some((names, value)) = self.try_parse(|p| {
886                p.advance(); // consume [
887
888                let mut names = vec![];
889
890                // Parse identifiers separated by commas
891                if !p.check(&TokenType::RBracket) {
892                    loop {
893                        if let TokenType::Ident(name) = &p.peek().typ {
894                            names.push(name.clone());
895                            p.advance();
896                        } else {
897                            // Not all identifiers, not tuple destructuring
898                            return Err(ParserError::ExpectedVariableName(p.peek().line));
899                        }
900
901                        if !p.match_token(&[TokenType::Comma]) {
902                            break;
903                        }
904                    }
905                }
906
907                p.consume(TokenType::RBracket, "Expected ']' in tuple destructuring")?;
908                p.consume(TokenType::Assign, "Expected '=' after tuple pattern")?;
909
910                // Skip newlines after =
911                p.skip_newlines();
912
913                let value = p.expression()?;
914
915                Ok((names, value))
916            }) {
917                return Ok(Stmt::TupleAssignment {
918                    names,
919                    value,
920                    loc: tuple_loc,
921                });
922            }
923        }
924
925        // Check for implicit variable declaration, reassignment, or function definition
926        // name = expr (declaration)
927        // name := expr (reassignment)
928        // name(params) => body (function definition)
929        if let TokenType::Ident(name) = &self.peek().typ {
930            let name = name.clone();
931            let name_loc = self.cur_loc();
932
933            // Check for function definition: name(params) =>
934            if let Some((param_structs, body)) = self.try_parse(|p| {
935                p.advance(); // consume identifier
936                p.consume(TokenType::LParen, "Expected '('")?;
937
938                let params = p.function_params()?;
939                p.consume(TokenType::RParen, "Expected ')' after function parameters")?;
940                p.consume(TokenType::Arrow, "Expected '=>'")?;
941
942                // Skip optional newline after =>
943                p.match_token(&[TokenType::Newline]);
944
945                // Parse function body (can be a block or single expression)
946                let body = p.parse_block()?;
947
948                Ok((params, body))
949            }) {
950                // Use the full FunctionParam structs for Expr::Function
951                let initializer = Some(Expr::Function {
952                    params: param_structs,
953                    body,
954                });
955                return Ok(Stmt::VarDecl {
956                    name,
957                    type_qualifier: None,
958                    type_annotation: None,
959                    initializer,
960                    var_kind: VarKind::Plain,
961                    loc: name_loc,
962                });
963            }
964
965            // Try to parse as assignment/declaration
966            if let Some(stmt) = self.try_parse(|p| {
967                p.advance(); // consume identifier
968
969                if p.match_token(&[TokenType::Assign]) {
970                    // This is an assignment with =, treat it as a var declaration
971                    let initializer = Some(p.parse_indented_expression()?);
972
973                    Ok(Stmt::VarDecl {
974                        name: name.clone(),
975                        type_qualifier: None,
976                        type_annotation: None,
977                        initializer,
978                        var_kind: VarKind::Plain,
979                        loc: name_loc,
980                    })
981                } else if p.match_token(&[TokenType::ColonAssign]) {
982                    // This is a reassignment with :=
983                    let value = p.parse_indented_expression()?;
984
985                    Ok(Stmt::Assignment {
986                        target: Expr::Variable {
987                            name: name.clone(),
988                            loc: name_loc,
989                        },
990                        value,
991                    })
992                } else if p.match_token(&[
993                    TokenType::PlusAssign,
994                    TokenType::MinusAssign,
995                    TokenType::StarAssign,
996                    TokenType::SlashAssign,
997                ]) {
998                    // Compound assignment: x += 5 is equivalent to x := x + 5
999                    let op_tok = &p.tokens[p.current - 1];
1000                    let op_loc = Loc::new(op_tok.line as u32, op_tok.column as u32);
1001                    let op = op_tok
1002                        .typ
1003                        .to_binop()
1004                        .expect("compound assign token should convert to binop");
1005
1006                    let right = p.parse_indented_expression()?;
1007
1008                    let value = Expr::Binary {
1009                        left: Box::new(Expr::Variable {
1010                            name: name.clone(),
1011                            loc: name_loc,
1012                        }),
1013                        op,
1014                        right: Box::new(right),
1015                        loc: op_loc,
1016                    };
1017                    Ok(Stmt::Assignment {
1018                        target: Expr::Variable {
1019                            name: name.clone(),
1020                            loc: name_loc,
1021                        },
1022                        value,
1023                    })
1024                } else {
1025                    // Not an assignment operator, fail
1026                    Err(ParserError::UnexpectedToken(
1027                        p.peek().typ.clone(),
1028                        p.peek().line,
1029                    ))
1030                }
1031            }) {
1032                return Ok(stmt);
1033            }
1034        }
1035
1036        self.expression_statement()
1037    }
1038
1039    fn function_params(&mut self) -> Result<Vec<pine_ast::FunctionParam>, ParserError> {
1040        self.parse_comma_separated(&TokenType::RParen, |p| {
1041            // Parse optional type qualifier (const, input, simple, series)
1042            let type_qualifier = p.parse_optional_type_qualifier();
1043
1044            // Parse optional type annotation
1045            let type_annotation = p.parse_optional_type_annotation();
1046
1047            let param_loc = p.cur_loc();
1048            let name = p.expect_identifier()?;
1049
1050            // Check for default value: param = value
1051            let default_value = if p.match_token(&[TokenType::Assign]) {
1052                Some(p.expression()?)
1053            } else {
1054                None
1055            };
1056
1057            Ok(pine_ast::FunctionParam {
1058                type_qualifier,
1059                type_annotation,
1060                name,
1061                default_value,
1062                loc: param_loc,
1063            })
1064        })
1065    }
1066
1067    fn for_statement(&mut self) -> Result<Stmt, ParserError> {
1068        // Check if it's a tuple form: for [index, item] in collection
1069        if self.check(&TokenType::LBracket) {
1070            self.advance(); // consume [
1071
1072            let loc = self.cur_loc();
1073            let index_var = self.expect_identifier()?;
1074
1075            self.consume(TokenType::Comma, "Expected ',' in for...in tuple")?;
1076
1077            let item_var = self.expect_identifier()?;
1078
1079            self.consume(TokenType::RBracket, "Expected ']' after for...in tuple")?;
1080            self.consume(TokenType::In, "Expected 'in' in for...in loop")?;
1081
1082            let collection = self.expression()?;
1083
1084            // Skip optional newline
1085            self.match_token(&[TokenType::Newline]);
1086
1087            let body = self.parse_block()?;
1088
1089            return Ok(Stmt::ForIn {
1090                index_var: Some(index_var),
1091                item_var,
1092                collection,
1093                body,
1094                loc,
1095            });
1096        }
1097
1098        // Parse variable name
1099        let loc = self.cur_loc();
1100        let var_name = self.expect_identifier()?;
1101
1102        // Check if it's for...in (simple form) or for...to
1103        if self.check(&TokenType::In) {
1104            self.advance(); // consume 'in'
1105
1106            let collection = self.expression()?;
1107
1108            // Skip optional newline
1109            self.match_token(&[TokenType::Newline]);
1110
1111            let body = self.parse_block()?;
1112
1113            Ok(Stmt::ForIn {
1114                index_var: None,
1115                item_var: var_name,
1116                collection,
1117                body,
1118                loc,
1119            })
1120        } else {
1121            // Traditional for...to loop
1122            self.consume(TokenType::Assign, "Expected '=' in for loop")?;
1123            let from = self.expression()?;
1124            self.consume(TokenType::To, "Expected 'to' in for loop")?;
1125            let to = self.expression()?;
1126
1127            // Optional `by <step>`. `by` is a contextual keyword (not lexed as
1128            // one), so match it as an identifier here.
1129            let step = if matches!(&self.peek().typ, TokenType::Ident(name) if name == "by") {
1130                self.advance(); // consume 'by'
1131                Some(self.expression()?)
1132            } else {
1133                None
1134            };
1135
1136            // Skip optional newline after to/step
1137            self.match_token(&[TokenType::Newline]);
1138
1139            // Parse the body - multiple statements
1140            let body = self.parse_block()?;
1141
1142            Ok(Stmt::For {
1143                var_name,
1144                from,
1145                to,
1146                step,
1147                body,
1148                loc,
1149            })
1150        }
1151    }
1152
1153    fn while_statement(&mut self) -> Result<Stmt, ParserError> {
1154        // Parse: while condition
1155        let condition = self.expression()?;
1156
1157        // Skip optional newline after condition
1158        self.match_token(&[TokenType::Newline]);
1159
1160        // Parse the body - multiple statements
1161        let body = self.parse_block()?;
1162
1163        Ok(Stmt::While { condition, body })
1164    }
1165
1166    fn if_statement(&mut self) -> Result<Stmt, ParserError> {
1167        // Parse the condition (no parentheses required in PineScript)
1168        let condition = self.expression()?;
1169
1170        // Skip optional newline after condition
1171        self.match_token(&[TokenType::Newline]);
1172
1173        // Parse the then branch - multiple statements until we hit 'else', dedent, or certain keywords
1174        let then_branch = self.parse_block()?;
1175
1176        // Parse else if branches
1177        let mut else_if_branches = Vec::new();
1178
1179        loop {
1180            // Skip any newlines before else
1181            self.skip_newlines();
1182
1183            // Check if we have "else if"
1184            if self.check(&TokenType::Else) {
1185                if let Some((else_if_condition, else_if_body)) = self.try_parse(|p| {
1186                    p.advance(); // consume 'else'
1187                                 // Check if next token is 'if'
1188                    if p.match_token(&[TokenType::If]) {
1189                        // This is an else if
1190                        let else_if_condition = p.expression()?;
1191                        p.match_token(&[TokenType::Newline]);
1192                        let else_if_body = p.parse_block()?;
1193                        Ok((else_if_condition, else_if_body))
1194                    } else {
1195                        Err(ParserError::UnexpectedToken(
1196                            p.peek().typ.clone(),
1197                            p.peek().line,
1198                        ))
1199                    }
1200                }) {
1201                    else_if_branches.push((else_if_condition, else_if_body));
1202                } else {
1203                    break;
1204                }
1205            } else {
1206                break;
1207            }
1208        }
1209
1210        // Check for final else branch
1211        self.skip_newlines();
1212
1213        let else_branch = if self.match_token(&[TokenType::Else]) {
1214            // Skip optional newline after else
1215            self.match_token(&[TokenType::Newline]);
1216
1217            Some(self.parse_block()?)
1218        } else {
1219            None
1220        };
1221
1222        Ok(Stmt::If {
1223            condition,
1224            then_branch,
1225            else_if_branches,
1226            else_branch,
1227        })
1228    }
1229
1230    fn if_expression(&mut self) -> Result<Expr, ParserError> {
1231        // Consume 'if' token
1232        self.consume(TokenType::If, "Expected 'if'")?;
1233
1234        // Parse the condition
1235        let condition = self.expression()?;
1236
1237        // Skip optional newline after condition
1238        self.match_token(&[TokenType::Newline]);
1239
1240        // Skip optional indent
1241        self.match_token(&[TokenType::Indent]);
1242
1243        // Parse the then expression (single expression, not a block of statements)
1244        let then_expr = self.expression()?;
1245
1246        // Skip newlines and dedent
1247        self.skip_newlines();
1248        self.match_token(&[TokenType::Dedent]);
1249
1250        // Parse else if branches
1251        let mut else_if_branches = Vec::new();
1252
1253        loop {
1254            // Skip any newlines before else
1255            self.skip_newlines();
1256
1257            // Check if we have "else if"
1258            if self.check(&TokenType::Else) {
1259                if let Some((else_if_condition, else_if_expr)) = self.try_parse(|p| {
1260                    p.advance(); // consume 'else'
1261                                 // Check if next token is 'if'
1262                    if p.match_token(&[TokenType::If]) {
1263                        // This is an else if
1264                        let else_if_condition = p.expression()?;
1265                        p.match_token(&[TokenType::Newline]);
1266                        p.match_token(&[TokenType::Indent]);
1267                        let else_if_expr = p.expression()?;
1268                        p.skip_newlines();
1269                        p.match_token(&[TokenType::Dedent]);
1270                        Ok((else_if_condition, else_if_expr))
1271                    } else {
1272                        Err(ParserError::UnexpectedToken(
1273                            p.peek().typ.clone(),
1274                            p.peek().line,
1275                        ))
1276                    }
1277                }) {
1278                    else_if_branches.push((else_if_condition, else_if_expr));
1279                } else {
1280                    break;
1281                }
1282            } else {
1283                break;
1284            }
1285        }
1286
1287        // Parse final else branch (optional - if not present, returns na)
1288        self.skip_newlines();
1289        let else_expr = if self.match_token(&[TokenType::Else]) {
1290            // Skip optional newline after else
1291            self.match_token(&[TokenType::Newline]);
1292
1293            // Skip optional indent
1294            self.match_token(&[TokenType::Indent]);
1295
1296            // Parse else expression
1297            let expr = self.expression()?;
1298
1299            // Skip newlines and optional dedent
1300            self.skip_newlines();
1301            self.match_token(&[TokenType::Dedent]);
1302
1303            Some(Box::new(expr))
1304        } else {
1305            None // Will return na if no branch matches
1306        };
1307
1308        Ok(Expr::IfExpr {
1309            condition: Box::new(condition),
1310            then_expr: Box::new(then_expr),
1311            else_if_branches,
1312            else_expr,
1313        })
1314    }
1315
1316    fn parse_block(&mut self) -> Result<Vec<Stmt>, ParserError> {
1317        let mut stmts = vec![];
1318
1319        // Expect an indent token to start the block
1320        if !self.match_token(&[TokenType::Indent]) {
1321            // No indent means single-line block or empty block
1322            // Try to parse a single statement on the same line
1323            if !self.check(&TokenType::Newline)
1324                && !self.check(&TokenType::Else)
1325                && !self.is_at_end()
1326            {
1327                stmts.push(self.declaration()?);
1328            }
1329            return Ok(stmts);
1330        }
1331
1332        // Parse statements until we hit a dedent
1333        loop {
1334            // Skip leading newlines
1335            self.skip_newlines();
1336
1337            // Check for else (which ends the then branch)
1338            if self.check(&TokenType::Else) {
1339                break;
1340            }
1341
1342            // Check for end of block
1343            if self.check(&TokenType::Dedent) {
1344                self.advance(); // consume the dedent
1345
1346                // Check if else follows the dedent
1347                self.skip_newlines();
1348                if self.check(&TokenType::Else) {
1349                    break;
1350                }
1351
1352                // If not else, we're truly done
1353                break;
1354            }
1355
1356            // Stop at EOF
1357            if self.is_at_end() {
1358                break;
1359            }
1360
1361            // Parse a statement
1362            stmts.push(self.declaration()?);
1363        }
1364
1365        Ok(stmts)
1366    }
1367
1368    fn expression_statement(&mut self) -> Result<Stmt, ParserError> {
1369        let expr = self.expression()?;
1370
1371        // Check if this is an assignment statement (e.g., obj.field := value)
1372        if self.match_token(&[TokenType::ColonAssign]) {
1373            let value = self.parse_indented_expression()?;
1374            return Ok(Stmt::Assignment {
1375                target: expr,
1376                value,
1377            });
1378        }
1379
1380        Ok(Stmt::Expression(expr))
1381    }
1382
1383    // Expression parsing with precedence
1384    fn expression(&mut self) -> Result<Expr, ParserError> {
1385        self.ternary()
1386    }
1387
1388    /// Generic binary operator parser using left-associativity
1389    fn binary_left_assoc(
1390        &mut self,
1391        operators: &[TokenType],
1392        next_precedence: fn(&mut Self) -> Result<Expr, ParserError>,
1393    ) -> Result<Expr, ParserError> {
1394        let mut expr = next_precedence(self)?;
1395
1396        loop {
1397            // Skip newlines before operators (for leading operators on continuation lines)
1398            self.skip_newlines();
1399
1400            if !self.match_token(operators) {
1401                break;
1402            }
1403
1404            let op_tok = &self.tokens[self.current - 1];
1405            let op_loc = Loc::new(op_tok.line as u32, op_tok.column as u32);
1406            let op = op_tok
1407                .typ
1408                .to_binop()
1409                .expect("matched operator token should convert to binop");
1410
1411            // Skip newlines after binary operators (for multi-line expressions)
1412            self.skip_newlines_and_indent();
1413
1414            let right = next_precedence(self)?;
1415            expr = Expr::Binary {
1416                left: Box::new(expr),
1417                op,
1418                right: Box::new(right),
1419                loc: op_loc,
1420            };
1421        }
1422
1423        Ok(expr)
1424    }
1425
1426    fn ternary(&mut self) -> Result<Expr, ParserError> {
1427        // Check for if expression first
1428        if self.check(&TokenType::If) {
1429            return self.if_expression();
1430        }
1431
1432        let mut expr = self.logical_or()?;
1433
1434        // Skip newlines before '?' for multi-line ternaries
1435        self.skip_newlines();
1436
1437        // Skip indent if followed by '?' (for multiline ternaries)
1438        self.try_consume_indent_if_followed_by(&TokenType::Question);
1439
1440        if self.match_token(&[TokenType::Question]) {
1441            // Skip newlines after '?'
1442            self.skip_newlines_and_indent();
1443
1444            let then_expr = self.expression()?;
1445
1446            // Skip newlines before ':'
1447            self.skip_newlines();
1448
1449            // Skip indent if followed by ':' (for multiline ternaries)
1450            self.try_consume_indent_if_followed_by(&TokenType::Colon);
1451
1452            self.consume(TokenType::Colon, "Expected ':' in ternary expression")?;
1453
1454            // Skip newlines after ':'
1455            self.skip_newlines_and_indent();
1456
1457            let else_expr = self.expression()?;
1458            expr = Expr::Ternary {
1459                condition: Box::new(expr),
1460                then_expr: Box::new(then_expr),
1461                else_expr: Box::new(else_expr),
1462            };
1463        }
1464
1465        Ok(expr)
1466    }
1467
1468    fn logical_or(&mut self) -> Result<Expr, ParserError> {
1469        self.binary_left_assoc(&[TokenType::Or], Self::logical_and)
1470    }
1471
1472    fn logical_and(&mut self) -> Result<Expr, ParserError> {
1473        self.binary_left_assoc(&[TokenType::And], Self::equality)
1474    }
1475
1476    fn equality(&mut self) -> Result<Expr, ParserError> {
1477        self.binary_left_assoc(&[TokenType::Equal, TokenType::NotEqual], Self::comparison)
1478    }
1479
1480    fn comparison(&mut self) -> Result<Expr, ParserError> {
1481        self.binary_left_assoc(
1482            &[
1483                TokenType::Greater,
1484                TokenType::Less,
1485                TokenType::GreaterEqual,
1486                TokenType::LessEqual,
1487            ],
1488            Self::term,
1489        )
1490    }
1491
1492    fn term(&mut self) -> Result<Expr, ParserError> {
1493        let mut expr = self.factor()?;
1494
1495        loop {
1496            // Skip newlines before operators (for leading operators on continuation lines)
1497            self.skip_newlines();
1498
1499            // Skip indent/dedent if followed by an operator (for leading operators on continuation lines)
1500            self.try_consume_layout_token_if_followed_by(&[TokenType::Plus, TokenType::Minus]);
1501
1502            if !self.match_token(&[TokenType::Plus, TokenType::Minus]) {
1503                break;
1504            }
1505
1506            let op_tok = &self.tokens[self.current - 1];
1507            let op_loc = Loc::new(op_tok.line as u32, op_tok.column as u32);
1508            let op = op_tok
1509                .typ
1510                .to_binop()
1511                .expect("term token should convert to binop");
1512            // Skip newlines after binary operators (for multi-line expressions)
1513            self.skip_newlines_and_indent();
1514            let right = self.factor()?;
1515            expr = Expr::Binary {
1516                left: Box::new(expr),
1517                op,
1518                right: Box::new(right),
1519                loc: op_loc,
1520            };
1521        }
1522
1523        Ok(expr)
1524    }
1525
1526    fn factor(&mut self) -> Result<Expr, ParserError> {
1527        self.binary_left_assoc(
1528            &[TokenType::Star, TokenType::Slash, TokenType::Percent],
1529            Self::unary,
1530        )
1531    }
1532
1533    fn unary(&mut self) -> Result<Expr, ParserError> {
1534        if self.match_token(&[TokenType::Minus]) {
1535            let expr = self.unary()?;
1536            return Ok(Expr::Unary {
1537                op: UnOp::Neg,
1538                expr: Box::new(expr),
1539            });
1540        }
1541
1542        if self.match_token(&[TokenType::Not]) {
1543            let expr = self.unary()?;
1544            return Ok(Expr::Unary {
1545                op: UnOp::Not,
1546                expr: Box::new(expr),
1547            });
1548        }
1549
1550        self.postfix()
1551    }
1552
1553    fn postfix(&mut self) -> Result<Expr, ParserError> {
1554        let mut expr = self.primary()?;
1555
1556        // A `switch`/`if` block expression spans lines and terminates the
1557        // expression. A following `[`/`.`/`(` begins a new statement, not a
1558        // postfix operator on the block's result (its trailing NEWLINE/DEDENT
1559        // has already been consumed, so the loop below can't see the boundary).
1560        if matches!(expr, Expr::Switch { .. } | Expr::IfExpr { .. }) {
1561            return Ok(expr);
1562        }
1563
1564        loop {
1565            if self.match_token(&[TokenType::Dot]) {
1566                // Member access: expr.member
1567                // Allow keywords as member names (e.g., input.int, color.new)
1568                let member = match &self.peek().typ {
1569                    TokenType::Ident(name) => {
1570                        let name = name.clone();
1571                        self.advance();
1572                        name
1573                    }
1574                    TokenType::Int => {
1575                        self.advance();
1576                        "int".to_string()
1577                    }
1578                    TokenType::Float => {
1579                        self.advance();
1580                        "float".to_string()
1581                    }
1582                    _ => {
1583                        // Try to use the lexeme if it's a keyword
1584                        let lexeme = self.peek().lexeme.clone();
1585                        if !lexeme.is_empty() {
1586                            self.advance();
1587                            lexeme
1588                        } else {
1589                            return Err(ParserError::ExpectedIdentifierAfterDot(self.peek().line));
1590                        }
1591                    }
1592                };
1593                let member_loc = self.prev_loc();
1594                expr = Expr::MemberAccess {
1595                    object: Box::new(expr),
1596                    member,
1597                    member_loc,
1598                };
1599            } else if self.match_token(&[TokenType::LBracket]) {
1600                // Historical reference: expr[index]
1601                let index = self.expression()?;
1602                self.consume(TokenType::RBracket, "Expected ']'")?;
1603                expr = Expr::Index {
1604                    expr: Box::new(expr),
1605                    index: Box::new(index),
1606                    id: self.next_call_id(),
1607                };
1608            } else if self.check(&TokenType::Less) {
1609                // Try to parse type arguments: <type>
1610                // This is tricky because < can also be a comparison operator
1611                // We use try_parse to backtrack if it's not actually type args
1612                let type_args = self.try_parse_type_args().unwrap_or_default();
1613
1614                // After type args, we must have a function call
1615                if self.match_token(&[TokenType::LParen]) {
1616                    let lparen = &self.tokens[self.current - 1];
1617                    let call_loc = Loc::new(lparen.line as u32, lparen.column as u32);
1618                    let id = self.next_call_id();
1619                    let args = self.arguments()?;
1620                    self.consume(TokenType::RParen, "Expected ')'")?;
1621                    expr = Expr::Call {
1622                        callee: Box::new(expr),
1623                        type_args,
1624                        args,
1625                        id,
1626                        loc: call_loc,
1627                    };
1628                } else {
1629                    // Not a function call, just break
1630                    break;
1631                }
1632            } else if self.match_token(&[TokenType::LParen]) {
1633                // Function call without type arguments
1634                let lparen = &self.tokens[self.current - 1];
1635                let call_loc = Loc::new(lparen.line as u32, lparen.column as u32);
1636                let id = self.next_call_id();
1637                let args = self.arguments()?;
1638                self.consume(TokenType::RParen, "Expected ')'")?;
1639                expr = Expr::Call {
1640                    callee: Box::new(expr),
1641                    type_args: vec![],
1642                    args,
1643                    id,
1644                    loc: call_loc,
1645                };
1646            } else {
1647                break;
1648            }
1649        }
1650
1651        Ok(expr)
1652    }
1653
1654    fn arguments(&mut self) -> Result<Vec<Argument>, ParserError> {
1655        let mut args = vec![];
1656
1657        if !self.check(&TokenType::RParen) {
1658            loop {
1659                // Check for named argument: name=value
1660                // In PineScript, function calls can have named arguments like plot(x, title="foo", color=red)
1661                // `type` is a keyword (v5 UDTs) but is also v3/v4's `input(..., type=...)`
1662                // parameter name, so accept it as a key too.
1663                let key_name = match &self.peek().typ {
1664                    TokenType::Ident(name) => Some(name.clone()),
1665                    TokenType::Type => Some("type".to_string()),
1666                    _ => None,
1667                };
1668                if let Some(name) = key_name {
1669                    if let Some((name, value)) = self.try_parse(|p| {
1670                        p.advance(); // consume identifier
1671                        if p.check(&TokenType::Assign) {
1672                            // This is a named argument
1673                            p.advance(); // consume =
1674                            let value = p.expression()?;
1675                            Ok((name.clone(), value))
1676                        } else {
1677                            Err(ParserError::UnexpectedToken(
1678                                p.peek().typ.clone(),
1679                                p.peek().line,
1680                            ))
1681                        }
1682                    }) {
1683                        args.push(Argument::Named { name, value });
1684                    } else {
1685                        // Not a named argument, parse as expression
1686                        let expr = self.expression()?;
1687                        args.push(Argument::Positional(expr));
1688                    }
1689                } else {
1690                    let expr = self.expression()?;
1691                    args.push(Argument::Positional(expr));
1692                }
1693
1694                if !self.match_token(&[TokenType::Comma]) {
1695                    break;
1696                }
1697            }
1698        }
1699
1700        Ok(args)
1701    }
1702
1703    fn primary(&mut self) -> Result<Expr, ParserError> {
1704        if let TokenType::IntLiteral(n) = self.peek().typ {
1705            self.advance();
1706            return Ok(Expr::Literal(Literal::Int(n)));
1707        }
1708
1709        if let TokenType::Number(n) = self.peek().typ {
1710            self.advance();
1711            return Ok(Expr::Literal(Literal::Number(n)));
1712        }
1713
1714        if let TokenType::String(ref s) = self.peek().typ {
1715            let s = s.clone();
1716            self.advance();
1717            return Ok(Expr::Literal(Literal::String(s)));
1718        }
1719
1720        if let TokenType::Bool(b) = self.peek().typ {
1721            self.advance();
1722            return Ok(Expr::Literal(Literal::Bool(b)));
1723        }
1724
1725        if let TokenType::HexColor(ref hex) = self.peek().typ {
1726            let hex = hex.clone();
1727            self.advance();
1728            return Ok(Expr::Literal(Literal::HexColor(hex)));
1729        }
1730
1731        // Handle na as a literal
1732        if self.match_token(&[TokenType::Na]) {
1733            return Ok(Expr::Literal(Literal::Na));
1734        }
1735
1736        // Handle keywords that can be used as identifiers (int, float)
1737        // These can be function names (e.g., int(), float())
1738        if self.match_token(&[TokenType::Int, TokenType::Float]) {
1739            let name = self.tokens[self.current - 1].lexeme.clone();
1740            return Ok(Expr::Variable {
1741                name,
1742                loc: self.prev_loc(),
1743            });
1744        }
1745
1746        if let TokenType::Ident(ref name) = self.peek().typ {
1747            let name = name.clone();
1748            let loc = self.cur_loc();
1749            self.advance();
1750            return Ok(Expr::Variable { name, loc });
1751        }
1752
1753        if self.match_token(&[TokenType::LParen]) {
1754            // Skip newlines and indents after opening parenthesis for multiline expressions
1755            self.skip_newlines();
1756            let had_indent = self.match_token(&[TokenType::Indent]);
1757
1758            let expr = self.expression()?;
1759
1760            // Skip newlines and consume dedent if we had indent
1761            self.skip_newlines();
1762            if had_indent {
1763                self.match_token(&[TokenType::Dedent]);
1764            }
1765
1766            self.consume(TokenType::RParen, "Expected ')'")?;
1767            return Ok(expr);
1768        }
1769
1770        // Switch expression: switch value \n case => result
1771        if self.match_token(&[TokenType::Switch]) {
1772            // The subjectless form (`switch` with boolean arms) has no expression
1773            // after the keyword — just a newline. Treat it as `switch true`, so an
1774            // arm matches when its condition evaluates to `true`.
1775            let value = if self.check(&TokenType::Newline) {
1776                Box::new(Expr::Literal(Literal::Bool(true)))
1777            } else {
1778                Box::new(self.expression()?)
1779            };
1780
1781            // Skip newline after switch value
1782            self.match_token(&[TokenType::Newline]);
1783
1784            // Skip indent for switch block
1785            let has_indent = self.match_token(&[TokenType::Indent]);
1786
1787            let mut cases = vec![];
1788
1789            // Parse cases until we can't parse any more
1790            loop {
1791                // Skip leading newlines
1792                self.skip_newlines();
1793
1794                // Check for dedent (end of switch block)
1795                if self.check(&TokenType::Dedent) {
1796                    if has_indent {
1797                        self.advance(); // consume dedent
1798                    }
1799                    break;
1800                }
1801
1802                // Check if we're done (end of block or EOF)
1803                if self.is_at_end() {
1804                    break;
1805                }
1806
1807                // Check for default case: => result (no pattern)
1808                if self.match_token(&[TokenType::Arrow]) {
1809                    // Skip newlines after =>
1810                    self.skip_newlines();
1811
1812                    // Parse the result expression
1813                    let result = self.expression()?;
1814
1815                    // Use a special "default" literal as the pattern
1816                    let default_pattern = Expr::Literal(Literal::Bool(true));
1817                    cases.push((default_pattern, result));
1818                    continue;
1819                }
1820
1821                // Try to parse a case
1822                if let Some((pattern, result)) = self.try_parse(|p| {
1823                    // Parse the pattern (could be a string, number, identifier, etc.)
1824                    let pattern = p.expression()?;
1825
1826                    // Expect =>
1827                    if !p.match_token(&[TokenType::Arrow]) {
1828                        return Err(ParserError::UnexpectedToken(
1829                            p.peek().typ.clone(),
1830                            p.peek().line,
1831                        ));
1832                    }
1833
1834                    // Skip newlines after =>
1835                    p.skip_newlines();
1836
1837                    // Parse the result expression
1838                    let result = p.expression()?;
1839                    Ok((pattern, result))
1840                }) {
1841                    cases.push((pattern, result));
1842                } else {
1843                    break;
1844                }
1845            }
1846
1847            return Ok(Expr::Switch { value, cases });
1848        }
1849
1850        // Array literal: [1, 2, 3]
1851        if self.match_token(&[TokenType::LBracket]) {
1852            // Skip leading newlines
1853            self.skip_newlines_and_indent();
1854
1855            let elements = self.parse_comma_separated(&TokenType::RBracket, |p| p.expression())?;
1856
1857            // Skip trailing newlines and dedent
1858            self.skip_newlines_and_dedent();
1859
1860            self.consume(TokenType::RBracket, "Expected ']'")?;
1861            return Ok(Expr::Array(elements));
1862        }
1863
1864        Err(ParserError::UnexpectedToken(
1865            self.peek().typ.clone(),
1866            self.peek().line,
1867        ))
1868    }
1869}
1870
1871#[cfg(test)]
1872mod tests {
1873    use super::*;
1874    use pine_lexer::Lexer;
1875
1876    fn parse_expr(input: &str) -> eyre::Result<Expr> {
1877        let mut lexer = Lexer::new(input);
1878        let tokens = lexer.tokenize()?;
1879        let mut parser = Parser::new(tokens);
1880        let stmts = parser.parse()?;
1881
1882        if let Some(Stmt::Expression(expr)) = stmts.first() {
1883            Ok(expr.clone())
1884        } else {
1885            Err(eyre::eyre!("Expected expression statement".to_string()))
1886        }
1887    }
1888
1889    #[test]
1890    fn test_literals() {
1891        // Numbers
1892        let expr = parse_expr("42").unwrap();
1893        assert_eq!(expr, Expr::Literal(Literal::Int(42)));
1894
1895        // Strings
1896        let expr = parse_expr(r#""hello""#).unwrap();
1897        assert_eq!(expr, Expr::Literal(Literal::String("hello".to_string())));
1898
1899        // Booleans
1900        let expr = parse_expr("true").unwrap();
1901        assert_eq!(expr, Expr::Literal(Literal::Bool(true)));
1902    }
1903
1904    #[test]
1905    fn test_variables() {
1906        let expr = parse_expr("close").unwrap();
1907        assert_eq!(expr, Expr::var("close"));
1908
1909        let expr = parse_expr("my_var").unwrap();
1910        assert_eq!(expr, Expr::var("my_var"));
1911    }
1912
1913    #[test]
1914    fn test_historical_references() {
1915        // close[1] - previous close
1916        let expr = parse_expr("close[1]").unwrap();
1917        assert!(matches!(expr, Expr::Index { .. }));
1918        if let Expr::Index {
1919            expr: base, index, ..
1920        } = expr
1921        {
1922            assert_eq!(*base, Expr::var("close"));
1923            assert_eq!(*index, Expr::Literal(Literal::Int(1)));
1924        }
1925
1926        // high[5] - 5 bars ago
1927        let expr = parse_expr("high[5]").unwrap();
1928        if let Expr::Index {
1929            expr: base, index, ..
1930        } = expr
1931        {
1932            assert_eq!(*base, Expr::var("high"));
1933            assert_eq!(*index, Expr::Literal(Literal::Int(5)));
1934        }
1935    }
1936
1937    #[test]
1938    fn test_function_calls() {
1939        // Simple function call
1940        let expr = parse_expr("sma(close, 14)").unwrap();
1941        if let Expr::Call {
1942            callee,
1943            type_args,
1944            args,
1945            ..
1946        } = expr
1947        {
1948            assert_eq!(*callee, Expr::var("sma"));
1949            assert_eq!(type_args.len(), 0);
1950            assert_eq!(args.len(), 2);
1951            assert_eq!(args[0], Argument::Positional(Expr::var("close")));
1952            assert_eq!(
1953                args[1],
1954                Argument::Positional(Expr::Literal(Literal::Int(14)))
1955            );
1956        } else {
1957            panic!("Expected function call");
1958        }
1959
1960        // No arguments
1961        let expr = parse_expr("foo()").unwrap();
1962        if let Expr::Call {
1963            callee,
1964            type_args,
1965            args,
1966            ..
1967        } = expr
1968        {
1969            assert_eq!(*callee, Expr::var("foo"));
1970            assert_eq!(type_args.len(), 0);
1971            assert_eq!(args.len(), 0);
1972        }
1973    }
1974
1975    #[test]
1976    fn test_arithmetic_expressions() {
1977        // Addition
1978        let expr = parse_expr("2 + 3").unwrap();
1979        if let Expr::Binary {
1980            left, op, right, ..
1981        } = expr
1982        {
1983            assert_eq!(*left, Expr::Literal(Literal::Int(2)));
1984            assert_eq!(op, BinOp::Add);
1985            assert_eq!(*right, Expr::Literal(Literal::Int(3)));
1986        }
1987
1988        // Multiplication has higher precedence: 2 + 3 * 4 = 2 + (3 * 4)
1989        let expr = parse_expr("2 + 3 * 4").unwrap();
1990        if let Expr::Binary {
1991            left,
1992            op: op1,
1993            right,
1994            ..
1995        } = expr
1996        {
1997            assert_eq!(*left, Expr::Literal(Literal::Int(2)));
1998            assert_eq!(op1, BinOp::Add);
1999            if let Expr::Binary {
2000                left: l2,
2001                op: op2,
2002                right: r2,
2003                ..
2004            } = *right
2005            {
2006                assert_eq!(*l2, Expr::Literal(Literal::Int(3)));
2007                assert_eq!(op2, BinOp::Mul);
2008                assert_eq!(*r2, Expr::Literal(Literal::Int(4)));
2009            }
2010        }
2011
2012        // Division
2013        let expr = parse_expr("10 / 2").unwrap();
2014        if let Expr::Binary {
2015            left, op, right, ..
2016        } = expr
2017        {
2018            assert_eq!(*left, Expr::Literal(Literal::Int(10)));
2019            assert_eq!(op, BinOp::Div);
2020            assert_eq!(*right, Expr::Literal(Literal::Int(2)));
2021        }
2022
2023        // Subtraction
2024        let expr = parse_expr("5 - 3").unwrap();
2025        if let Expr::Binary {
2026            left, op, right, ..
2027        } = expr
2028        {
2029            assert_eq!(*left, Expr::Literal(Literal::Int(5)));
2030            assert_eq!(op, BinOp::Sub);
2031            assert_eq!(*right, Expr::Literal(Literal::Int(3)));
2032        }
2033    }
2034
2035    #[test]
2036    fn test_comparison_expressions() {
2037        // Greater than
2038        let expr = parse_expr("close > open").unwrap();
2039        if let Expr::Binary {
2040            left, op, right, ..
2041        } = expr
2042        {
2043            assert_eq!(*left, Expr::var("close"));
2044            assert_eq!(op, BinOp::Greater);
2045            assert_eq!(*right, Expr::var("open"));
2046        }
2047
2048        // Less than
2049        let expr = parse_expr("rsi < 30").unwrap();
2050        if let Expr::Binary {
2051            left, op, right, ..
2052        } = expr
2053        {
2054            assert_eq!(*left, Expr::var("rsi"));
2055            assert_eq!(op, BinOp::Less);
2056            assert_eq!(*right, Expr::Literal(Literal::Int(30)));
2057        }
2058
2059        // Equality
2060        let expr = parse_expr("x == 5").unwrap();
2061        if let Expr::Binary {
2062            left, op, right, ..
2063        } = expr
2064        {
2065            assert_eq!(*left, Expr::var("x"));
2066            assert_eq!(op, BinOp::Eq);
2067            assert_eq!(*right, Expr::Literal(Literal::Int(5)));
2068        }
2069    }
2070
2071    #[test]
2072    fn test_unary_expressions() {
2073        // Negation
2074        let expr = parse_expr("-5").unwrap();
2075        if let Expr::Unary { op, expr } = expr {
2076            assert_eq!(op, UnOp::Neg);
2077            assert_eq!(*expr, Expr::Literal(Literal::Int(5)));
2078        }
2079
2080        // Double negation
2081        let expr = parse_expr("--10").unwrap();
2082        if let Expr::Unary { op: op1, expr: e1 } = expr {
2083            assert_eq!(op1, UnOp::Neg);
2084            if let Expr::Unary { op: op2, expr: e2 } = *e1 {
2085                assert_eq!(op2, UnOp::Neg);
2086                assert_eq!(*e2, Expr::Literal(Literal::Int(10)));
2087            }
2088        }
2089    }
2090
2091    #[test]
2092    fn test_var_declarations() {
2093        let mut lexer = Lexer::new("var x = 10");
2094        let tokens = lexer.tokenize().unwrap();
2095        let mut parser = Parser::new(tokens);
2096        let stmts = parser.parse().unwrap();
2097
2098        assert_eq!(stmts.len(), 1);
2099        if let Stmt::VarDecl {
2100            name,
2101            type_qualifier,
2102            type_annotation,
2103            initializer,
2104            var_kind,
2105            ..
2106        } = &stmts[0]
2107        {
2108            assert_eq!(name, "x");
2109            assert_eq!(*type_qualifier, None);
2110            assert_eq!(*type_annotation, None);
2111            assert_eq!(*var_kind, VarKind::Var, "var x = 10 must be Var");
2112            assert_eq!(
2113                initializer.as_ref().unwrap(),
2114                &Expr::Literal(Literal::Int(10))
2115            );
2116        } else {
2117            panic!("Expected VarDecl");
2118        }
2119
2120        // Var without initializer
2121        let mut lexer = Lexer::new("var y");
2122        let tokens = lexer.tokenize().unwrap();
2123        let mut parser = Parser::new(tokens);
2124        let stmts = parser.parse().unwrap();
2125
2126        if let Stmt::VarDecl {
2127            name, initializer, ..
2128        } = &stmts[0]
2129        {
2130            assert_eq!(name, "y");
2131            assert!(initializer.is_none());
2132        }
2133    }
2134
2135    #[test]
2136    fn test_pinescript_examples() {
2137        // PineScript: close[1] > close[2]
2138        let expr = parse_expr("close[1] > close[2]").unwrap();
2139        assert!(matches!(
2140            expr,
2141            Expr::Binary {
2142                op: BinOp::Greater,
2143                ..
2144            }
2145        ));
2146
2147        // PineScript: sma(close, 14) > sma(close, 28)
2148        let expr = parse_expr("sma(close, 14) > sma(close, 28)").unwrap();
2149        if let Expr::Binary {
2150            left, op, right, ..
2151        } = expr
2152        {
2153            assert_eq!(op, BinOp::Greater);
2154            assert!(matches!(*left, Expr::Call { .. }));
2155            assert!(matches!(*right, Expr::Call { .. }));
2156        }
2157
2158        // PineScript: (high + low) / 2
2159        let expr = parse_expr("(high + low) / 2").unwrap();
2160        if let Expr::Binary {
2161            left,
2162            op: div_op,
2163            right,
2164            ..
2165        } = expr
2166        {
2167            assert_eq!(div_op, BinOp::Div);
2168            assert!(matches!(*left, Expr::Binary { op: BinOp::Add, .. }));
2169            assert_eq!(*right, Expr::Literal(Literal::Int(2)));
2170        }
2171    }
2172
2173    /// Helper function to recursively collect all .pine files in a directory
2174    fn collect_pine_files_recursive(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
2175        walkdir::WalkDir::new(dir)
2176            .into_iter()
2177            .filter_map(|e| e.ok())
2178            .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("pine"))
2179            .map(|e| e.path().to_path_buf())
2180            .collect()
2181    }
2182
2183    #[test]
2184    fn test_parse_testdata_files() -> eyre::Result<()> {
2185        use std::fs;
2186        use std::path::Path;
2187
2188        let testdata_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata");
2189
2190        let filter = std::env::var("TEST_FILE").ok();
2191        let debug = std::env::var("DEBUG").is_ok();
2192        let generate_ast = std::env::var("GENERATE_AST").is_ok();
2193
2194        let pine_files = collect_pine_files_recursive(&testdata_dir);
2195
2196        let process_file = |path: &std::path::PathBuf| -> eyre::Result<()> {
2197            let content = fs::read_to_string(path)?;
2198
2199            let mut lexer = Lexer::new(&content);
2200            let tokens = lexer.tokenize()?;
2201
2202            if debug {
2203                println!("Tokens: {:#?}", tokens);
2204            }
2205
2206            let mut parser = Parser::new(tokens);
2207            let ast = parser.parse()?;
2208
2209            if debug {
2210                let ast_json = serde_json::to_string(&ast)?;
2211                println!("AST JSON: {:?}", ast_json);
2212            }
2213
2214            // Check for corresponding _ast.json file
2215            let json_path = path.with_file_name(format!(
2216                "{}_ast.json",
2217                path.file_stem().unwrap().to_str().unwrap()
2218            ));
2219
2220            if generate_ast {
2221                // Generate/overwrite AST JSON file
2222                let json = serde_json::to_string_pretty(&ast)?;
2223                fs::write(&json_path, &json)?;
2224            } else if json_path.exists() {
2225                // Compare with expected AST
2226                let expected_json = fs::read_to_string(&json_path)?;
2227                let expected_ast: Vec<Stmt> = serde_json::from_str(&expected_json)?;
2228
2229                if ast != expected_ast {
2230                    return Err(eyre::eyre!(
2231                        "AST mismatch, expected AST from {:?}",
2232                        json_path
2233                    ));
2234                }
2235            }
2236
2237            Ok(())
2238        };
2239
2240        for path in pine_files {
2241            let filename = path.file_name().unwrap().to_str().unwrap();
2242
2243            // Skip if filter is set and doesn't match
2244            if let Some(ref filter_name) = filter {
2245                if filename != filter_name {
2246                    continue;
2247                }
2248            }
2249
2250            if let Err(e) = process_file(&path) {
2251                return Err(eyre::eyre!("Failed to process {}: {}", filename, e));
2252            }
2253        }
2254
2255        Ok(())
2256    }
2257
2258    #[test]
2259    #[ignore]
2260    fn test_parse_external_pinescript_indicators() -> eyre::Result<()> {
2261        use std::fs;
2262        use std::path::Path;
2263
2264        let testdata_dir =
2265            Path::new(env!("CARGO_MANIFEST_DIR")).join("tradingview-pinescript-indicators");
2266
2267        let filter = std::env::var("TEST_FILE").ok();
2268        let debug = std::env::var("DEBUG").is_ok();
2269
2270        let pine_files = collect_pine_files_recursive(&testdata_dir);
2271
2272        let process_file = |path: &std::path::PathBuf| -> eyre::Result<()> {
2273            let content = fs::read_to_string(path)?;
2274
2275            let mut lexer = Lexer::new(&content);
2276            let tokens = lexer.tokenize()?;
2277
2278            if debug {
2279                println!("Tokens: {:#?}", tokens);
2280            }
2281
2282            let mut parser = Parser::new(tokens);
2283            let ast = parser.parse()?;
2284
2285            let ast_json = serde_json::to_string(&ast)?;
2286
2287            if debug {
2288                println!("AST JSON: {:?}", ast_json);
2289            }
2290
2291            Ok(())
2292        };
2293
2294        for path in pine_files {
2295            let filename = path.file_name().unwrap().to_str().unwrap();
2296
2297            // Skip if filter is set and doesn't match
2298            if let Some(ref filter_name) = filter {
2299                if filename != filter_name {
2300                    continue;
2301                }
2302            }
2303
2304            if let Err(e) = process_file(&path) {
2305                return Err(eyre::eyre!("Failed to process {}: {}", filename, e));
2306            }
2307        }
2308
2309        Ok(())
2310    }
2311}