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