oak_tailwind/lexer/
mod.rs1#![doc = include_str!("readme.md")]
2pub mod token_type;
4
5use crate::{language::TailwindLanguage, lexer::token_type::TailwindTokenType};
6use oak_core::{Lexer, LexerCache, LexerState, OakError, lexer::LexOutput, source::Source};
7
8#[derive(Clone, Debug, Default)]
10pub struct TailwindLexer {
11 pub config: TailwindLanguage,
13}
14
15pub(crate) type State<'a, S> = LexerState<'a, S, TailwindLanguage>;
16
17impl TailwindLexer {
18 pub fn new(config: TailwindLanguage) -> Self {
20 Self { config }
21 }
22}
23
24impl Lexer<TailwindLanguage> for TailwindLexer {
25 fn lex<'a, S: Source + ?Sized>(&self, source: &S, _edits: &[oak_core::TextEdit], cache: &'a mut impl LexerCache<TailwindLanguage>) -> LexOutput<TailwindLanguage> {
27 let mut state = LexerState::new(source);
28 let result = self.run(&mut state);
29 if result.is_ok() {
30 state.add_eof()
31 }
32 state.finish_with_cache(result, cache)
33 }
34}
35
36impl TailwindLexer {
37 pub fn run<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> Result<(), OakError> {
39 while state.not_at_end() {
40 let safe_point = state.get_position();
41
42 if self.skip_whitespace(state) {
43 continue;
44 }
45
46 if self.lex_comment(state) {
47 continue;
48 }
49
50 if self.lex_directive(state) {
51 continue;
52 }
53
54 if self.lex_tailwind_class_part(state) {
55 continue;
56 }
57
58 if self.lex_punctuation(state) {
59 continue;
60 }
61
62 state.advance_if_dead_lock(safe_point)
63 }
64
65 Ok(())
66 }
67
68 pub fn skip_whitespace<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
70 let start = state.get_position();
71 let mut found = false;
72
73 while let Some(ch) = state.peek() {
74 if ch.is_whitespace() {
75 state.advance(ch.len_utf8());
76 found = true
77 }
78 else {
79 break;
80 }
81 }
82
83 if found {
84 state.add_token(TailwindTokenType::Whitespace, start, state.get_position())
85 }
86
87 found
88 }
89
90 pub fn lex_comment<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
92 let start = state.get_position();
93 if state.consume_if_starts_with("/*") {
94 while state.not_at_end() {
95 if state.consume_if_starts_with("*/") {
96 break;
97 }
98 if let Some(ch) = state.peek() {
99 state.advance(ch.len_utf8())
100 }
101 }
102 state.add_token(TailwindTokenType::Comment, start, state.get_position());
103 return true;
104 }
105 if state.consume_if_starts_with("//") {
106 while state.not_at_end() {
107 if let Some(ch) = state.peek() {
108 if ch == '\n' {
109 break;
110 }
111 state.advance(ch.len_utf8())
112 }
113 }
114 state.add_token(TailwindTokenType::Comment, start, state.get_position());
115 return true;
116 }
117 false
118 }
119
120 pub fn lex_directive<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
122 let start = state.get_position();
123 if state.consume_if_starts_with("@") {
124 while let Some(ch) = state.peek() {
125 if ch.is_alphabetic() || ch == '-' {
126 state.advance(ch.len_utf8());
127 }
128 else {
129 break;
130 }
131 }
132 state.add_token(TailwindTokenType::Directive, start, state.get_position());
133 return true;
134 }
135 false
136 }
137
138 pub fn lex_tailwind_class_part<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
140 let start = state.get_position();
141
142 if state.consume_if_starts_with("!") {
143 state.add_token(TailwindTokenType::Important, start, state.get_position());
144 return true;
145 }
146
147 if state.peek() == Some('[') {
148 return self.lex_arbitrary_value(state);
149 }
150
151 let mut has_content = false;
153 let _current_pos = state.get_position();
154
155 while let Some(ch) = state.peek() {
156 if ch.is_alphanumeric() || ch == '-' || ch == '/' || ch == '.' || ch == '_' {
157 state.advance(ch.len_utf8());
158 has_content = true;
159
160 if state.peek() == Some(':') {
161 state.advance(':'.len_utf8());
162 state.add_token(TailwindTokenType::Modifier, start, state.get_position());
163 return true;
164 }
165 }
166 else {
167 break;
168 }
169 }
170
171 if has_content {
172 state.add_token(TailwindTokenType::Utility, start, state.get_position());
173 return true;
174 }
175
176 false
177 }
178
179 pub fn lex_arbitrary_value<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
181 let start = state.get_position();
182 if state.consume_if_starts_with("[") {
183 let mut depth = 1;
184 while state.not_at_end() && depth > 0 {
185 if let Some(ch) = state.peek() {
186 if ch == '[' {
187 depth += 1;
188 }
189 else if ch == ']' {
190 depth -= 1;
191 }
192 state.advance(ch.len_utf8());
193 }
194 else {
195 break;
196 }
197 }
198 state.add_token(TailwindTokenType::ArbitraryValue, start, state.get_position());
199 return true;
200 }
201 false
202 }
203
204 pub fn lex_punctuation<S: Source + ?Sized>(&self, state: &mut State<'_, S>) -> bool {
206 let start = state.get_position();
207
208 macro_rules! check {
209 ($s:expr, $t:ident) => {
210 if state.consume_if_starts_with($s) {
211 state.add_token(TailwindTokenType::$t, start, state.get_position());
212 return true;
213 }
214 };
215 }
216
217 check!("[", LeftBracket);
218 check!("]", RightBracket);
219 check!("(", LeftParen);
220 check!(")", RightParen);
221 check!(":", Colon);
222 check!(";", Semicolon);
223 check!("@", At);
224 check!("!", Bang);
225 check!("-", Dash);
226 check!("/", Slash);
227 check!(".", Dot);
228 check!("#", Hash);
229 check!(",", Comma);
230
231 false
232 }
233}