Skip to main content

harn_parser/parser/
state.rs

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