oak_tailwind/parser/
mod.rs1pub mod element_type;
4
5use crate::{
6 ast::{TailwindArbitraryValue, TailwindClass, TailwindClassKind, TailwindComment, TailwindDirective, TailwindModifier, TailwindNode, TailwindRoot, TailwindUtility},
7 language::TailwindLanguage,
8 lexer::{TailwindLexer, token_type::TailwindTokenType},
9 parser::element_type::TailwindElementType,
10};
11use core::range::Range;
12use oak_core::{
13 errors::OakError,
14 parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
15 source::{Source, TextEdit},
16};
17
18pub(crate) type State<'a, S> = ParserState<'a, TailwindLanguage, S>;
19
20#[derive(Debug, Clone, Copy, Default)]
22pub struct TailwindParser {
23 pub config: TailwindLanguage,
25}
26
27impl TailwindParser {
28 pub fn new(config: TailwindLanguage) -> Self {
30 Self { config }
31 }
32
33 fn parse_node<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<Option<TailwindNode>, OakError> {
34 match state.peek_kind() {
35 Some(TailwindTokenType::Directive) => {
36 let directive = self.parse_directive(state)?;
37 Ok(Some(TailwindNode::Directive(directive)))
38 }
39 Some(TailwindTokenType::Modifier) | Some(TailwindTokenType::Utility) | Some(TailwindTokenType::Important) | Some(TailwindTokenType::ArbitraryValue) => {
40 let class = self.parse_class(state)?;
41 Ok(Some(TailwindNode::Class(class)))
42 }
43 Some(TailwindTokenType::Comment) => {
44 let token = state.current().unwrap();
45 let start_pos = token.span.start;
46 let content = state.source.get_text_in(token.span).to_string();
47
48 let cp = state.checkpoint();
49 state.bump();
50 state.finish_at(cp, TailwindElementType::Comment);
51
52 let end_pos = state.current_offset();
53 Ok(Some(TailwindNode::Comment(TailwindComment { span: Range { start: start_pos, end: end_pos }, content })))
54 }
55 _ => {
56 state.advance();
57 Ok(None)
58 }
59 }
60 }
61
62 fn parse_class<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<TailwindClass, OakError> {
64 let checkpoint = state.checkpoint();
65 let start_pos = state.current_offset();
66 let mut is_important = false;
67
68 if state.eat(TailwindTokenType::Important) {
70 is_important = true;
71 }
72
73 let mut modifiers = Vec::new();
74 while state.at(TailwindTokenType::Modifier) {
76 let mod_token = state.current().unwrap();
77 let mod_range = mod_token.span;
78 let mod_text = state.source.get_text_in(mod_range).to_string();
79
80 let mod_cp = state.checkpoint();
81 state.bump();
82 state.finish_at(mod_cp, TailwindElementType::Modifier);
83
84 modifiers.push(TailwindModifier { span: mod_range, name: mod_text });
85 }
86
87 let kind = if state.at(TailwindTokenType::Utility) {
89 let util_token = state.current().unwrap();
90 let util_range = util_token.span;
91 let util_text = state.source.get_text_in(util_range).to_string();
92
93 let util_cp = state.checkpoint();
94 state.bump();
95 state.finish_at(util_cp, TailwindElementType::Utility);
96
97 TailwindClassKind::Utility(TailwindUtility { span: util_range, name: util_text })
98 }
99 else if state.at(TailwindTokenType::ArbitraryValue) {
100 let arb_token = state.current().unwrap();
101 let arb_range = arb_token.span;
102 let arb_text = state.source.get_text_in(arb_range).to_string();
103
104 let arb_cp = state.checkpoint();
105 state.bump();
106 state.finish_at(arb_cp, TailwindElementType::ArbitraryValue);
107
108 TailwindClassKind::ArbitraryValue(TailwindArbitraryValue { span: arb_range, value: arb_text })
109 }
110 else {
111 TailwindClassKind::Utility(TailwindUtility { span: Range { start: state.current_offset(), end: state.current_offset() }, name: String::new() })
113 };
114
115 if state.eat(TailwindTokenType::Important) {
117 is_important = true;
118 }
119
120 state.finish_at(checkpoint, TailwindElementType::Class);
121 let end_pos = state.current_offset();
122
123 Ok(TailwindClass { span: Range { start: start_pos, end: end_pos }, is_important, modifiers, kind })
124 }
125
126 fn parse_directive<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<TailwindDirective, OakError> {
128 let checkpoint = state.checkpoint();
129 let start_pos = state.current_offset();
130
131 let dir_token = state.current().unwrap();
132 let name = state.source.get_text_in(dir_token.span).to_string();
133 state.expect(TailwindTokenType::Directive)?;
134
135 let mut body_parts = Vec::new();
136 while state.not_at_end() && !state.at(TailwindTokenType::Semicolon) {
138 if let Some(token) = state.current() {
139 body_parts.push(state.source.get_text_in(token.span).to_string());
140 }
141 state.bump();
142 }
143
144 let body = if body_parts.is_empty() { None } else { Some(body_parts.join("")) };
145
146 state.eat(TailwindTokenType::Semicolon);
147 state.finish_at(checkpoint, TailwindElementType::Directive);
148 let end_pos = state.current_offset();
149
150 Ok(TailwindDirective { span: Range { start: start_pos, end: end_pos }, name, body })
151 }
152}
153
154impl Parser<TailwindLanguage> for TailwindParser {
155 fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<TailwindLanguage>) -> ParseOutput<'a, TailwindLanguage> {
157 let lexer = TailwindLexer::new(self.config);
158 let mut ast_nodes = Vec::new();
159
160 let output = parse_with_lexer(&lexer, text, edits, cache, |state| {
161 let checkpoint = state.checkpoint();
162 let start_pos = state.current_offset();
163
164 while state.not_at_end() {
165 if let Ok(Some(node)) = self.parse_node(state) {
166 ast_nodes.push(node);
167 }
168 }
169
170 let root_range = Range { start: start_pos, end: state.current_offset() };
171 let _root_ast = TailwindRoot::new(root_range, ast_nodes);
172 Ok(state.finish_at(checkpoint, TailwindElementType::Root))
176 });
177
178 output
179 }
180}