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!(
51            t.token,
52            Token::Ellipsis | Token::Section | Token::LineComment | Token::BlockComment
53        ) {
54            // After ellipsis, also drop any immediately following Newline tokens.
55            // The lexer callback already consumed the first \n after `...`; any
56            // additional blank lines should be treated as part of the continuation.
57            skip_newlines = matches!(t.token, Token::Ellipsis);
58            continue;
59        }
60        if skip_newlines && matches!(t.token, Token::Newline) {
61            continue;
62        }
63        skip_newlines = false;
64        tokens.push(TokenInfo {
65            token: t.token,
66            lexeme: t.lexeme,
67            position: t.start,
68            end: t.end,
69        });
70    }
71
72    let mut parser = Parser {
73        tokens,
74        pos: 0,
75        input: input.to_string(),
76        options,
77        in_matrix_expr: false,
78        current_classdef_name: None,
79    };
80    parser.parse_program()
81}
82
83impl Parser {
84    fn parse_program(&mut self) -> Result<Program, SyntaxError> {
85        let mut body = Vec::new();
86        while self.pos < self.tokens.len() {
87            if self.consume(&Token::Semicolon)
88                || self.consume(&Token::Comma)
89                || self.consume(&Token::Newline)
90            {
91                continue;
92            }
93            body.push(self.parse_stmt_with_semicolon()?);
94        }
95        Ok(Program { body })
96    }
97
98    fn finalize_stmt(&self, stmt: Stmt, is_semicolon_terminated: bool) -> Stmt {
99        match stmt {
100            Stmt::ExprStmt(expr, _, span) => Stmt::ExprStmt(expr, is_semicolon_terminated, span),
101            Stmt::Assign(name, expr, _, span) => {
102                Stmt::Assign(name, expr, is_semicolon_terminated, span)
103            }
104            Stmt::MultiAssign(names, expr, _, span) => {
105                Stmt::MultiAssign(names, expr, is_semicolon_terminated, span)
106            }
107            Stmt::AssignLValue(lv, expr, _, span) => {
108                Stmt::AssignLValue(lv, expr, is_semicolon_terminated, span)
109            }
110            other => other,
111        }
112    }
113}