Skip to main content

harn_parser/parser/
state.rs

1use crate::ast::*;
2use harn_lexer::{Span, Token, TokenKind};
3
4use super::error::ParserError;
5
6pub(crate) const MAX_NESTING_DEPTH: usize = 64;
7
8/// Recursive descent parser for Harn.
9pub struct Parser {
10    pub(super) tokens: Vec<Token>,
11    pub(super) pos: usize,
12    pub(super) errors: Vec<ParserError>,
13    nesting_depth: usize,
14}
15
16impl Parser {
17    pub(super) fn at_module_scope(&self) -> bool {
18        self.nesting_depth == 0
19    }
20
21    pub fn new(tokens: Vec<Token>) -> Self {
22        Self {
23            tokens,
24            pos: 0,
25            errors: Vec::new(),
26            nesting_depth: 0,
27        }
28    }
29
30    pub(super) fn check_token_nesting_limit(&self) -> Result<(), ParserError> {
31        let mut depth = 0usize;
32        for token in &self.tokens {
33            match token.kind {
34                TokenKind::LBrace | TokenKind::LBracket | TokenKind::LParen => {
35                    depth += 1;
36                    if depth > MAX_NESTING_DEPTH {
37                        return Err(ParserError::Unexpected {
38                            got: "source nesting depth exceeded".to_string(),
39                            expected: format!(
40                                "parser nesting depth within {MAX_NESTING_DEPTH} levels"
41                            ),
42                            span: token.span,
43                        });
44                    }
45                }
46                TokenKind::RBrace | TokenKind::RBracket | TokenKind::RParen => {
47                    depth = depth.saturating_sub(1);
48                }
49                _ => {}
50            }
51        }
52        Ok(())
53    }
54
55    pub(super) fn current_span(&self) -> Span {
56        self.tokens
57            .get(self.pos)
58            .map(|t| t.span)
59            .unwrap_or(Span::dummy())
60    }
61
62    pub(super) fn current_kind(&self) -> Option<&TokenKind> {
63        self.tokens.get(self.pos).map(|t| &t.kind)
64    }
65
66    pub(super) fn prev_span(&self) -> Span {
67        if self.pos > 0 {
68            self.tokens[self.pos - 1].span
69        } else {
70            Span::dummy()
71        }
72    }
73
74    /// Span of the most recently consumed *non-newline* token. Useful when
75    /// computing a node's end span after the parser has already consumed
76    /// trailing newlines (e.g. while looking ahead for an optional `else` /
77    /// `catch` / `finally` clause). Using `prev_span()` in that position
78    /// would report a newline token whose `end_line` is past the visual end
79    /// of the node, which downstream tools (notably the formatter) interpret
80    /// as belonging to the node.
81    pub(super) fn last_non_newline_span(&self) -> Span {
82        let mut i = self.pos;
83        while i > 0 {
84            i -= 1;
85            if self.tokens[i].kind != TokenKind::Newline {
86                return self.tokens[i].span;
87            }
88        }
89        Span::dummy()
90    }
91
92    /// Parse a complete .harn file. Reports multiple errors via recovery.
93    pub fn parse(&mut self) -> Result<Vec<SNode>, ParserError> {
94        self.check_token_nesting_limit()?;
95
96        let mut nodes = Vec::new();
97        self.skip_newlines();
98
99        while !self.is_at_end() {
100            // Recovery may leave us pointing at a stray `}` at top level; skip it.
101            if self.check(&TokenKind::RBrace) {
102                self.advance();
103                self.skip_newlines();
104                continue;
105            }
106
107            let result = if self.check(&TokenKind::Import) {
108                self.parse_import()
109            } else if self.check(&TokenKind::At) {
110                self.parse_attributed_decl()
111            } else if self.check(&TokenKind::Pipeline) {
112                self.parse_pipeline()
113            } else if self.check(&TokenKind::EvalPack) {
114                self.parse_eval_pack_decl(false)
115            } else {
116                self.parse_statement()
117            };
118
119            match result {
120                Ok(node) => {
121                    let end_line = node.span.end_line;
122                    nodes.push(node);
123                    let consumed_sep = self.consume_statement_separator();
124                    if !consumed_sep && !self.is_at_end() {
125                        self.require_statement_separator(end_line, "top-level item")?;
126                    }
127                }
128                Err(err) => {
129                    self.errors.push(err);
130                    self.synchronize();
131                }
132            }
133        }
134
135        if let Some(first) = self.errors.first() {
136            return Err(first.clone());
137        }
138        Ok(nodes)
139    }
140
141    /// Return all accumulated parser errors (after `parse()` returns).
142    pub fn all_errors(&self) -> &[ParserError] {
143        &self.errors
144    }
145
146    /// Check if the current token is one that starts a statement.
147    pub(super) fn is_statement_start(&self) -> bool {
148        matches!(
149            self.current_kind(),
150            Some(
151                TokenKind::Let
152                    | TokenKind::Const
153                    | TokenKind::Var
154                    | TokenKind::If
155                    | TokenKind::For
156                    | TokenKind::While
157                    | TokenKind::Match
158                    | TokenKind::Retry
159                    | TokenKind::Return
160                    | TokenKind::Throw
161                    | TokenKind::Fn
162                    | TokenKind::Pub
163                    | TokenKind::Try
164                    | TokenKind::Select
165                    | TokenKind::Pipeline
166                    | TokenKind::Import
167                    | TokenKind::Parallel
168                    | TokenKind::Enum
169                    | TokenKind::EvalPack
170                    | TokenKind::Struct
171                    | TokenKind::Interface
172                    | TokenKind::Emit
173                    | TokenKind::Guard
174                    | TokenKind::Require
175                    | TokenKind::Deadline
176                    | TokenKind::Yield
177                    | TokenKind::Mutex
178                    | TokenKind::Defer
179                    | TokenKind::Break
180                    | TokenKind::Continue
181                    | TokenKind::Tool
182                    | TokenKind::Skill
183                    | TokenKind::Impl
184            )
185        )
186    }
187
188    /// Advance past tokens until we reach a likely statement boundary.
189    pub(super) fn synchronize(&mut self) {
190        while !self.is_at_end() {
191            if self.check(&TokenKind::Semicolon) {
192                self.advance();
193                self.skip_newlines();
194                return;
195            }
196            if self.check(&TokenKind::Newline) {
197                self.advance();
198                if self.is_at_end() || self.is_statement_start() {
199                    return;
200                }
201                continue;
202            }
203            if self.check(&TokenKind::RBrace) {
204                return;
205            }
206            self.advance();
207        }
208    }
209
210    pub(super) fn is_at_end(&self) -> bool {
211        self.pos >= self.tokens.len()
212            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
213    }
214
215    pub(super) fn current(&self) -> Option<&Token> {
216        self.tokens.get(self.pos)
217    }
218
219    pub(super) fn peek_kind(&self) -> Option<&TokenKind> {
220        self.tokens.get(self.pos + 1).map(|t| &t.kind)
221    }
222
223    pub(super) fn peek_kind_at(&self, offset: usize) -> Option<&TokenKind> {
224        self.tokens.get(self.pos + offset).map(|t| &t.kind)
225    }
226
227    pub(super) fn check(&self, kind: &TokenKind) -> bool {
228        self.current()
229            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
230            .unwrap_or(false)
231    }
232
233    /// Check for `kind`, skipping newlines first; used for binary operators
234    /// like `||` and `&&` that can span lines.
235    pub(super) fn check_skip_newlines(&mut self, kind: &TokenKind) -> bool {
236        let saved = self.pos;
237        self.skip_newlines();
238        if self.check(kind) {
239            true
240        } else {
241            self.pos = saved;
242            false
243        }
244    }
245
246    /// Check if current token is an identifier with the given name (without consuming it).
247    pub(super) fn check_identifier(&self, name: &str) -> bool {
248        matches!(self.current().map(|t| &t.kind), Some(TokenKind::Identifier(s)) if s == name)
249    }
250
251    /// `gen` is contextual so existing identifiers named `gen` keep working.
252    /// It starts a stream declaration only when followed by `fn`.
253    pub(super) fn check_contextual_gen_fn(&self) -> bool {
254        if !self.check_identifier("gen") {
255            return false;
256        }
257        matches!(
258            self.tokens.get(self.pos + 1).map(|t| &t.kind),
259            Some(TokenKind::Fn)
260        )
261    }
262
263    /// Block-introducing words remain contextual so identically named values,
264    /// dict keys, and properties keep working. They introduce a block only
265    /// when immediately followed by `{` at statement position.
266    pub(super) fn check_contextual_brace_block(&self, name: &str) -> bool {
267        if !self.check_identifier(name) {
268            return false;
269        }
270        matches!(
271            self.tokens.get(self.pos + 1).map(|t| &t.kind),
272            Some(TokenKind::LBrace)
273        )
274    }
275
276    pub(super) fn advance(&mut self) {
277        if self.pos < self.tokens.len() {
278            self.pos += 1;
279        }
280    }
281
282    pub(super) fn consume(
283        &mut self,
284        kind: &TokenKind,
285        expected: &str,
286    ) -> Result<Token, ParserError> {
287        self.skip_newlines();
288        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
289        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
290            return Err(self.make_error(expected));
291        }
292        let tok = tok.clone();
293        self.advance();
294        Ok(tok)
295    }
296
297    pub(super) fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
298        self.skip_newlines();
299        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
300        if let TokenKind::Identifier(name) = &tok.kind {
301            let name = name.clone();
302            self.advance();
303            Ok(name)
304        } else {
305            // Distinguish reserved-keyword misuse (e.g. `for tool in list`) from
306            // a general unexpected token so the error is actionable.
307            let kw_name = harn_lexer::KEYWORDS
308                .iter()
309                .find(|&&kw| kw == tok.kind.to_string());
310            if let Some(kw) = kw_name {
311                Err(ParserError::Unexpected {
312                    got: format!("'{kw}' (reserved keyword)"),
313                    expected: expected.into(),
314                    span: tok.span,
315                })
316            } else {
317                Err(self.make_error(expected))
318            }
319        }
320    }
321
322    pub(super) fn consume_contextual_keyword(
323        &mut self,
324        name: &str,
325        expected: &str,
326    ) -> Result<Token, ParserError> {
327        self.skip_newlines();
328        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
329        if matches!(&tok.kind, TokenKind::Identifier(id) if id == name) {
330            let tok = tok.clone();
331            self.advance();
332            Ok(tok)
333        } else {
334            Err(self.make_error(expected))
335        }
336    }
337
338    /// Like `consume_identifier`, but also accepts keywords as identifiers.
339    /// Used for property access (e.g., `obj.type`) and dict keys where
340    /// keywords are valid member names.
341    pub(super) fn consume_identifier_or_keyword(
342        &mut self,
343        expected: &str,
344    ) -> Result<String, ParserError> {
345        self.skip_newlines();
346        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
347        if let TokenKind::Identifier(name) = &tok.kind {
348            let name = name.clone();
349            self.advance();
350            return Ok(name);
351        }
352        let name = match &tok.kind {
353            TokenKind::Pipeline => "pipeline",
354            TokenKind::Extends => "extends",
355            TokenKind::Override => "override",
356            TokenKind::Let => "let",
357            TokenKind::Const => "const",
358            TokenKind::Var => "var",
359            TokenKind::If => "if",
360            TokenKind::Else => "else",
361            TokenKind::For => "for",
362            TokenKind::In => "in",
363            TokenKind::Match => "match",
364            TokenKind::Retry => "retry",
365            TokenKind::Parallel => "parallel",
366            TokenKind::Return => "return",
367            TokenKind::Import => "import",
368            TokenKind::True => "true",
369            TokenKind::False => "false",
370            TokenKind::Nil => "nil",
371            TokenKind::Try => "try",
372            TokenKind::Catch => "catch",
373            TokenKind::Throw => "throw",
374            TokenKind::Finally => "finally",
375            TokenKind::Fn => "fn",
376            TokenKind::Spawn => "spawn",
377            TokenKind::While => "while",
378            TokenKind::TypeKw => "type",
379            TokenKind::Enum => "enum",
380            TokenKind::EvalPack => "eval_pack",
381            TokenKind::Struct => "struct",
382            TokenKind::Interface => "interface",
383            TokenKind::Emit => "emit",
384            TokenKind::Pub => "pub",
385            TokenKind::From => "from",
386            TokenKind::To => "to",
387            TokenKind::Tool => "tool",
388            TokenKind::Exclusive => "exclusive",
389            TokenKind::Guard => "guard",
390            TokenKind::Require => "require",
391            TokenKind::Deadline => "deadline",
392            TokenKind::Defer => "defer",
393            TokenKind::Yield => "yield",
394            TokenKind::Mutex => "mutex",
395            TokenKind::Break => "break",
396            TokenKind::Continue => "continue",
397            TokenKind::Select => "select",
398            TokenKind::Impl => "impl",
399            TokenKind::Skill => "skill",
400            _ => return Err(self.make_error(expected)),
401        };
402        let name = name.to_string();
403        self.advance();
404        Ok(name)
405    }
406
407    pub(super) fn skip_newlines(&mut self) {
408        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
409            self.pos += 1;
410        }
411    }
412
413    /// Consume an optional semicolon statement separator followed by any
414    /// number of newlines, or one-or-more newlines on their own.
415    ///
416    /// This is intentionally narrower than `skip_newlines()`: semicolons are
417    /// only legal between already-parsed list items, not in arbitrary parse
418    /// positions.
419    pub(super) fn consume_statement_separator(&mut self) -> bool {
420        let mut consumed = false;
421        if self.check(&TokenKind::Semicolon) {
422            self.advance();
423            consumed = true;
424        }
425        let start = self.pos;
426        self.skip_newlines();
427        consumed || self.pos != start
428    }
429
430    pub(super) fn require_statement_separator(
431        &self,
432        prev_end_line: usize,
433        expected_item: &str,
434    ) -> Result<(), ParserError> {
435        let Some(tok) = self.current() else {
436            return Ok(());
437        };
438        if tok.kind == TokenKind::Eof || tok.span.line != prev_end_line {
439            return Ok(());
440        }
441        Err(ParserError::Unexpected {
442            got: tok.kind.to_string(),
443            expected: format!("{expected_item} separator (`;` or newline)"),
444            span: tok.span,
445        })
446    }
447
448    pub(super) fn make_error(&self, expected: &str) -> ParserError {
449        if let Some(tok) = self.tokens.get(self.pos) {
450            if tok.kind == TokenKind::Eof {
451                return ParserError::UnexpectedEof {
452                    expected: expected.into(),
453                    span: tok.span,
454                };
455            }
456            ParserError::Unexpected {
457                got: tok.kind.to_string(),
458                expected: expected.into(),
459                span: tok.span,
460            }
461        } else {
462            ParserError::UnexpectedEof {
463                expected: expected.into(),
464                span: self.prev_span(),
465            }
466        }
467    }
468
469    pub(super) fn error(&self, expected: &str) -> ParserError {
470        self.make_error(expected)
471    }
472
473    pub(super) fn with_nesting<T>(
474        &mut self,
475        context: &'static str,
476        f: impl FnOnce(&mut Self) -> Result<T, ParserError>,
477    ) -> Result<T, ParserError> {
478        if self.nesting_depth >= MAX_NESTING_DEPTH {
479            return Err(ParserError::Unexpected {
480                got: format!("{context} nesting depth exceeded"),
481                expected: format!("parser nesting depth within {MAX_NESTING_DEPTH} levels"),
482                span: self.current_span(),
483            });
484        }
485        self.nesting_depth += 1;
486        let result = f(self);
487        self.nesting_depth = self.nesting_depth.saturating_sub(1);
488        result
489    }
490}