1use stylex_macros::stylex_unreachable;
8
9use crate::{
10 CssParseError,
11 css_types::{calc_constant::CalcConstant, common_types::Percentage},
12 token_parser::TokenParser,
13 token_types::{SimpleToken, TokenList},
14};
15use std::fmt::{self, Display};
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct CalcDimension {
20 pub value: f32,
21 pub unit: String,
22}
23
24impl CalcDimension {
25 pub fn new(value: f32, unit: String) -> Self {
26 Self { value, unit }
27 }
28}
29
30impl Display for CalcDimension {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "{}{}", self.value, self.unit)
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct Addition {
39 pub left: Box<CalcValue>,
40 pub right: Box<CalcValue>,
41}
42
43impl Addition {
44 pub fn new(left: CalcValue, right: CalcValue) -> Self {
45 Self {
46 left: Box::new(left),
47 right: Box::new(right),
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub struct Subtraction {
55 pub left: Box<CalcValue>,
56 pub right: Box<CalcValue>,
57}
58
59impl Subtraction {
60 pub fn new(left: CalcValue, right: CalcValue) -> Self {
61 Self {
62 left: Box::new(left),
63 right: Box::new(right),
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
70pub struct Multiplication {
71 pub left: Box<CalcValue>,
72 pub right: Box<CalcValue>,
73}
74
75impl Multiplication {
76 pub fn new(left: CalcValue, right: CalcValue) -> Self {
77 Self {
78 left: Box::new(left),
79 right: Box::new(right),
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
86pub struct Division {
87 pub left: Box<CalcValue>,
88 pub right: Box<CalcValue>,
89}
90
91impl Division {
92 pub fn new(left: CalcValue, right: CalcValue) -> Self {
93 Self {
94 left: Box::new(left),
95 right: Box::new(right),
96 }
97 }
98}
99
100#[derive(Debug, Clone, PartialEq)]
102pub struct Group {
103 pub expr: Box<CalcValue>,
104}
105
106impl Group {
107 pub fn new(expr: CalcValue) -> Self {
108 Self {
109 expr: Box::new(expr),
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq)]
116pub enum CalcValueOrOperator {
117 Value(CalcValue),
118 Operator(String),
119}
120
121#[derive(Debug, Clone, PartialEq)]
122pub enum CalcValue {
123 Number(f32),
125 Dimension(CalcDimension),
127 Percentage(Percentage),
129 Constant(CalcConstant),
131 Addition(Addition),
133 Subtraction(Subtraction),
135 Multiplication(Multiplication),
137 Division(Division),
139 Group(Group),
141}
142
143impl CalcValue {
144 pub fn value_parser() -> TokenParser<CalcValue> {
146 TokenParser::new(Self::parse_calc_value, "calc_value")
147 }
148
149 fn parse_calc_value(tokens: &mut TokenList) -> Result<CalcValue, CssParseError> {
151 let token = tokens
152 .consume_next_token()?
153 .ok_or(CssParseError::ParseError {
154 message: "Expected Number, Dimension, Percentage, or Constant token".to_string(),
155 })?;
156
157 match token {
158 SimpleToken::Number(value) => Ok(CalcValue::Number(value as f32)),
159 SimpleToken::Dimension { value, unit } => {
160 Ok(CalcValue::Dimension(CalcDimension::new(value as f32, unit)))
161 },
162 SimpleToken::Percentage(value) => {
163 Ok(CalcValue::Percentage(Percentage::new(
166 (value * 100.0) as f32,
167 )))
168 },
169 SimpleToken::Ident(name) => {
170 match CalcConstant::parse(&name) {
172 Some(constant) => Ok(CalcValue::Constant(constant)),
173 None => Err(CssParseError::ParseError {
174 message: format!("Unknown calc constant: {}", name),
175 }),
176 }
177 },
178 _ => Err(CssParseError::ParseError {
179 message: format!(
180 "Expected Number, Dimension, Percentage, or Constant token, got {:?}",
181 token
182 ),
183 }),
184 }
185 }
186
187 pub fn parser() -> TokenParser<CalcValue> {
188 TokenParser::new(Self::parse_calc_expression, "calc_expression")
189 }
190
191 fn parse_calc_expression(tokens: &mut TokenList) -> Result<CalcValue, CssParseError> {
193 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
195 tokens.consume_next_token()?;
196 }
197
198 let first_value = match Self::try_parse_parenthesized_group(tokens) {
200 Ok(grouped) => CalcValue::Group(grouped),
201 Err(_) => Self::parse_calc_value(tokens)?,
202 };
203
204 let mut values_and_operators = vec![CalcValueOrOperator::Value(first_value)];
206
207 loop {
208 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
210 tokens.consume_next_token()?;
211 }
212
213 if let Ok(Some(SimpleToken::RightParen)) = tokens.peek() {
215 break;
217 }
218
219 let checkpoint = tokens.current_index;
221 match Self::try_parse_operator(tokens) {
222 Ok(operator) => {
223 values_and_operators.push(CalcValueOrOperator::Operator(operator));
224
225 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
227 tokens.consume_next_token()?;
228 }
229
230 let next_value = match Self::try_parse_parenthesized_group(tokens) {
232 Ok(grouped) => CalcValue::Group(grouped),
233 Err(_) => Self::parse_calc_value(tokens)?,
234 };
235 values_and_operators.push(CalcValueOrOperator::Value(next_value));
236 },
237 Err(_) => {
238 tokens.set_current_index(checkpoint);
240 break;
241 },
242 }
243 }
244
245 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
247 tokens.consume_next_token()?;
248 }
249
250 if values_and_operators.len() == 1 {
252 if let CalcValueOrOperator::Value(value) = &values_and_operators[0] {
253 Ok(value.clone())
254 } else {
255 stylex_unreachable!("First element should always be a value")
256 }
257 } else {
258 Self::split_by_multiplication_or_division(values_and_operators)
259 }
260 }
261
262 fn split_by_multiplication_or_division(
264 values_and_operators: Vec<CalcValueOrOperator>,
265 ) -> Result<CalcValue, CssParseError> {
266 if values_and_operators.len() == 1 {
267 match &values_and_operators[0] {
268 CalcValueOrOperator::Value(value) => return Ok(value.clone()),
269 CalcValueOrOperator::Operator(_) => {
270 return Err(CssParseError::ParseError {
271 message: "Invalid operator".to_string(),
272 });
273 },
274 }
275 }
276
277 let first_operator = values_and_operators
279 .iter()
280 .position(|item| matches!(item, CalcValueOrOperator::Operator(op) if op == "*" || op == "/"));
281
282 match first_operator {
283 None => {
284 Self::compose_add_and_subtraction(values_and_operators)
286 },
287 Some(op_index) => {
288 let left_slice = values_and_operators[..op_index].to_vec();
289 let right_slice = values_and_operators[op_index + 1..].to_vec();
290
291 if let CalcValueOrOperator::Operator(operator) = &values_and_operators[op_index] {
292 let left = Self::compose_add_and_subtraction(left_slice)?;
293 let right = Self::split_by_multiplication_or_division(right_slice)?;
294
295 match operator.as_str() {
296 "*" => Ok(CalcValue::Multiplication(Multiplication::new(left, right))),
297 "/" => Ok(CalcValue::Division(Division::new(left, right))),
298 _ => Err(CssParseError::ParseError {
299 message: "Invalid operator".to_string(),
300 }),
301 }
302 } else {
303 Err(CssParseError::ParseError {
304 message: "Expected operator".to_string(),
305 })
306 }
307 },
308 }
309 }
310
311 fn compose_add_and_subtraction(
313 values_and_operators: Vec<CalcValueOrOperator>,
314 ) -> Result<CalcValue, CssParseError> {
315 if values_and_operators.len() == 1 {
316 match &values_and_operators[0] {
317 CalcValueOrOperator::Value(value) => return Ok(value.clone()),
318 CalcValueOrOperator::Operator(op) => {
319 return Err(CssParseError::ParseError {
320 message: format!("Invalid operator: {}", op),
321 });
322 },
323 }
324 }
325
326 let first_operator = values_and_operators
328 .iter()
329 .position(|item| matches!(item, CalcValueOrOperator::Operator(op) if op == "+" || op == "-"));
330
331 match first_operator {
332 None => Err(CssParseError::ParseError {
333 message: "No valid operator found".to_string(),
334 }),
335 Some(op_index) => {
336 let left_slice = values_and_operators[..op_index].to_vec();
337 let right_slice = values_and_operators[op_index + 1..].to_vec();
338
339 if let CalcValueOrOperator::Operator(operator) = &values_and_operators[op_index] {
340 let left = Self::compose_add_and_subtraction(left_slice)?;
341 let right = Self::compose_add_and_subtraction(right_slice)?;
342
343 match operator.as_str() {
344 "+" => Ok(CalcValue::Addition(Addition::new(left, right))),
345 "-" => Ok(CalcValue::Subtraction(Subtraction::new(left, right))),
346 _ => Err(CssParseError::ParseError {
347 message: "Invalid operator".to_string(),
348 }),
349 }
350 } else {
351 Err(CssParseError::ParseError {
352 message: "Expected operator".to_string(),
353 })
354 }
355 },
356 }
357 }
358
359 fn try_parse_parenthesized_group(tokens: &mut TokenList) -> Result<Group, CssParseError> {
361 let checkpoint = tokens.current_index;
363
364 let open_paren_token = tokens
366 .consume_next_token()?
367 .ok_or(CssParseError::ParseError {
368 message: "Expected opening parenthesis".to_string(),
369 })?;
370
371 if !matches!(open_paren_token, SimpleToken::LeftParen) {
372 tokens.set_current_index(checkpoint);
374 return Err(CssParseError::ParseError {
375 message: "Expected '(' token".to_string(),
376 });
377 }
378
379 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
381 tokens.consume_next_token()?;
382 }
383
384 let inner_expr = Self::parse_calc_expression(tokens)?;
386
387 while let Ok(Some(SimpleToken::Whitespace)) = tokens.peek() {
389 tokens.consume_next_token()?;
390 }
391
392 let close_paren_token = tokens
394 .consume_next_token()?
395 .ok_or(CssParseError::ParseError {
396 message: "Expected closing parenthesis".to_string(),
397 })?;
398
399 if !matches!(close_paren_token, SimpleToken::RightParen) {
400 return Err(CssParseError::ParseError {
401 message: "Expected ')' token".to_string(),
402 });
403 }
404
405 Ok(Group::new(inner_expr))
406 }
407
408 fn try_parse_operator(tokens: &mut TokenList) -> Result<String, CssParseError> {
410 let token = tokens
411 .consume_next_token()?
412 .ok_or(CssParseError::ParseError {
413 message: "Expected operator token".to_string(),
414 })?;
415
416 match token {
417 SimpleToken::Delim('+') => Ok("+".to_string()),
418 SimpleToken::Delim('-') => Ok("-".to_string()),
419 SimpleToken::Delim('*') => Ok("*".to_string()),
420 SimpleToken::Delim('/') => Ok("/".to_string()),
421 _ => Err(CssParseError::ParseError {
422 message: format!("Expected operator (+, -, *, /), got {:?}", token),
423 }),
424 }
425 }
426}
427
428impl Display for CalcValue {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 match self {
431 CalcValue::Number(n) => write!(f, "{}", n),
432 CalcValue::Dimension(d) => write!(f, "{}", d),
433 CalcValue::Percentage(p) => write!(f, "{}", p),
434 CalcValue::Constant(c) => write!(f, "{}", c),
435 CalcValue::Addition(op) => write!(f, "{} + {}", op.left, op.right),
436 CalcValue::Subtraction(op) => write!(f, "{} - {}", op.left, op.right),
437 CalcValue::Multiplication(op) => write!(f, "{} * {}", op.left, op.right),
438 CalcValue::Division(op) => write!(f, "{} / {}", op.left, op.right),
439 CalcValue::Group(group) => write!(f, "({})", group.expr),
440 }
441 }
442}
443
444#[derive(Debug, Clone, PartialEq)]
446pub struct Calc {
447 pub value: CalcValue,
448}
449
450impl Calc {
451 pub fn new(value: CalcValue) -> Self {
453 Calc { value }
454 }
455
456 pub fn parse() -> TokenParser<Calc> {
458 Self::parser()
459 }
460
461 pub fn parser() -> TokenParser<Calc> {
463 TokenParser::new(
465 |tokens| {
466 let token = tokens
468 .consume_next_token()?
469 .ok_or(CssParseError::ParseError {
470 message: "Expected calc function".to_string(),
471 })?;
472
473 if let SimpleToken::Function(fn_name) = token {
474 if fn_name != "calc" {
475 return Err(CssParseError::ParseError {
476 message: format!("Expected calc function, got {}", fn_name),
477 });
478 }
479 } else {
480 return Err(CssParseError::ParseError {
481 message: "Expected function token".to_string(),
482 });
483 }
484
485 let calc_value = CalcValue::parse_calc_expression(tokens)?;
487
488 let close_token = tokens
490 .consume_next_token()?
491 .ok_or(CssParseError::ParseError {
492 message: "Expected closing parenthesis".to_string(),
493 })?;
494
495 if !matches!(close_token, SimpleToken::RightParen) {
496 return Err(CssParseError::ParseError {
497 message: "Expected closing parenthesis".to_string(),
498 });
499 }
500
501 Ok(Calc::new(calc_value))
502 },
503 "calc_parser",
504 )
505 }
506}
507
508impl Display for Calc {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 write!(f, "calc({})", self.value)
511 }
512}
513
514pub fn calc_value_to_string(value: &CalcValue) -> String {
515 match value {
516 CalcValue::Number(n) => n.to_string(),
517 CalcValue::Dimension(d) => d.to_string(),
518 CalcValue::Percentage(p) => p.to_string(),
519 CalcValue::Constant(c) => c.to_string(),
520 CalcValue::Addition(op) => format!(
521 "{} + {}",
522 calc_value_to_string(&op.left),
523 calc_value_to_string(&op.right)
524 ),
525 CalcValue::Subtraction(op) => format!(
526 "{} - {}",
527 calc_value_to_string(&op.left),
528 calc_value_to_string(&op.right)
529 ),
530 CalcValue::Multiplication(op) => format!(
531 "{} * {}",
532 calc_value_to_string(&op.left),
533 calc_value_to_string(&op.right)
534 ),
535 CalcValue::Division(op) => format!(
536 "{} / {}",
537 calc_value_to_string(&op.left),
538 calc_value_to_string(&op.right)
539 ),
540 CalcValue::Group(group) => format!("({})", calc_value_to_string(&group.expr)),
541 }
542}
543
544