Skip to main content

oak_cmd/parser/
mod.rs

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