Skip to main content

oak_bat/parser/
mod.rs

1#![doc = include_str!("readme.md")]
2pub mod element_type;
3
4pub use element_type::BatElementType;
5
6use crate::{
7    language::BatLanguage,
8    lexer::{BatLexer, BatTokenType},
9};
10use oak_core::{
11    OakError, TextEdit,
12    parser::{ParseCache, Parser, ParserState},
13    source::Source,
14};
15
16pub(crate) type State<'a, S> = ParserState<'a, BatLanguage, S>;
17
18pub struct BatParser<'config> {
19    pub(crate) config: &'config BatLanguage,
20}
21
22impl<'config> BatParser<'config> {
23    pub fn new(config: &'config BatLanguage) -> Self {
24        Self { config }
25    }
26
27    fn parse_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
28        self.skip_trivia(state);
29        if !state.not_at_end() {
30            return Ok(());
31        }
32
33        match state.peek_kind() {
34            Some(BatTokenType::Label) => self.parse_label(state),
35            Some(BatTokenType::Keyword) => {
36                let text = state.peek_text().map(|s| s.to_uppercase());
37                match text.as_deref() {
38                    Some("IF") => self.parse_if(state),
39                    Some("FOR") => self.parse_for(state),
40                    Some("SET") => self.parse_set(state),
41                    _ => self.parse_command(state),
42                }
43            }
44            _ => self.parse_command(state),
45        }
46    }
47
48    fn skip_trivia<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
49        while state.at(BatTokenType::Whitespace) || state.at(BatTokenType::Newline) || state.at(BatTokenType::Comment) {
50            state.bump();
51        }
52    }
53
54    fn parse_label<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
55        let cp = state.checkpoint();
56        state.expect(BatTokenType::Label)?;
57        state.finish_at(cp, BatElementType::LabelDefinition);
58        Ok(())
59    }
60
61    fn parse_if<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
62        let cp = state.checkpoint();
63        state.bump(); // IF
64        // Basic IF parsing: consume until newline or block
65        while state.not_at_end() && !state.at(BatTokenType::Newline) {
66            state.bump();
67        }
68        state.finish_at(cp, BatElementType::IfStatement);
69        Ok(())
70    }
71
72    fn parse_for<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
73        let cp = state.checkpoint();
74        state.bump(); // FOR
75        while state.not_at_end() && !state.at(BatTokenType::Newline) {
76            state.bump();
77        }
78        state.finish_at(cp, BatElementType::ForStatement);
79        Ok(())
80    }
81
82    fn parse_set<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
83        let cp = state.checkpoint();
84        state.bump(); // SET
85        while state.not_at_end() && !state.at(BatTokenType::Newline) {
86            state.bump();
87        }
88        state.finish_at(cp, BatElementType::SetStatement);
89        Ok(())
90    }
91
92    fn parse_command<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
93        let cp = state.checkpoint();
94        while state.not_at_end() && !state.at(BatTokenType::Newline) {
95            state.bump();
96        }
97        state.finish_at(cp, BatElementType::CommandStatement);
98        Ok(())
99    }
100}
101
102impl<'config> Parser<BatLanguage> for BatParser<'config> {
103    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<BatLanguage>) -> oak_core::ParseOutput<'a, BatLanguage> {
104        let lexer = BatLexer::new(self.config);
105        oak_core::parser::parse_with_lexer(&lexer, text, edits, cache, |state| {
106            let checkpoint = state.checkpoint();
107            while state.not_at_end() {
108                if self.parse_statement(state).is_err() {
109                    break;
110                }
111            }
112            Ok(state.finish_at(checkpoint, BatElementType::Root))
113        })
114    }
115}