Skip to main content

oak_bat/parser/
mod.rs

1#![doc = include_str!("readme.md")]
2/// Element type definitions for Windows Batch (BAT) syntax tree nodes.
3///
4/// This module provides [`BatElementType`] which defines all element types
5/// used in the BAT parse tree, including statements, labels, and commands.
6pub mod element_type;
7
8pub use element_type::BatElementType;
9
10use crate::{
11    language::BatLanguage,
12    lexer::{BatLexer, BatTokenType},
13};
14use oak_core::{
15    OakError, TextEdit,
16    parser::{ParseCache, Parser, ParserState},
17    source::Source,
18};
19
20pub(crate) type State<'a, S> = ParserState<'a, BatLanguage, S>;
21
22/// Parser for Windows Batch (BAT) files.
23///
24/// This parser transforms tokens from the lexer into a structured
25/// parse tree (green tree) representing the BAT source code.
26pub struct BatParser<'config> {
27    pub(crate) config: &'config BatLanguage,
28}
29
30impl<'config> BatParser<'config> {
31    /// Creates a new `BatParser` instance with the specified language configuration.
32    ///
33    /// # Arguments
34    ///
35    /// * `config` - A reference to the `BatLanguage` configuration that defines
36    ///   the language-specific settings for parsing.
37    pub fn new(config: &'config BatLanguage) -> Self {
38        Self { config }
39    }
40
41    fn parse_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
42        self.skip_trivia(state);
43        if !state.not_at_end() {
44            return Ok(());
45        }
46
47        match state.peek_kind() {
48            Some(BatTokenType::Label) => self.parse_label(state),
49            Some(BatTokenType::Keyword) => {
50                let text = state.peek_text().map(|s| s.to_uppercase());
51                match text.as_deref() {
52                    Some("IF") => self.parse_if(state),
53                    Some("FOR") => self.parse_for(state),
54                    Some("SET") => self.parse_set(state),
55                    _ => self.parse_command(state),
56                }
57            }
58            _ => self.parse_command(state),
59        }
60    }
61
62    fn skip_trivia<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
63        while state.at(BatTokenType::Whitespace) || state.at(BatTokenType::Newline) || state.at(BatTokenType::Comment) {
64            state.bump();
65        }
66    }
67
68    fn parse_label<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
69        let cp = state.checkpoint();
70        state.expect(BatTokenType::Label)?;
71        state.finish_at(cp, BatElementType::LabelDefinition);
72        Ok(())
73    }
74
75    fn parse_if<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
76        let cp = state.checkpoint();
77        state.bump(); // IF
78        // Basic IF parsing: consume until newline or block
79        while state.not_at_end() && !state.at(BatTokenType::Newline) {
80            state.bump();
81        }
82        state.finish_at(cp, BatElementType::IfStatement);
83        Ok(())
84    }
85
86    fn parse_for<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
87        let cp = state.checkpoint();
88        state.bump(); // FOR
89        while state.not_at_end() && !state.at(BatTokenType::Newline) {
90            state.bump();
91        }
92        state.finish_at(cp, BatElementType::ForStatement);
93        Ok(())
94    }
95
96    fn parse_set<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
97        let cp = state.checkpoint();
98        state.bump(); // SET
99        while state.not_at_end() && !state.at(BatTokenType::Newline) {
100            state.bump();
101        }
102        state.finish_at(cp, BatElementType::SetStatement);
103        Ok(())
104    }
105
106    fn parse_command<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
107        let cp = state.checkpoint();
108        while state.not_at_end() && !state.at(BatTokenType::Newline) {
109            state.bump();
110        }
111        state.finish_at(cp, BatElementType::CommandStatement);
112        Ok(())
113    }
114}
115
116impl<'config> Parser<BatLanguage> for BatParser<'config> {
117    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<BatLanguage>) -> oak_core::ParseOutput<'a, BatLanguage> {
118        let lexer = BatLexer::new(self.config);
119        oak_core::parser::parse_with_lexer(&lexer, text, edits, cache, |state| {
120            let checkpoint = state.checkpoint();
121            while state.not_at_end() {
122                if self.parse_statement(state).is_err() {
123                    break;
124                }
125            }
126            Ok(state.finish_at(checkpoint, BatElementType::Root))
127        })
128    }
129}