Skip to main content

oak_typst/parser/
mod.rs

1pub mod element_type;
2
3use crate::{
4    language::TypstLanguage,
5    lexer::{TypstLexer, token_type::TypstTokenType},
6    parser::element_type::TypstElementType,
7};
8use oak_core::{
9    GreenNode, OakError,
10    parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
11    source::{Source, TextEdit},
12};
13
14pub(crate) type State<'a, S> = ParserState<'a, TypstLanguage, S>;
15
16pub struct TypstParser<'config> {
17    pub(crate) config: &'config TypstLanguage,
18}
19
20impl<'config> TypstParser<'config> {
21    pub fn new(config: &'config TypstLanguage) -> Self {
22        Self { config }
23    }
24
25    fn parse_item<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), OakError> {
26        let kind = state.peek_kind();
27        match kind {
28            Some(TypstTokenType::Heading) => {
29                let checkpoint = state.checkpoint();
30                state.bump(); // Heading marker
31                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Newline) {
32                    self.parse_item(state)?;
33                }
34                state.finish_at(checkpoint, TypstElementType::Heading);
35            }
36            Some(TypstTokenType::Hash) => {
37                let checkpoint = state.checkpoint();
38                state.bump(); // #
39                // Check if it's "quote" or other commands
40                while state.not_at_end()
41                    && state
42                        .peek_kind()
43                        .map(|k| {
44                            matches!(
45                                k,
46                                TypstTokenType::Identifier
47                                    | TypstTokenType::Let
48                                    | TypstTokenType::If
49                                    | TypstTokenType::Else
50                                    | TypstTokenType::For
51                                    | TypstTokenType::While
52                                    | TypstTokenType::Set
53                                    | TypstTokenType::Show
54                                    | TypstTokenType::Import
55                                    | TypstTokenType::Include
56                            )
57                        })
58                        .unwrap_or(false)
59                {
60                    state.bump()
61                }
62
63                if state.peek_kind() == Some(TypstTokenType::LeftBracket) {
64                    state.bump(); // [
65                    while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::RightBracket) {
66                        self.parse_item(state)?;
67                    }
68                    if state.peek_kind() == Some(TypstTokenType::RightBracket) {
69                        state.bump();
70                    }
71                }
72                else {
73                    // Just a simple #cmd without arguments
74                    while state.not_at_end() && !matches!(state.peek_kind(), Some(TypstTokenType::Newline) | Some(TypstTokenType::Whitespace)) {
75                        state.bump()
76                    }
77                }
78                state.finish_at(checkpoint, TypstElementType::Quote);
79            }
80            Some(TypstTokenType::Dollar) => {
81                let checkpoint = state.checkpoint();
82                state.bump(); // $
83                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Dollar) {
84                    self.parse_item(state)?;
85                }
86                if state.peek_kind() == Some(TypstTokenType::Dollar) {
87                    state.bump()
88                }
89                state.finish_at(checkpoint, TypstElementType::Math);
90            }
91            Some(TypstTokenType::Strong) => {
92                let checkpoint = state.checkpoint();
93                state.bump(); // *
94                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Strong) {
95                    self.parse_item(state)?;
96                }
97                if state.peek_kind() == Some(TypstTokenType::Strong) {
98                    state.bump()
99                }
100                state.finish_at(checkpoint, TypstElementType::Strong);
101            }
102            Some(TypstTokenType::Emphasis) => {
103                let checkpoint = state.checkpoint();
104                state.bump(); // _
105                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Emphasis) {
106                    self.parse_item(state)?;
107                }
108                if state.peek_kind() == Some(TypstTokenType::Emphasis) {
109                    state.bump()
110                }
111                state.finish_at(checkpoint, TypstElementType::Emphasis);
112            }
113            Some(TypstTokenType::ListItem) => {
114                let checkpoint = state.checkpoint();
115                state.bump(); // - or +
116                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Newline) {
117                    self.parse_item(state)?;
118                }
119                state.finish_at(checkpoint, TypstElementType::ListItem);
120            }
121            Some(TypstTokenType::EnumItem) => {
122                let checkpoint = state.checkpoint();
123                state.bump(); // 1.
124                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Newline) {
125                    self.parse_item(state)?;
126                }
127                state.finish_at(checkpoint, TypstElementType::EnumItem);
128            }
129            Some(TypstTokenType::Backtick) => {
130                let checkpoint = state.checkpoint();
131                state.bump(); // `
132                while state.not_at_end() && state.peek_kind() != Some(TypstTokenType::Backtick) {
133                    state.bump()
134                }
135                if state.peek_kind() == Some(TypstTokenType::Backtick) {
136                    state.bump()
137                }
138                state.finish_at(checkpoint, TypstElementType::Raw);
139            }
140            _ => {
141                state.bump();
142            }
143        };
144        Ok(())
145    }
146}
147
148impl<'config> Parser<TypstLanguage> for TypstParser<'config> {
149    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<TypstLanguage>) -> ParseOutput<'a, TypstLanguage> {
150        let lexer = TypstLexer::new(&self.config);
151        parse_with_lexer(&lexer, text, edits, cache, |state| {
152            let checkpoint = state.checkpoint();
153
154            while state.not_at_end() {
155                self.parse_item(state)?
156            }
157
158            Ok(state.finish_at(checkpoint, TypstElementType::Root))
159        })
160    }
161}