Skip to main content

oak_markdown/lexer/
inline.rs

1use crate::lexer::{MarkdownLexer, State, token_type::MarkdownTokenType};
2use oak_core::Source;
3
4impl<'config> MarkdownLexer<'config> {
5    /// Handles emphasis and strong.
6    pub fn lex_emphasis<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
7        let start_pos = state.get_position();
8
9        let marker_char = if let Some('*') = state.peek() {
10            '*'
11        }
12        else if let Some('_') = state.peek() {
13            '_'
14        }
15        else {
16            return false;
17        };
18
19        let mut marker_count = 0;
20        let mut pos = start_pos;
21
22        while let Some(ch) = state.source().get_char_at(pos) {
23            if ch == marker_char {
24                marker_count += 1;
25                pos += 1;
26            }
27            else {
28                break;
29            }
30        }
31
32        if marker_count == 0 {
33            return false;
34        }
35
36        state.advance(marker_count);
37
38        let token_kind = if marker_count >= 2 { MarkdownTokenType::Strong } else { MarkdownTokenType::Emphasis };
39
40        state.add_token(token_kind, start_pos, state.get_position());
41        true
42    }
43
44    /// Handles inline code.
45    pub fn lex_inline_code<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
46        let start_pos = state.get_position();
47
48        if let Some('`') = state.peek() {
49            state.advance(1);
50            let mut found_end = false;
51
52            while let Some(ch) = state.peek() {
53                if ch == '`' {
54                    state.advance(1);
55                    found_end = true;
56                    break;
57                }
58                else if ch == '\n' || ch == '\r' {
59                    break;
60                }
61                else {
62                    state.advance(ch.len_utf8());
63                }
64            }
65
66            if found_end {
67                state.add_token(MarkdownTokenType::InlineCode, start_pos, state.get_position());
68                true
69            }
70            else {
71                state.set_position(start_pos);
72                false
73            }
74        }
75        else {
76            false
77        }
78    }
79
80    /// Handles strikethrough.
81    pub fn lex_strikethrough<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
82        let start_pos = state.get_position();
83
84        if let Some('~') = state.peek() {
85            if let Some('~') = state.source().get_char_at(start_pos + 1) {
86                state.advance(2);
87                state.add_token(MarkdownTokenType::Strikethrough, start_pos, state.get_position());
88                true
89            }
90            else {
91                false
92            }
93        }
94        else {
95            false
96        }
97    }
98
99    /// Handles links and images.
100    pub fn lex_link_or_image<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
101        let start_pos = state.get_position();
102
103        let is_image = if let Some('!') = state.peek() {
104            state.advance(1);
105            true
106        }
107        else {
108            false
109        };
110
111        if let Some('[') = state.peek() {
112            state.advance(1);
113
114            let token_kind = if is_image { MarkdownTokenType::Image } else { MarkdownTokenType::Link };
115
116            state.add_token(token_kind, start_pos, state.get_position());
117            true
118        }
119        else {
120            if is_image {
121                state.set_position(start_pos);
122            }
123            false
124        }
125    }
126
127    /// Lexes math formulas.
128    pub fn lex_math<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
129        let start_pos = state.get_position();
130
131        if let Some('$') = state.peek() {
132            state.advance(1);
133            let mut is_block = false;
134
135            if let Some('$') = state.peek() {
136                state.advance(1);
137                is_block = true;
138            }
139
140            let mut found_end = false;
141            while let Some(ch) = state.peek() {
142                if ch == '$' {
143                    if is_block {
144                        if let Some('$') = state.source().get_char_at(state.get_position() + 1) {
145                            state.advance(2);
146                            found_end = true;
147                            break;
148                        }
149                    }
150                    else {
151                        state.advance(1);
152                        found_end = true;
153                        break;
154                    }
155                }
156                state.advance(ch.len_utf8())
157            }
158
159            if found_end {
160                let kind = if is_block { MarkdownTokenType::MathBlock } else { MarkdownTokenType::MathInline };
161                state.add_token(kind, start_pos, state.get_position());
162                true
163            }
164            else {
165                state.set_position(start_pos);
166                false
167            }
168        }
169        else {
170            false
171        }
172    }
173
174    /// Lexes footnotes.
175    pub fn lex_footnote<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
176        let start_pos = state.get_position();
177
178        if let Some('^') = state.peek() {
179            let check_pos = start_pos;
180            if check_pos > 0 && state.source().get_char_at(check_pos - 1) == Some('[') {
181                state.advance(1);
182                while let Some(ch) = state.peek() {
183                    if ch == ']' {
184                        state.advance(1);
185                        if state.peek() == Some(':') {
186                            state.advance(1);
187                            state.add_token(MarkdownTokenType::FootnoteDefinition, start_pos - 1, state.get_position())
188                        }
189                        else {
190                            state.add_token(MarkdownTokenType::FootnoteReference, start_pos - 1, state.get_position())
191                        }
192                        return true;
193                    }
194                    else if ch == '\n' || ch == '\r' {
195                        break;
196                    }
197                    state.advance(ch.len_utf8())
198                }
199            }
200            state.set_position(start_pos);
201        }
202        false
203    }
204
205    /// Lexes superscripts and subscripts.
206    pub fn lex_sub_superscript<S: Source + ?Sized>(&self, state: &mut State<S>) -> bool {
207        let start_pos = state.get_position();
208
209        if let Some(ch) = state.peek() {
210            let marker = ch;
211            if marker == '^' || marker == '~' {
212                state.advance(1);
213                let mut found_end = false;
214                while let Some(next_ch) = state.peek() {
215                    if next_ch == marker {
216                        state.advance(1);
217                        found_end = true;
218                        break;
219                    }
220                    else if next_ch == ' ' || next_ch == '\t' || next_ch == '\n' || next_ch == '\r' {
221                        break;
222                    }
223                    state.advance(next_ch.len_utf8())
224                }
225
226                if found_end {
227                    let kind = if marker == '^' { MarkdownTokenType::Superscript } else { MarkdownTokenType::Subscript };
228                    state.add_token(kind, start_pos, state.get_position());
229                    true
230                }
231                else {
232                    state.set_position(start_pos);
233                    false
234                }
235            }
236            else {
237                false
238            }
239        }
240        else {
241            false
242        }
243    }
244}