Skip to main content

stylex_css_parser/
css_value.rs

1/*!
2Universal CSS Value System for Dynamic Parsing
3
4This module implements a flexible value system that can represent any CSS value type,
5enabling dynamic parsing and type mixing in parser sequences.
6
7Key features:
8- Universal CssValue enum for any parsed result
9- Convenient property access (value.as_number(), etc.)
10- Dynamic type checking and conversion
11- Support for heterogeneous parser sequences
12*/
13
14use crate::{
15  css_types::{Angle, Color, Length, Percentage},
16  token_types::SimpleToken,
17};
18use std::fmt;
19
20/// Universal CSS value that can represent any parsed result
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum CssValue {
24  /// Raw number value: 42, 3.14, -5
25  Number(f64),
26
27  /// Percentage value: 50%, 100%
28  Percentage(f64),
29
30  /// Dimension with unit: 10px, 2em, 45deg
31  Dimension {
32    value: f64,
33    unit: String,
34  },
35
36  /// String value: "Arial", 'Times New Roman'
37  String(String),
38
39  /// Identifier: auto, none, inherit
40  Ident(String),
41
42  /// Function with arguments: rgb(255, 0, 0), calc(1px + 2em)
43  Function {
44    name: String,
45    args: Vec<CssValue>,
46  },
47
48  /// Sequence of values (for parser combinations)
49  Sequence(Vec<CssValue>),
50
51  /// Typed CSS values
52  Angle(Angle),
53  Color(Color),
54  Length(Length),
55
56  /// Unit tokens (for internal parsing)
57  Token(SimpleToken),
58
59  /// None/null value
60  None,
61}
62
63impl CssValue {
64  pub fn as_number(&self) -> Option<f64> {
65    match self {
66      CssValue::Number(n) => Some(*n),
67      CssValue::Percentage(p) => Some(*p),
68      CssValue::Dimension { value, .. } => Some(*value),
69      _ => None,
70    }
71  }
72
73  /// Extract percentage value: 50% -> 50.0
74  pub fn as_percentage(&self) -> Option<f64> {
75    match self {
76      CssValue::Percentage(p) => Some(*p),
77      _ => None,
78    }
79  }
80
81  /// Extract string/ident value
82  pub fn as_string(&self) -> Option<&String> {
83    match self {
84      CssValue::String(s) | CssValue::Ident(s) => Some(s),
85      _ => None,
86    }
87  }
88
89  /// Extract angle value
90  pub fn as_angle(&self) -> Option<&Angle> {
91    match self {
92      CssValue::Angle(a) => Some(a),
93      _ => None,
94    }
95  }
96
97  /// Extract color value
98  pub fn as_color(&self) -> Option<&Color> {
99    match self {
100      CssValue::Color(c) => Some(c),
101      _ => None,
102    }
103  }
104
105  /// Extract dimension parts
106  pub fn as_dimension(&self) -> Option<(f64, &String)> {
107    match self {
108      CssValue::Dimension { value, unit } => Some((*value, unit)),
109      _ => None,
110    }
111  }
112
113  /// Extract sequence items
114  pub fn as_sequence(&self) -> Option<&Vec<CssValue>> {
115    match self {
116      CssValue::Sequence(seq) => Some(seq),
117      _ => None,
118    }
119  }
120
121  /// Extract function name and args
122  pub fn as_function(&self) -> Option<(&String, &Vec<CssValue>)> {
123    match self {
124      CssValue::Function { name, args } => Some((name, args)),
125      _ => None,
126    }
127  }
128
129  pub fn is_number(&self) -> bool {
130    matches!(self, CssValue::Number(_))
131  }
132
133  pub fn is_percentage(&self) -> bool {
134    matches!(self, CssValue::Percentage(_))
135  }
136
137  pub fn is_dimension(&self) -> bool {
138    matches!(self, CssValue::Dimension { .. })
139  }
140
141  pub fn is_string(&self) -> bool {
142    matches!(self, CssValue::String(_))
143  }
144
145  pub fn is_ident(&self) -> bool {
146    matches!(self, CssValue::Ident(_))
147  }
148
149  pub fn is_function(&self) -> bool {
150    matches!(self, CssValue::Function { .. })
151  }
152
153  pub fn is_sequence(&self) -> bool {
154    matches!(self, CssValue::Sequence(_))
155  }
156
157  pub fn is_angle(&self) -> bool {
158    matches!(self, CssValue::Angle(_))
159  }
160
161  pub fn is_color(&self) -> bool {
162    matches!(self, CssValue::Color(_))
163  }
164
165  pub fn is_none(&self) -> bool {
166    matches!(self, CssValue::None)
167  }
168
169  /// Check if has specific unit
170  pub fn has_unit(&self, unit: &str) -> bool {
171    match self {
172      CssValue::Dimension { unit: u, .. } => u == unit,
173      _ => false,
174    }
175  }
176
177  /// Get unit if it's a dimension
178  pub fn get_unit(&self) -> Option<&String> {
179    match self {
180      CssValue::Dimension { unit, .. } => Some(unit),
181      _ => None,
182    }
183  }
184
185  /// Convenience constructors
186  pub fn number(value: f64) -> Self {
187    CssValue::Number(value)
188  }
189
190  pub fn percentage(value: f64) -> Self {
191    CssValue::Percentage(value)
192  }
193
194  pub fn dimension(value: f64, unit: impl Into<String>) -> Self {
195    CssValue::Dimension {
196      value,
197      unit: unit.into(),
198    }
199  }
200
201  pub fn string(value: impl Into<String>) -> Self {
202    CssValue::String(value.into())
203  }
204
205  pub fn ident(value: impl Into<String>) -> Self {
206    CssValue::Ident(value.into())
207  }
208
209  pub fn function(name: impl Into<String>, args: Vec<CssValue>) -> Self {
210    CssValue::Function {
211      name: name.into(),
212      args,
213    }
214  }
215
216  pub fn sequence(values: Vec<CssValue>) -> Self {
217    CssValue::Sequence(values)
218  }
219}
220
221impl fmt::Display for CssValue {
222  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223    match self {
224      CssValue::Number(n) => write!(f, "{}", n),
225      CssValue::Percentage(p) => write!(f, "{}%", p),
226      CssValue::Dimension { value, unit } => write!(f, "{}{}", value, unit),
227      CssValue::String(s) => write!(f, "\"{}\"", s),
228      CssValue::Ident(s) => write!(f, "{}", s),
229      CssValue::Function { name, args } => {
230        write!(f, "{}(", name)?;
231        for (i, arg) in args.iter().enumerate() {
232          if i > 0 {
233            write!(f, ", ")?;
234          }
235          write!(f, "{}", arg)?;
236        }
237        write!(f, ")")
238      },
239      CssValue::Sequence(seq) => {
240        for (i, item) in seq.iter().enumerate() {
241          if i > 0 {
242            write!(f, " ")?;
243          }
244          write!(f, "{}", item)?;
245        }
246        Ok(())
247      },
248      CssValue::Angle(a) => write!(f, "{}", a),
249      CssValue::Color(c) => write!(f, "{}", c),
250      CssValue::Length(l) => write!(f, "{}", l),
251      CssValue::Token(t) => write!(f, "{:?}", t), // Debug format for tokens
252      CssValue::None => write!(f, "none"),
253    }
254  }
255}
256
257/// Convert from specific CSS types to CssValue
258impl From<f64> for CssValue {
259  fn from(value: f64) -> Self {
260    CssValue::Number(value)
261  }
262}
263
264impl From<String> for CssValue {
265  fn from(value: String) -> Self {
266    CssValue::String(value)
267  }
268}
269
270impl From<&str> for CssValue {
271  fn from(value: &str) -> Self {
272    CssValue::String(value.to_string())
273  }
274}
275
276impl From<Angle> for CssValue {
277  fn from(value: Angle) -> Self {
278    CssValue::Angle(value)
279  }
280}
281
282impl From<Color> for CssValue {
283  fn from(value: Color) -> Self {
284    CssValue::Color(value)
285  }
286}
287
288impl From<Length> for CssValue {
289  fn from(value: Length) -> Self {
290    CssValue::Length(value)
291  }
292}
293
294impl From<Percentage> for CssValue {
295  fn from(value: Percentage) -> Self {
296    CssValue::Percentage(value.value as f64)
297  }
298}
299
300impl From<SimpleToken> for CssValue {
301  fn from(token: SimpleToken) -> Self {
302    match token {
303      SimpleToken::Number(n) => CssValue::Number(n),
304      SimpleToken::Percentage(p) => CssValue::Percentage(p),
305      SimpleToken::Dimension { value, unit } => CssValue::Dimension { value, unit },
306      SimpleToken::String(s) => CssValue::String(s),
307      SimpleToken::Ident(s) => CssValue::Ident(s),
308      _ => CssValue::Token(token),
309    }
310  }
311}
312
313#[cfg(test)]
314mod tests {
315  use super::*;
316
317  #[test]
318  fn test_css_value_creation() {
319    let num = CssValue::number(42.0);
320    let percent = CssValue::percentage(50.0);
321    let dim = CssValue::dimension(10.0, "px");
322    let str_val = CssValue::string("Arial");
323    let ident = CssValue::ident("auto");
324
325    assert_eq!(num.as_number(), Some(42.0));
326    assert_eq!(percent.as_percentage(), Some(50.0));
327    assert_eq!(dim.as_dimension(), Some((10.0, &"px".to_string())));
328    assert_eq!(str_val.as_string(), Some(&"Arial".to_string()));
329    assert_eq!(ident.as_string(), Some(&"auto".to_string()));
330  }
331
332  #[test]
333  fn test_type_checking() {
334    let num = CssValue::number(42.0);
335    let percent = CssValue::percentage(50.0);
336    let dim = CssValue::dimension(10.0, "px");
337
338    assert!(num.is_number());
339    assert!(!num.is_percentage());
340    assert!(!num.is_dimension());
341
342    assert!(percent.is_percentage());
343    assert!(!percent.is_number());
344
345    assert!(dim.is_dimension());
346    assert!(dim.has_unit("px"));
347    assert!(!dim.has_unit("em"));
348  }
349
350  #[test]
351  fn test_function_value() {
352    let func = CssValue::function(
353      "rgb",
354      vec![
355        CssValue::number(255.0),
356        CssValue::number(0.0),
357        CssValue::number(0.0),
358      ],
359    );
360
361    assert!(func.is_function());
362
363    if let Some((name, args)) = func.as_function() {
364      assert_eq!(name, "rgb");
365      assert_eq!(args.len(), 3);
366      assert_eq!(args[0].as_number(), Some(255.0));
367    }
368  }
369
370  #[test]
371  fn test_sequence_value() {
372    let seq = CssValue::sequence(vec![
373      CssValue::number(1.0),
374      CssValue::ident("solid"),
375      CssValue::ident("red"),
376    ]);
377
378    assert!(seq.is_sequence());
379
380    if let Some(items) = seq.as_sequence() {
381      assert_eq!(items.len(), 3);
382      assert!(items[0].is_number());
383      assert!(items[1].is_ident());
384      assert!(items[2].is_ident());
385    }
386  }
387
388  #[test]
389  fn test_display_formatting() {
390    let num = CssValue::number(42.0);
391    let percent = CssValue::percentage(50.0);
392    let dim = CssValue::dimension(10.0, "px");
393    let func = CssValue::function(
394      "calc",
395      vec![
396        CssValue::dimension(1.0, "px"),
397        CssValue::ident("+"),
398        CssValue::dimension(2.0, "em"),
399      ],
400    );
401
402    assert_eq!(num.to_string(), "42");
403    assert_eq!(percent.to_string(), "50%");
404    assert_eq!(dim.to_string(), "10px");
405    assert_eq!(func.to_string(), "calc(1px, +, 2em)");
406  }
407}