1use super::prelude::*;
2use crate::{
3 AngleOrNumber, AttrFunction, EnvFunction, FirstValidFunction, IfFunction, NoneOr, Number, TreeCountingFunction,
4 Unresolved, VarFunction,
5};
6use css_parse::SemanticEq;
7
8#[node]
38#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
40#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
41#[derive(csskit_derives::NodeWithMetadata)]
42#[metadata(declaration_kinds = Computed)]
43pub enum MathFunction<'a, T> {
44 CalcFunction(CalcFunction<'a, T>),
45 MinFunction(MinFunction<'a, T>),
46 MaxFunction(MaxFunction<'a, T>),
47 ClampFunction(ClampFunction<'a, T>),
48 RoundFunction(RoundFunction<'a, T>),
49 ModFunction(ModFunction<'a, T>),
50 RemFunction(RemFunction<'a, T>),
51 SinFunction(SinFunction<'a>),
52 CosFunction(CosFunction<'a>),
53 TanFunction(TanFunction<'a>),
54 AsinFunction(AsinFunction<'a>),
55 AcosFunction(AcosFunction<'a>),
56 AtanFunction(AtanFunction<'a>),
57 Atan2Function(Atan2Function<'a, T>),
58 PowFunction(PowFunction<'a>),
59 SqrtFunction(SqrtFunction<'a>),
60 HypotFunction(HypotFunction<'a, T>),
61 LogFunction(LogFunction<'a>),
62 ExpFunction(ExpFunction<'a>),
63 AbsFunction(AbsFunction<'a, T>),
64 SignFunction(SignFunction<'a, T>),
65}
66
67pub fn is_math_function(atom: CssAtomSet) -> bool {
69 matches!(
70 atom,
71 CssAtomSet::Calc
72 | CssAtomSet::Min
73 | CssAtomSet::Max
74 | CssAtomSet::Clamp
75 | CssAtomSet::Round
76 | CssAtomSet::Mod
77 | CssAtomSet::Rem
78 | CssAtomSet::Sin
79 | CssAtomSet::Cos
80 | CssAtomSet::Tan
81 | CssAtomSet::Asin
82 | CssAtomSet::Acos
83 | CssAtomSet::Atan
84 | CssAtomSet::Atan2
85 | CssAtomSet::Pow
86 | CssAtomSet::Sqrt
87 | CssAtomSet::Hypot
88 | CssAtomSet::Log
89 | CssAtomSet::Exp
90 | CssAtomSet::Abs
91 | CssAtomSet::Sign
92 )
93}
94
95impl<'a, T> Peek<'a> for MathFunction<'a, T> {
96 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
97 where
98 I: Iterator<Item = Cursor> + Clone,
99 {
100 <T![Function]>::peek(p, c) && is_math_function(p.to_atom::<CssAtomSet>(c))
101 }
102}
103
104#[node]
105#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
107#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
108#[derive(csskit_derives::NodeWithMetadata)]
109#[metadata(declaration_kinds = Computed)]
110pub struct CalcFunction<'a, T> {
111 #[cfg_attr(feature = "visitable", visit(skip))]
112 #[atom(CssAtomSet::Calc)]
113 pub name: Function,
114 pub params: CalcSum<'a, T>,
115 #[cfg_attr(feature = "visitable", visit(skip))]
116 #[semantic_eq(skip)]
117 pub close: RightParen,
118}
119
120#[node]
121#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
123#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
124#[derive(csskit_derives::NodeWithMetadata)]
125#[metadata(declaration_kinds = Computed)]
126pub struct MinFunction<'a, T> {
127 #[cfg_attr(feature = "visitable", visit(skip))]
128 #[atom(CssAtomSet::Min)]
129 pub name: Function,
130 pub params: CommaSeparated<'a, CalcSum<'a, T>>,
131 #[cfg_attr(feature = "visitable", visit(skip))]
132 #[semantic_eq(skip)]
133 pub close: RightParen,
134}
135
136#[node]
137#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
139#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
140#[derive(csskit_derives::NodeWithMetadata)]
141#[metadata(declaration_kinds = Computed)]
142pub struct MaxFunction<'a, T> {
143 #[cfg_attr(feature = "visitable", visit(skip))]
144 #[atom(CssAtomSet::Max)]
145 pub name: Function,
146 pub params: CommaSeparated<'a, CalcSum<'a, T>>,
147 #[cfg_attr(feature = "visitable", visit(skip))]
148 #[semantic_eq(skip)]
149 pub close: RightParen,
150}
151
152#[node]
158#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
160#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
161#[derive(csskit_derives::NodeWithMetadata)]
162#[metadata(declaration_kinds = Computed)]
163pub struct ClampFunction<'a, T> {
164 #[cfg_attr(feature = "visitable", visit(skip))]
165 #[atom(CssAtomSet::Clamp)]
166 pub name: Function,
167 pub min: NoneOr<CalcSum<'a, T>>,
168 #[cfg_attr(feature = "visitable", visit(skip))]
169 #[semantic_eq(skip)]
170 pub comma_1: Comma,
171 pub value: Box<'a, CalcSum<'a, T>>,
172 #[cfg_attr(feature = "visitable", visit(skip))]
173 #[semantic_eq(skip)]
174 pub comma_2: Comma,
175 pub max: NoneOr<CalcSum<'a, T>>,
176 #[cfg_attr(feature = "visitable", visit(skip))]
177 #[semantic_eq(skip)]
178 pub close: RightParen,
179}
180
181#[node]
187#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
189#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
190#[derive(csskit_derives::NodeWithMetadata)]
191#[metadata(declaration_kinds = Computed)]
192pub struct RoundFunction<'a, T> {
193 #[cfg_attr(feature = "visitable", visit(skip))]
194 #[atom(CssAtomSet::Round)]
195 pub name: Function,
196 pub strategy: Option<RoundingStrategy>,
197 #[cfg_attr(feature = "visitable", visit(skip))]
198 #[semantic_eq(skip)]
199 pub comma_1: Option<Comma>,
200 pub value: Box<'a, CalcSum<'a, T>>,
201 #[cfg_attr(feature = "visitable", visit(skip))]
202 #[semantic_eq(skip)]
203 pub comma_2: Option<Comma>,
204 pub step: Option<Box<'a, CalcSum<'a, T>>>,
205 #[cfg_attr(feature = "visitable", visit(skip))]
206 #[semantic_eq(skip)]
207 pub close: RightParen,
208}
209
210#[node]
216#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
218#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
219#[derive(csskit_derives::NodeWithMetadata)]
220#[metadata(declaration_kinds = Computed)]
221pub struct ModFunction<'a, T> {
222 #[cfg_attr(feature = "visitable", visit(skip))]
223 #[atom(CssAtomSet::Mod)]
224 pub name: Function,
225 pub dividend: CalcSum<'a, T>,
226 #[cfg_attr(feature = "visitable", visit(skip))]
227 #[semantic_eq(skip)]
228 pub comma: Comma,
229 pub divisor: Box<'a, CalcSum<'a, T>>,
230 #[cfg_attr(feature = "visitable", visit(skip))]
231 #[semantic_eq(skip)]
232 pub close: RightParen,
233}
234
235#[node]
241#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
243#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
244#[derive(csskit_derives::NodeWithMetadata)]
245#[metadata(declaration_kinds = Computed)]
246pub struct RemFunction<'a, T> {
247 #[cfg_attr(feature = "visitable", visit(skip))]
248 #[atom(CssAtomSet::Rem)]
249 pub name: Function,
250 pub dividend: CalcSum<'a, T>,
251 #[cfg_attr(feature = "visitable", visit(skip))]
252 #[semantic_eq(skip)]
253 pub comma: Comma,
254 pub divisor: Box<'a, CalcSum<'a, T>>,
255 #[cfg_attr(feature = "visitable", visit(skip))]
256 #[semantic_eq(skip)]
257 pub close: RightParen,
258}
259
260#[node]
266#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
268#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
269#[derive(csskit_derives::NodeWithMetadata)]
270#[metadata(declaration_kinds = Computed)]
271pub struct SinFunction<'a> {
272 #[cfg_attr(feature = "visitable", visit(skip))]
273 #[atom(CssAtomSet::Sin)]
274 pub name: Function,
275 pub params: CalcSum<'a, AngleOrNumber>,
276 #[cfg_attr(feature = "visitable", visit(skip))]
277 #[semantic_eq(skip)]
278 pub close: RightParen,
279}
280
281#[node]
287#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
289#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
290#[derive(csskit_derives::NodeWithMetadata)]
291#[metadata(declaration_kinds = Computed)]
292pub struct CosFunction<'a> {
293 #[cfg_attr(feature = "visitable", visit(skip))]
294 #[atom(CssAtomSet::Cos)]
295 pub name: Function,
296 pub params: CalcSum<'a, AngleOrNumber>,
297 #[cfg_attr(feature = "visitable", visit(skip))]
298 #[semantic_eq(skip)]
299 pub close: RightParen,
300}
301
302#[node]
308#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
309#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
310#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
311#[derive(csskit_derives::NodeWithMetadata)]
312#[metadata(declaration_kinds = Computed)]
313pub struct TanFunction<'a> {
314 #[cfg_attr(feature = "visitable", visit(skip))]
315 #[atom(CssAtomSet::Tan)]
316 pub name: Function,
317 pub params: CalcSum<'a, AngleOrNumber>,
318 #[cfg_attr(feature = "visitable", visit(skip))]
319 #[semantic_eq(skip)]
320 pub close: RightParen,
321}
322
323#[node]
329#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
331#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
332#[derive(csskit_derives::NodeWithMetadata)]
333#[metadata(declaration_kinds = Computed)]
334pub struct AsinFunction<'a> {
335 #[cfg_attr(feature = "visitable", visit(skip))]
336 #[atom(CssAtomSet::Asin)]
337 pub name: Function,
338 pub params: CalcSum<'a, Number>,
339 #[cfg_attr(feature = "visitable", visit(skip))]
340 #[semantic_eq(skip)]
341 pub close: RightParen,
342}
343
344#[node]
350#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
351#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
352#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
353#[derive(csskit_derives::NodeWithMetadata)]
354#[metadata(declaration_kinds = Computed)]
355pub struct AcosFunction<'a> {
356 #[cfg_attr(feature = "visitable", visit(skip))]
357 #[atom(CssAtomSet::Acos)]
358 pub name: Function,
359 pub params: CalcSum<'a, Number>,
360 #[cfg_attr(feature = "visitable", visit(skip))]
361 #[semantic_eq(skip)]
362 pub close: RightParen,
363}
364
365#[node]
371#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
372#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
373#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
374#[derive(csskit_derives::NodeWithMetadata)]
375#[metadata(declaration_kinds = Computed)]
376pub struct AtanFunction<'a> {
377 #[cfg_attr(feature = "visitable", visit(skip))]
378 #[atom(CssAtomSet::Atan)]
379 pub name: Function,
380 pub params: CalcSum<'a, Number>,
381 #[cfg_attr(feature = "visitable", visit(skip))]
382 #[semantic_eq(skip)]
383 pub close: RightParen,
384}
385
386#[node]
392#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
393#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
394#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
395#[derive(csskit_derives::NodeWithMetadata)]
396#[metadata(declaration_kinds = Computed)]
397pub struct Atan2Function<'a, T> {
398 #[cfg_attr(feature = "visitable", visit(skip))]
399 #[atom(CssAtomSet::Atan2)]
400 pub name: Function,
401 pub y: CalcSum<'a, T>,
402 #[cfg_attr(feature = "visitable", visit(skip))]
403 #[semantic_eq(skip)]
404 pub comma: Comma,
405 pub x: Box<'a, CalcSum<'a, T>>,
406 #[cfg_attr(feature = "visitable", visit(skip))]
407 #[semantic_eq(skip)]
408 pub close: RightParen,
409}
410
411#[node]
417#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
418#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
419#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
420#[derive(csskit_derives::NodeWithMetadata)]
421#[metadata(declaration_kinds = Computed)]
422pub struct PowFunction<'a> {
423 #[cfg_attr(feature = "visitable", visit(skip))]
424 #[atom(CssAtomSet::Pow)]
425 pub name: Function,
426 pub base: CalcSum<'a, Number>,
427 #[cfg_attr(feature = "visitable", visit(skip))]
428 #[semantic_eq(skip)]
429 pub comma: Comma,
430 pub exponent: Box<'a, CalcSum<'a, Number>>,
431 #[cfg_attr(feature = "visitable", visit(skip))]
432 #[semantic_eq(skip)]
433 pub close: RightParen,
434}
435
436#[node]
442#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
443#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
444#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
445#[derive(csskit_derives::NodeWithMetadata)]
446#[metadata(declaration_kinds = Computed)]
447pub struct SqrtFunction<'a> {
448 #[cfg_attr(feature = "visitable", visit(skip))]
449 #[atom(CssAtomSet::Sqrt)]
450 pub name: Function,
451 pub params: CalcSum<'a, Number>,
452 #[cfg_attr(feature = "visitable", visit(skip))]
453 #[semantic_eq(skip)]
454 pub close: RightParen,
455}
456
457#[node]
463#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
464#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
465#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
466#[derive(csskit_derives::NodeWithMetadata)]
467#[metadata(declaration_kinds = Computed)]
468pub struct HypotFunction<'a, T> {
469 #[cfg_attr(feature = "visitable", visit(skip))]
470 #[atom(CssAtomSet::Hypot)]
471 pub name: Function,
472 pub params: CommaSeparated<'a, CalcSum<'a, T>>,
473 #[cfg_attr(feature = "visitable", visit(skip))]
474 #[semantic_eq(skip)]
475 pub close: RightParen,
476}
477
478#[node]
484#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
485#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
486#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
487#[derive(csskit_derives::NodeWithMetadata)]
488#[metadata(declaration_kinds = Computed)]
489pub struct LogFunction<'a> {
490 #[cfg_attr(feature = "visitable", visit(skip))]
491 #[atom(CssAtomSet::Log)]
492 pub name: Function,
493 pub value: CalcSum<'a, Number>,
494 #[cfg_attr(feature = "visitable", visit(skip))]
495 #[semantic_eq(skip)]
496 pub comma: Option<Comma>,
497 pub base: Option<Box<'a, CalcSum<'a, Number>>>,
498 #[cfg_attr(feature = "visitable", visit(skip))]
499 #[semantic_eq(skip)]
500 pub close: RightParen,
501}
502
503#[node]
509#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
510#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
511#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
512#[derive(csskit_derives::NodeWithMetadata)]
513#[metadata(declaration_kinds = Computed)]
514pub struct ExpFunction<'a> {
515 #[cfg_attr(feature = "visitable", visit(skip))]
516 #[atom(CssAtomSet::Exp)]
517 pub name: Function,
518 pub params: CalcSum<'a, Number>,
519 #[cfg_attr(feature = "visitable", visit(skip))]
520 #[semantic_eq(skip)]
521 pub close: RightParen,
522}
523
524#[node]
530#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
531#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
532#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
533#[derive(csskit_derives::NodeWithMetadata)]
534#[metadata(declaration_kinds = Computed)]
535pub struct AbsFunction<'a, T> {
536 #[cfg_attr(feature = "visitable", visit(skip))]
537 #[atom(CssAtomSet::Abs)]
538 pub name: Function,
539 pub params: CalcSum<'a, T>,
540 #[cfg_attr(feature = "visitable", visit(skip))]
541 #[semantic_eq(skip)]
542 pub close: RightParen,
543}
544
545#[node]
551#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
552#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
553#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
554#[derive(csskit_derives::NodeWithMetadata)]
555#[metadata(declaration_kinds = Computed)]
556pub struct SignFunction<'a, T> {
557 #[cfg_attr(feature = "visitable", visit(skip))]
558 #[atom(CssAtomSet::Sign)]
559 pub name: Function,
560 pub params: CalcSum<'a, T>,
561 #[cfg_attr(feature = "visitable", visit(skip))]
562 #[semantic_eq(skip)]
563 pub close: RightParen,
564}
565
566#[node]
572#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
573#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
574#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
575#[derive(csskit_derives::NodeWithMetadata)]
576pub struct CalcSum<'a, T> {
577 pub first: CalcProduct<'a, T>,
578 pub rest: Vec<'a, (CalcSumOperator, CalcProduct<'a, T>)>,
579}
580
581#[node]
587#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
588#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
589#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
590#[derive(csskit_derives::NodeWithMetadata)]
591pub enum CalcSumOperator {
592 Add(T![+]),
593 Subtract(T![-]),
594}
595
596impl<'a> Peek<'a> for CalcSumOperator {
597 fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
598 where
599 I: Iterator<Item = Cursor> + Clone,
600 {
601 c == Kind::Delim && (c == '+' || c == '-') && p.peek_n_including_whitespace(1) == Kind::Whitespace
602 }
603}
604
605impl<'a> Parse<'a> for CalcSumOperator {
606 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
607 where
608 I: Iterator<Item = Cursor> + Clone,
609 {
610 let c = p.peek_n(1);
611 let rules = AssociatedWhitespaceRules::EnforceBefore | AssociatedWhitespaceRules::EnforceAfter;
614 let op = if c == '+' {
615 Self::Add(p.parse::<T![+]>()?.with_associated_whitespace(rules))
616 } else {
617 Self::Subtract(p.parse::<T![-]>()?.with_associated_whitespace(rules))
618 };
619 if p.peek_n_including_whitespace(1) != Kind::Whitespace {
620 Err(Diagnostic::new(p.peek_n(1), Diagnostic::unexpected_delim))?;
621 }
622 Ok(op)
623 }
624}
625
626#[node]
631#[derive(Peek, ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
632#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
633#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
634#[derive(csskit_derives::NodeWithMetadata)]
635#[metadata(declaration_kinds = Computed)]
636pub enum CalcOperand<'a, T> {
637 Literal(CalcValue<'a, T>),
638 Substituted(Box<'a, CalcOperandSubstitutionFunction<'a, T>>),
639 #[peek(skip)]
640 Unresolved(Box<'a, Unresolved<'a>>),
641}
642
643crate::values::impl_value_slot_parse!(CalcOperand, CalcOperandSubstitutionFunction, CalcValue<T>);
644
645impl<'a, T: SemanticEq> SemanticEq for CalcOperand<'a, T> {
646 fn semantic_eq(&self, other: &Self) -> bool {
647 calc_operand_eq(self, other)
648 }
649}
650
651fn calc_operand_eq<'a, T: SemanticEq>(a: &CalcOperand<'a, T>, b: &CalcOperand<'a, T>) -> bool {
652 match (a, b) {
653 (CalcOperand::Literal(a), CalcOperand::Literal(b)) => calc_value_eq(a, b),
654 (CalcOperand::Substituted(a), CalcOperand::Substituted(b)) => calc_operand_substitution_eq(a, b),
655 (CalcOperand::Unresolved(a), CalcOperand::Unresolved(b)) => a.semantic_eq(b),
656 _ => false,
657 }
658}
659
660fn calc_value_eq<'a, T: SemanticEq>(a: &CalcValue<'a, T>, b: &CalcValue<'a, T>) -> bool {
661 match (a, b) {
662 (CalcValue::Number(a), CalcValue::Number(b)) => a.semantic_eq(b),
663 (CalcValue::Typed(a), CalcValue::Typed(b)) => a.semantic_eq(b),
664 (CalcValue::Keyword(a), CalcValue::Keyword(b)) => a.semantic_eq(b),
665 (CalcValue::TreeCounting(a), CalcValue::TreeCounting(b)) => a.semantic_eq(b),
666 (CalcValue::Parenthesized(a), CalcValue::Parenthesized(b)) => calc_in_parens_eq(a, b),
667 _ => false,
668 }
669}
670
671fn calc_in_parens_eq<'a, T: SemanticEq>(a: &CalcInParens<'a, T>, b: &CalcInParens<'a, T>) -> bool {
672 a.open.semantic_eq(&b.open) && calc_sum_eq(&a.sum, &b.sum)
673}
674
675fn calc_sum_eq<'a, T: SemanticEq>(a: &CalcSum<'a, T>, b: &CalcSum<'a, T>) -> bool {
676 if a.rest.len() != b.rest.len() {
677 return false;
678 }
679 calc_product_eq(&a.first, &b.first)
680 && a.rest.iter().zip(b.rest.iter()).all(|((op_a, p_a), (op_b, p_b))| op_a == op_b && calc_product_eq(p_a, p_b))
681}
682
683fn calc_product_eq<'a, T: SemanticEq>(a: &CalcProduct<'a, T>, b: &CalcProduct<'a, T>) -> bool {
684 if a.rest.len() != b.rest.len() {
685 return false;
686 }
687 calc_operand_eq(&a.first, &b.first)
688 && a.rest.iter().zip(b.rest.iter()).all(|((op_a, p_a), (op_b, p_b))| op_a == op_b && calc_operand_eq(p_a, p_b))
689}
690
691fn calc_operand_substitution_eq<'a, T: SemanticEq>(
692 a: &CalcOperandSubstitutionFunction<'a, T>,
693 b: &CalcOperandSubstitutionFunction<'a, T>,
694) -> bool {
695 match (a, b) {
696 (CalcOperandSubstitutionFunction::Math(a), CalcOperandSubstitutionFunction::Math(b)) => math_function_eq(a, b),
697 (CalcOperandSubstitutionFunction::Var(a), CalcOperandSubstitutionFunction::Var(b)) => a.semantic_eq(b),
698 (CalcOperandSubstitutionFunction::Env(a), CalcOperandSubstitutionFunction::Env(b)) => a.semantic_eq(b),
699 (CalcOperandSubstitutionFunction::Attr(a), CalcOperandSubstitutionFunction::Attr(b)) => a.semantic_eq(b),
700 (CalcOperandSubstitutionFunction::If(a), CalcOperandSubstitutionFunction::If(b)) => a.semantic_eq(b),
701 (CalcOperandSubstitutionFunction::FirstValid(a), CalcOperandSubstitutionFunction::FirstValid(b)) => {
702 a.semantic_eq(b)
703 }
704 _ => false,
705 }
706}
707
708fn math_function_eq<'a, T: SemanticEq>(a: &MathFunction<'a, T>, b: &MathFunction<'a, T>) -> bool {
709 use MathFunction::*;
710 match (a, b) {
711 (CalcFunction(a), CalcFunction(b)) => calc_sum_eq(&a.params, &b.params),
712 (MinFunction(a), MinFunction(b)) => a.params.semantic_eq(&b.params),
713 (MaxFunction(a), MaxFunction(b)) => a.params.semantic_eq(&b.params),
714 (ClampFunction(a), ClampFunction(b)) => {
715 calc_sum_or_none_eq(&a.min, &b.min)
716 && calc_sum_eq(&a.value, &b.value)
717 && calc_sum_or_none_eq(&a.max, &b.max)
718 }
719 (RoundFunction(a), RoundFunction(b)) => {
720 a.strategy == b.strategy && calc_sum_eq(&a.value, &b.value) && calc_sum_opt_eq(&a.step, &b.step)
721 }
722 (ModFunction(a), ModFunction(b)) => {
723 calc_sum_eq(&a.dividend, &b.dividend) && calc_sum_eq(&a.divisor, &b.divisor)
724 }
725 (RemFunction(a), RemFunction(b)) => {
726 calc_sum_eq(&a.dividend, &b.dividend) && calc_sum_eq(&a.divisor, &b.divisor)
727 }
728 (SinFunction(a), SinFunction(b)) => a.semantic_eq(b),
729 (CosFunction(a), CosFunction(b)) => a.semantic_eq(b),
730 (TanFunction(a), TanFunction(b)) => a.semantic_eq(b),
731 (AsinFunction(a), AsinFunction(b)) => a.semantic_eq(b),
732 (AcosFunction(a), AcosFunction(b)) => a.semantic_eq(b),
733 (AtanFunction(a), AtanFunction(b)) => a.semantic_eq(b),
734 (Atan2Function(a), Atan2Function(b)) => a.semantic_eq(b),
735 (PowFunction(a), PowFunction(b)) => a.semantic_eq(b),
736 (SqrtFunction(a), SqrtFunction(b)) => a.semantic_eq(b),
737 (HypotFunction(a), HypotFunction(b)) => a.semantic_eq(b),
738 (LogFunction(a), LogFunction(b)) => a.semantic_eq(b),
739 (ExpFunction(a), ExpFunction(b)) => a.semantic_eq(b),
740 (AbsFunction(a), AbsFunction(b)) => a.semantic_eq(b),
741 (SignFunction(a), SignFunction(b)) => calc_sum_eq(&a.params, &b.params),
742 _ => false,
743 }
744}
745
746fn calc_sum_or_none_eq<'a, T: SemanticEq>(a: &NoneOr<CalcSum<'a, T>>, b: &NoneOr<CalcSum<'a, T>>) -> bool {
747 match (a, b) {
748 (NoneOr::Some(a), NoneOr::Some(b)) => calc_sum_eq(a, b),
749 (NoneOr::None(_), NoneOr::None(_)) => true,
750 _ => false,
751 }
752}
753
754fn calc_sum_opt_eq<'a, T: SemanticEq>(
755 a: &Option<Box<'a, CalcSum<'a, T>>>,
756 b: &Option<Box<'a, CalcSum<'a, T>>>,
757) -> bool {
758 match (a, b) {
759 (None, None) => true,
760 (Some(a), Some(b)) => calc_sum_eq(a, b),
761 _ => false,
762 }
763}
764
765#[node]
767#[derive(Peek, Parse, ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
768#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
769#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
770#[derive(csskit_derives::NodeWithMetadata)]
771#[metadata(declaration_kinds = Computed)]
772pub enum CalcOperandSubstitutionFunction<'a, T> {
773 Math(MathFunction<'a, T>),
774 Var(VarFunction<'a, CalcOperand<'a, T>>),
775 Env(EnvFunction<'a, CalcOperand<'a, T>>),
776 Attr(AttrFunction<'a>),
777 If(IfFunction<'a, CalcOperand<'a, T>>),
778 FirstValid(FirstValidFunction<'a, CalcOperand<'a, T>>),
779}
780
781#[node]
787#[derive(Parse, Peek, ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
788#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
789#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
790#[derive(csskit_derives::NodeWithMetadata)]
791pub struct CalcProduct<'a, T> {
792 pub first: CalcOperand<'a, T>,
793 pub rest: Vec<'a, (CalcProductOperator, CalcOperand<'a, T>)>,
794}
795
796impl<'a, T: SemanticEq> SemanticEq for CalcProduct<'a, T> {
797 fn semantic_eq(&self, other: &Self) -> bool {
798 calc_product_eq(self, other)
799 }
800}
801
802#[node]
805#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
806#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
807#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
808#[derive(csskit_derives::NodeWithMetadata)]
809pub enum CalcProductOperator {
810 Multiply(T![*]),
811 Divide(T![/]),
812}
813
814#[node]
821#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
822#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
823#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
824#[derive(csskit_derives::NodeWithMetadata)]
825pub enum CalcValue<'a, T> {
826 #[cfg_attr(feature = "visitable", visit(skip))]
827 Number(Number),
828 Typed(T),
829 Keyword(CalcKeyword),
830 TreeCounting(TreeCountingFunction),
831 Parenthesized(CalcInParens<'a, T>),
832}
833
834#[node]
836#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
837#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
838#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
839#[derive(csskit_derives::NodeWithMetadata)]
840pub struct CalcInParens<'a, T> {
841 #[cfg_attr(feature = "visitable", visit(skip))]
842 pub open: LeftParen,
843 pub sum: Box<'a, CalcSum<'a, T>>,
844 #[cfg_attr(feature = "visitable", visit(skip))]
845 #[semantic_eq(skip)]
846 pub close: RightParen,
847}
848
849#[node]
855#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
856#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
857#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
858#[derive(csskit_derives::NodeWithMetadata)]
859pub enum CalcKeyword {
860 #[atom(CssAtomSet::E)]
861 E(Ident),
862 #[atom(CssAtomSet::Pi)]
863 Pi(Ident),
864 #[atom(CssAtomSet::Infinity)]
865 Infinity(Ident),
866 #[atom(CssAtomSet::_NegInfinity)]
867 NegativeInfinity(Ident),
868 #[atom(CssAtomSet::NaN)]
869 NaN(Ident),
870}
871
872#[node]
878#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
879#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
880#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
881#[derive(csskit_derives::NodeWithMetadata)]
882pub enum RoundingStrategy {
883 #[atom(CssAtomSet::Nearest)]
884 Nearest(Ident),
885 #[atom(CssAtomSet::Up)]
886 Up(Ident),
887 #[atom(CssAtomSet::Down)]
888 Down(Ident),
889 #[atom(CssAtomSet::ToZero)]
890 ToZero(Ident),
891 #[atom(CssAtomSet::LineWidth)]
892 LineWidth(Ident),
893}
894
895#[cfg(test)]
896mod tests {
897 use super::*;
898 use crate::{CssAtomSet, Length};
899 use css_parse::{assert_parse, assert_parse_error};
900
901 type LengthMathFunction<'a> = MathFunction<'a, Length>;
902
903 #[test]
904 fn test_calc_functions() {
905 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(2 + 3)");
906 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(1, 2)");
907 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "max(3, 4)");
908 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(0, 5, 10)");
909 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(5.5)");
910 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "abs(-5)");
911 }
912
913 #[test]
914 fn test_clamp() {
915 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(none, 5px, 10px)");
916 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(0px, 5px, none)");
917 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(1px, 2px, 3px)");
918 }
919
920 #[test]
921 fn test_round() {
922 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(5.5, 2)");
923 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(up, 5.5)");
924 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(nearest, 5.5, 2)");
925 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(to-zero, 5.5)");
926 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(line-width, 5.5)");
927 }
928
929 #[test]
930 fn test_mod_rem() {
931 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "mod(18, 5)");
932 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "rem(18, 5)");
933 }
934
935 #[test]
936 fn test_trig_and_exponential() {
937 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sin(45deg)");
938 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "cos(45deg)");
939 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "tan(45deg)");
940 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "asin(0.5)");
941 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "acos(0.5)");
942 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "atan(0.5)");
943 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "atan2(1, 1)");
944 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "pow(2, 3)");
945 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sqrt(2)");
946 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "hypot(1, 2, 3)");
947 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "log(8, 2)");
948 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "log(8)");
949 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "exp(1)");
950 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sign(-5)");
951 }
952
953 #[test]
954 fn test_calc_keywords() {
955 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(e)");
956 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(pi)");
957 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(infinity)");
958 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(-infinity)");
959 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(NaN)");
960 }
961
962 #[test]
963 fn test_calc_nesting_and_precedence() {
964 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + 2px * 3)");
965 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc((1px + 2px) * 3)");
966 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px * 2 / 3)");
967 }
968
969 #[test]
970 fn test_calc_substitution() {
971 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(var(--foo) * 2)");
972 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + var(--foo))");
973 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(var(--foo, 1px) + var(--bar, 2px))");
974 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(var(--foo), 10px)");
975 }
976
977 #[test]
978 fn test_calc_nested_math_function() {
979 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(min(1px, 2px) + 3px)");
980 }
981
982 #[test]
983 fn test_calc_tree_counting_functions() {
984 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(sibling-index() * 10px)");
985 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(10px * sibling-count())");
986 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(sibling-index(), 4)");
987 }
988
989 #[test]
990 fn test_calc_operator_whitespace_sensitivity() {
991 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + -2px)");
992 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px*2)");
993 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px/2)");
994 }
995
996 #[test]
997 fn test_calc_errors() {
998 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px -2px)");
999 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc()");
1000 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(1px, 2px)");
1001 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + 10%)");
1002 }
1003}