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