Skip to main content

oak_typst/parser/
mod.rs

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