Skip to main content

runmat_parser/parser/
mod.rs

1mod assignment;
2mod classdef;
3mod command;
4mod cursor;
5mod expr;
6mod stmt;
7
8use runmat_lexer::Token;
9
10use crate::{ParserOptions, Program, Stmt, SyntaxError};
11
12#[derive(Clone)]
13struct TokenInfo {
14    token: Token,
15    lexeme: String,
16    position: usize,
17    end: usize,
18}
19
20struct Parser {
21    tokens: Vec<TokenInfo>,
22    pos: usize,
23    input: String,
24    options: ParserOptions,
25    in_matrix_expr: bool,
26}
27
28pub fn parse(input: &str) -> Result<Program, SyntaxError> {
29    parse_with_options(input, ParserOptions::default())
30}
31
32pub fn parse_with_options(input: &str, options: ParserOptions) -> Result<Program, SyntaxError> {
33    use runmat_lexer::tokenize_detailed;
34
35    let toks = tokenize_detailed(input);
36    let mut tokens = Vec::new();
37    let mut skip_newlines = false;
38
39    for t in toks {
40        if matches!(t.token, Token::Error) {
41            return Err(SyntaxError {
42                message: format!("Invalid token: '{}'", t.lexeme),
43                position: t.start,
44                found_token: Some(t.lexeme),
45                expected: None,
46            });
47        }
48        // Skip layout-only tokens from lexing.
49        if matches!(t.token, Token::Ellipsis | Token::Section) {
50            // After ellipsis, also drop any immediately following Newline tokens.
51            // The lexer callback already consumed the first \n after `...`; any
52            // additional blank lines should be treated as part of the continuation.
53            skip_newlines = matches!(t.token, Token::Ellipsis);
54            continue;
55        }
56        if skip_newlines && matches!(t.token, Token::Newline) {
57            continue;
58        }
59        skip_newlines = false;
60        tokens.push(TokenInfo {
61            token: t.token,
62            lexeme: t.lexeme,
63            position: t.start,
64            end: t.end,
65        });
66    }
67
68    let mut parser = Parser {
69        tokens,
70        pos: 0,
71        input: input.to_string(),
72        options,
73        in_matrix_expr: false,
74    };
75    parser.parse_program()
76}
77
78impl Parser {
79    fn parse_program(&mut self) -> Result<Program, SyntaxError> {
80        let mut body = Vec::new();
81        while self.pos < self.tokens.len() {
82            if self.consume(&Token::Semicolon)
83                || self.consume(&Token::Comma)
84                || self.consume(&Token::Newline)
85            {
86                continue;
87            }
88            body.push(self.parse_stmt_with_semicolon()?);
89        }
90        Ok(Program { body })
91    }
92
93    fn finalize_stmt(&self, stmt: Stmt, is_semicolon_terminated: bool) -> Stmt {
94        match stmt {
95            Stmt::ExprStmt(expr, _, span) => Stmt::ExprStmt(expr, is_semicolon_terminated, span),
96            Stmt::Assign(name, expr, _, span) => {
97                Stmt::Assign(name, expr, is_semicolon_terminated, span)
98            }
99            Stmt::MultiAssign(names, expr, _, span) => {
100                Stmt::MultiAssign(names, expr, is_semicolon_terminated, span)
101            }
102            Stmt::AssignLValue(lv, expr, _, span) => {
103                Stmt::AssignLValue(lv, expr, is_semicolon_terminated, span)
104            }
105            other => other,
106        }
107    }
108}