Skip to main content

oak_tailwind/lexer/
mod.rs

1use crate::{kind::TailwindSyntaxKind, language::TailwindLanguage};
2use oak_core::{Lexer, LexerCache, LexerState, OakError, lexer::LexOutput, source::Source};
3
4#[derive(Clone, Debug)]
5pub struct TailwindLexer<'config> {
6    /// 语言配置
7    _config: &'config TailwindLanguage,
8}
9
10type State<'a, S> = LexerState<'a, S, TailwindLanguage>;
11
12impl<'config> TailwindLexer<'config> {
13    /// 创建新的 Tailwind 词法分析器
14    pub fn new(config: &'config TailwindLanguage) -> Self {
15        Self { _config: config }
16    }
17}
18
19impl<'config> Lexer<TailwindLanguage> for TailwindLexer<'config> {
20    fn lex<'a, S: Source + ?Sized>(&self, source: &S, _edits: &[oak_core::TextEdit], cache: &'a mut impl LexerCache<TailwindLanguage>) -> LexOutput<TailwindLanguage> {
21        let mut state = LexerState::new(source);
22        let result = self.run(&mut state);
23        if result.is_ok() {
24            state.add_eof();
25        }
26        state.finish_with_cache(result, cache)
27    }
28}
29
30impl<'config> TailwindLexer<'config> {
31    fn run<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> Result<(), OakError> {
32        while state.not_at_end() {
33            let safe_point = state.get_position();
34
35            if self.skip_whitespace(state) {
36                continue;
37            }
38
39            if self.skip_comment(state) {
40                continue;
41            }
42
43            if self.lex_string(state) {
44                continue;
45            }
46
47            if self.lex_number(state) {
48                continue;
49            }
50
51            if self.lex_punctuation(state) {
52                continue;
53            }
54
55            if self.lex_identifier(state) {
56                continue;
57            }
58
59            state.advance_if_dead_lock(safe_point);
60        }
61
62        Ok(())
63    }
64
65    fn skip_whitespace<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
66        let start = state.get_position();
67        let mut found = false;
68
69        while let Some(ch) = state.peek() {
70            if ch.is_whitespace() {
71                state.advance(ch.len_utf8());
72                found = true;
73            }
74            else {
75                break;
76            }
77        }
78
79        if found {
80            state.add_token(TailwindSyntaxKind::Whitespace, start, state.get_position());
81        }
82
83        found
84    }
85
86    fn skip_comment<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
87        let start = state.get_position();
88        if state.consume_if_starts_with("{#") {
89            while state.not_at_end() {
90                if state.consume_if_starts_with("#}") {
91                    break;
92                }
93                if let Some(ch) = state.peek() {
94                    state.advance(ch.len_utf8());
95                }
96            }
97            state.add_token(TailwindSyntaxKind::Comment, start, state.get_position());
98            return true;
99        }
100        false
101    }
102
103    fn lex_string<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
104        let start = state.get_position();
105
106        if let Some(quote) = state.peek() {
107            if quote == '"' || quote == '\'' {
108                state.advance(1);
109
110                while let Some(ch) = state.peek() {
111                    if ch == quote {
112                        state.advance(1);
113                        break;
114                    }
115                    else if ch == '\\' {
116                        state.advance(1);
117                        if let Some(_) = state.peek() {
118                            state.advance(1);
119                        }
120                    }
121                    else {
122                        state.advance(ch.len_utf8());
123                    }
124                }
125
126                state.add_token(TailwindSyntaxKind::String, start, state.get_position());
127                return true;
128            }
129        }
130
131        false
132    }
133
134    fn lex_number<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
135        let start = state.get_position();
136
137        if let Some(ch) = state.peek() {
138            if ch.is_ascii_digit() {
139                state.advance(1);
140
141                while let Some(ch) = state.peek() {
142                    if ch.is_ascii_digit() || ch == '.' {
143                        state.advance(1);
144                    }
145                    else {
146                        break;
147                    }
148                }
149
150                state.add_token(TailwindSyntaxKind::Number, start, state.get_position());
151                return true;
152            }
153        }
154
155        false
156    }
157
158    fn lex_punctuation<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
159        let start = state.get_position();
160        let rest = state.rest();
161
162        // 双字符操作符
163        if rest.starts_with("{{") {
164            state.advance(2);
165            state.add_token(TailwindSyntaxKind::DoubleLeftBrace, start, state.get_position());
166            return true;
167        }
168        if rest.starts_with("}}") {
169            state.advance(2);
170            state.add_token(TailwindSyntaxKind::DoubleRightBrace, start, state.get_position());
171            return true;
172        }
173        if rest.starts_with("{%") {
174            state.advance(2);
175            state.add_token(TailwindSyntaxKind::LeftBracePercent, start, state.get_position());
176            return true;
177        }
178        if rest.starts_with("%}") {
179            state.advance(2);
180            state.add_token(TailwindSyntaxKind::PercentRightBrace, start, state.get_position());
181            return true;
182        }
183
184        // 单字符操作符
185        if let Some(ch) = state.peek() {
186            let kind = match ch {
187                '{' => TailwindSyntaxKind::LeftBrace,
188                '}' => TailwindSyntaxKind::RightBrace,
189                '(' => TailwindSyntaxKind::LeftParen,
190                ')' => TailwindSyntaxKind::RightParen,
191                '[' => TailwindSyntaxKind::LeftBracket,
192                ']' => TailwindSyntaxKind::RightBracket,
193                ',' => TailwindSyntaxKind::Comma,
194                '.' => TailwindSyntaxKind::Dot,
195                ':' => TailwindSyntaxKind::Colon,
196                ';' => TailwindSyntaxKind::Semicolon,
197                '|' => TailwindSyntaxKind::Pipe,
198                '=' => TailwindSyntaxKind::Eq,
199                '+' => TailwindSyntaxKind::Plus,
200                '-' => TailwindSyntaxKind::Minus,
201                '*' => TailwindSyntaxKind::Star,
202                '/' => TailwindSyntaxKind::Slash,
203                '%' => TailwindSyntaxKind::Percent,
204                '!' => TailwindSyntaxKind::Bang,
205                '?' => TailwindSyntaxKind::Question,
206                '<' => TailwindSyntaxKind::Lt,
207                '>' => TailwindSyntaxKind::Gt,
208                '&' => TailwindSyntaxKind::Amp,
209                '^' => TailwindSyntaxKind::Caret,
210                '~' => TailwindSyntaxKind::Tilde,
211                _ => return false,
212            };
213
214            state.advance(1);
215            state.add_token(kind, start, state.get_position());
216            return true;
217        }
218
219        false
220    }
221
222    fn lex_identifier<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> bool {
223        let start = state.get_position();
224
225        if let Some(ch) = state.peek() {
226            if ch.is_ascii_alphabetic() || ch == '_' {
227                state.advance(ch.len_utf8());
228
229                while let Some(ch) = state.peek() {
230                    if ch.is_ascii_alphanumeric() || ch == '_' {
231                        state.advance(ch.len_utf8());
232                    }
233                    else {
234                        break;
235                    }
236                }
237
238                let end = state.get_position();
239                let text = state.get_text_in((start..end).into());
240
241                // 检查是否为布尔关键字
242                let kind = match text.as_ref() {
243                    "true" | "false" => TailwindSyntaxKind::Boolean,
244                    _ => TailwindSyntaxKind::Identifier,
245                };
246                state.add_token(kind, start, end);
247                return true;
248            }
249        }
250        false
251    }
252}