1use crate::{
20 CssParseError,
21 css_value::CssValue,
22 token_parser::TokenParser,
23 token_types::{SimpleToken, TokenList},
24};
25use std::fmt::Debug;
26
27pub type FlexParser = TokenParser<CssValue>;
29
30#[derive(Debug, Clone, PartialEq)]
32pub enum ParseContext {
33 Function(String),
35 Property(String),
37 Selector,
39 AtRule(String),
41 CalcExpression,
43 Default,
45}
46
47pub struct FlexCombinators;
49
50impl FlexCombinators {
51 pub fn mixed_sequence(parsers: Vec<FlexParser>) -> FlexParser {
53 TokenParser::new(
54 move |input| {
55 let mut results = Vec::new();
56 for parser in &parsers {
57 results.push(parser.run.as_ref()(input)?);
58 }
59 Ok(CssValue::sequence(results))
60 },
61 "mixed_sequence",
62 )
63 }
64
65 pub fn try_all(parsers: Vec<FlexParser>) -> FlexParser {
67 TokenParser::new(
68 move |input| {
69 let mut last_error = CssParseError::ParseError {
70 message: "No parsers provided".to_string(),
71 };
72
73 for parser in &parsers {
74 let checkpoint = input.current_index;
75 match parser.run.as_ref()(input) {
76 Ok(result) => return Ok(result),
77 Err(err) => {
78 input.set_current_index(checkpoint);
79 last_error = err;
80 },
81 }
82 }
83 Err(last_error)
84 },
85 "try_all",
86 )
87 }
88
89 pub fn context_parser<F>(selector: F) -> FlexParser
91 where
92 F: Fn(&ParseContext) -> FlexParser + 'static,
93 {
94 TokenParser::new(
95 move |input| {
96 let context = input.get_context().unwrap_or(ParseContext::Default);
97 let parser = selector(&context);
98 parser.run.as_ref()(input)
99 },
100 "context_aware",
101 )
102 }
103
104 pub fn parse_until(delimiter: SimpleToken, value_parser: FlexParser) -> FlexParser {
106 TokenParser::new(
107 move |input| {
108 let mut results = Vec::new();
109
110 while let Ok(Some(token)) = input.peek() {
111 if token == delimiter {
112 break;
113 }
114 match value_parser.run.as_ref()(input) {
115 Ok(value) => results.push(value),
116 Err(_) => break, }
118 }
119
120 Ok(CssValue::sequence(results))
121 },
122 "parse_until",
123 )
124 }
125
126 pub fn function_with_args(name: &'static str, arg_parser: FlexParser) -> FlexParser {
128 TokenParser::new(
129 move |input| {
130 match input.consume_next_token()? {
132 Some(SimpleToken::Function(fn_name)) if fn_name == name => {},
133 _ => {
134 return Err(CssParseError::ParseError {
135 message: format!("Expected function {}", name),
136 });
137 },
138 }
139
140 let mut args = Vec::new();
142 while let Ok(Some(token)) = input.peek() {
143 if token == SimpleToken::RightParen {
144 input.consume_next_token()?; break;
146 }
147 args.push(arg_parser.run.as_ref()(input)?);
148 }
149
150 Ok(CssValue::function(name, args))
151 },
152 &format!("function_{}", name),
153 )
154 }
155
156 pub fn comma_separated(value_parser: FlexParser) -> FlexParser {
158 TokenParser::new(
159 move |input| {
160 let mut results = Vec::new();
161
162 results.push(value_parser.run.as_ref()(input)?);
164
165 while let Ok(Some(SimpleToken::Comma)) = input.peek() {
167 input.consume_next_token()?; while let Ok(Some(SimpleToken::Whitespace)) = input.peek() {
171 input.consume_next_token()?;
172 }
173
174 results.push(value_parser.run.as_ref()(input)?);
175 }
176
177 Ok(CssValue::sequence(results))
178 },
179 "comma_separated",
180 )
181 }
182
183 pub fn with_suggestions(parser: FlexParser, suggestions: Vec<String>) -> FlexParser {
185 TokenParser::new(
186 move |input| {
187 match parser.run.as_ref()(input) {
188 Ok(result) => Ok(result),
189 Err(mut err) => {
190 if let CssParseError::ParseError { ref mut message } = err {
192 message.push_str(&format!(" Suggestions: {}", suggestions.join(", ")));
193 }
194 Err(err)
195 },
196 }
197 },
198 "with_suggestions",
199 )
200 }
201
202 pub fn optional_with_default(parser: FlexParser, default: CssValue) -> FlexParser {
204 TokenParser::new(
205 move |input| {
206 let checkpoint = input.current_index;
207 match parser.run.as_ref()(input) {
208 Ok(result) => Ok(result),
209 Err(_) => {
210 input.set_current_index(checkpoint);
211 Ok(default.clone())
212 },
213 }
214 },
215 "optional_with_default",
216 )
217 }
218}
219
220pub mod smart_tokens {
222 use super::*;
223 use crate::token_parser::tokens;
224
225 pub fn number() -> FlexParser {
227 tokens::number().map(
228 |token| match token {
229 SimpleToken::Number(n) => CssValue::Number(n),
230 _ => CssValue::None,
231 },
232 Some("smart_number"),
233 )
234 }
235
236 pub fn percentage() -> FlexParser {
238 tokens::percentage().map(
239 |token| match token {
240 SimpleToken::Percentage(p) => CssValue::Percentage(p),
241 _ => CssValue::None,
242 },
243 Some("smart_percentage"),
244 )
245 }
246
247 pub fn dimension() -> FlexParser {
249 tokens::dimension().map(
250 |token| match token {
251 SimpleToken::Dimension { value, unit } => CssValue::Dimension { value, unit },
252 _ => CssValue::None,
253 },
254 Some("smart_dimension"),
255 )
256 }
257
258 pub fn string() -> FlexParser {
260 tokens::string().map(
261 |token| match token {
262 SimpleToken::String(s) => CssValue::String(s),
263 _ => CssValue::None,
264 },
265 Some("smart_string"),
266 )
267 }
268
269 pub fn ident() -> FlexParser {
271 tokens::ident().map(
272 |token| match token {
273 SimpleToken::Ident(s) => CssValue::Ident(s),
274 _ => CssValue::None,
275 },
276 Some("smart_ident"),
277 )
278 }
279
280 pub fn numeric() -> FlexParser {
282 FlexCombinators::try_all(vec![number(), percentage(), dimension()])
283 }
284
285 pub fn textual() -> FlexParser {
287 FlexCombinators::try_all(vec![string(), ident()])
288 }
289}
290
291pub trait TokenListExt {
292 fn get_context(&self) -> Option<ParseContext>;
293 fn set_context(&mut self, context: ParseContext);
294}
295
296impl TokenListExt for TokenList {
297 fn get_context(&self) -> Option<ParseContext> {
298 Some(ParseContext::Default)
303 }
304
305 fn set_context(&mut self, _context: ParseContext) {
306 }
311}
312
313pub fn parse_rgb() -> FlexParser {
315 use smart_tokens::*;
316
317 FlexCombinators::function_with_args(
318 "rgb",
319 FlexCombinators::comma_separated(FlexCombinators::try_all(vec![
320 number().where_fn(
321 |v| {
322 if let Some(n) = v.as_number() {
323 (0.0..=255.0).contains(&n)
324 } else {
325 false
326 }
327 },
328 Some("valid_rgb_number"),
329 ),
330 percentage().where_fn(
331 |v| {
332 if let Some(p) = v.as_percentage() {
333 (0.0..=100.0).contains(&p)
334 } else {
335 false
336 }
337 },
338 Some("valid_rgb_percentage"),
339 ),
340 ])),
341 )
342}
343
344pub fn parse_hsl() -> FlexParser {
345 use smart_tokens::*;
346
347 FlexCombinators::function_with_args(
348 "hsl",
349 FlexCombinators::comma_separated(FlexCombinators::try_all(vec![
350 FlexCombinators::try_all(vec![
352 dimension().where_fn(
353 |v| v.has_unit("deg") || v.has_unit("rad") || v.has_unit("grad") || v.has_unit("turn"),
354 Some("valid_angle_unit"),
355 ),
356 number(), ]),
358 percentage(),
360 percentage(),
362 ])),
363 )
364}
365
366#[cfg(test)]
367mod tests {
368 use stylex_macros::stylex_panic;
369
370 use super::*;
371
372 #[test]
373 fn test_mixed_sequence() {
374 let values = vec![
376 CssValue::ident("rgb"),
377 CssValue::number(255.0),
378 CssValue::ident(","),
379 CssValue::number(0.0),
380 CssValue::ident(","),
381 CssValue::number(0.0),
382 ];
383
384 let result = CssValue::sequence(values);
385
386 if let Some(seq) = result.as_sequence() {
387 assert_eq!(seq.len(), 6);
388 assert_eq!(seq[0].as_string(), Some(&"rgb".to_string()));
389 assert_eq!(seq[1].as_number(), Some(255.0));
390 assert_eq!(seq[3].as_number(), Some(0.0));
391 assert_eq!(seq[5].as_number(), Some(0.0));
392 } else {
393 stylex_panic!("Expected sequence");
394 }
395 }
396
397 #[test]
398 fn test_try_all_flexibility() {
399 let numeric_value = CssValue::number(42.0);
401 let string_value = CssValue::string("auto");
402 let percentage_value = CssValue::percentage(50.0);
403
404 assert!(numeric_value.is_number());
406 assert!(string_value.is_string());
407 assert!(percentage_value.is_percentage());
408 }
409
410 #[test]
411 fn test_smart_tokens() {
412 let num_token = SimpleToken::Number(42.0);
414 let percent_token = SimpleToken::Percentage(50.0);
415 let dim_token = SimpleToken::Dimension {
416 value: 10.0,
417 unit: "px".to_string(),
418 };
419
420 let num_value: CssValue = num_token.into();
421 let percent_value: CssValue = percent_token.into();
422 let dim_value: CssValue = dim_token.into();
423
424 assert_eq!(num_value.as_number(), Some(42.0));
425 assert_eq!(percent_value.as_percentage(), Some(50.0));
426 assert_eq!(dim_value.as_dimension(), Some((10.0, &"px".to_string())));
427 }
428}