Skip to main content

seqc/parser/
driver.rs

1//! Public entry points: construction, external-union registration, top-level parse.
2use crate::ast::Program;
3
4use super::{Parser, annotate_error_with_line, tokenize};
5
6impl Parser {
7    pub fn new(source: &str) -> Self {
8        let tokens = tokenize(source);
9        Parser {
10            tokens,
11            pos: 0,
12            next_quotation_id: 0,
13            pending_allowed_lints: Vec::new(),
14            known_unions: std::collections::HashSet::new(),
15        }
16    }
17
18    /// Register external union names (e.g., from included modules)
19    /// These union types will be recognized in stack effect declarations.
20    pub fn register_external_unions(&mut self, union_names: &[&str]) {
21        for name in union_names {
22            self.known_unions.insert(name.to_string());
23        }
24    }
25
26    pub fn parse(&mut self) -> Result<Program, String> {
27        let mut program = Program::new();
28
29        // Check for unclosed string error from tokenizer
30        if let Some(error_token) = self.tokens.iter().find(|t| *t == "<<<UNCLOSED_STRING>>>") {
31            return Err(format!(
32                "Unclosed string literal at line {}, column {} - missing closing quote",
33                error_token.line + 1, // 1-indexed for user display
34                error_token.column + 1
35            ));
36        }
37
38        while !self.is_at_end() {
39            self.skip_comments();
40            if self.is_at_end() {
41                break;
42            }
43
44            // Dispatch to the appropriate sub-parser. If the sub-parser returns
45            // an error, annotate it with the current token's line so the LSP
46            // can surface the diagnostic at the offending location rather than
47            // defaulting to line 1.
48            let result = if self.check("include") {
49                self.parse_include().map(|inc| program.includes.push(inc))
50            } else if self.check("union") {
51                self.parse_union_def().map(|u| program.unions.push(u))
52            } else {
53                self.parse_word_def().map(|w| program.words.push(w))
54            };
55
56            if let Err(msg) = result {
57                // Prefer the token we were looking at when the error fired.
58                // If we were already at EOF, fall back to the final token's line
59                // so the diagnostic lands near the unterminated construct
60                // instead of on line 1.
61                let loc_token = self.current_token().or_else(|| self.tokens.last());
62                return Err(annotate_error_with_line(msg, loc_token));
63            }
64        }
65
66        Ok(program)
67    }
68}