1pub 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
21pub struct CmdParser<'config> {
23 pub(crate) config: &'config CmdLanguage,
24}
25
26impl<'config> CmdParser<'config> {
27 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(); 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(); 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(); 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}