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