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<Box<'a, 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<Box<'a, 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, source_text: &str) -> bool {
647 calc_operand_eq(self, other, source_text)
648 }
649}
650
651fn calc_operand_eq<'a, T: SemanticEq>(a: &CalcOperand<'a, T>, b: &CalcOperand<'a, T>, source_text: &str) -> bool {
652 match (a, b) {
653 (CalcOperand::Literal(a), CalcOperand::Literal(b)) => calc_value_eq(a, b, source_text),
654 (CalcOperand::Substituted(a), CalcOperand::Substituted(b)) => calc_operand_substitution_eq(a, b, source_text),
655 (CalcOperand::Unresolved(a), CalcOperand::Unresolved(b)) => a.semantic_eq(b, source_text),
656 _ => false,
657 }
658}
659
660fn calc_value_eq<'a, T: SemanticEq>(a: &CalcValue<'a, T>, b: &CalcValue<'a, T>, source_text: &str) -> bool {
661 match (a, b) {
662 (CalcValue::Number(a), CalcValue::Number(b)) => a.semantic_eq(b, source_text),
663 (CalcValue::Typed(a), CalcValue::Typed(b)) => a.semantic_eq(b, source_text),
664 (CalcValue::Keyword(a), CalcValue::Keyword(b)) => a.semantic_eq(b, source_text),
665 (CalcValue::TreeCounting(a), CalcValue::TreeCounting(b)) => a.semantic_eq(b, source_text),
666 (CalcValue::Parenthesized(a), CalcValue::Parenthesized(b)) => calc_in_parens_eq(a, b, source_text),
667 _ => false,
668 }
669}
670
671fn calc_in_parens_eq<'a, T: SemanticEq>(a: &CalcInParens<'a, T>, b: &CalcInParens<'a, T>, source_text: &str) -> bool {
672 a.open.semantic_eq(&b.open, source_text) && calc_sum_eq(&a.sum, &b.sum, source_text)
673}
674
675fn calc_sum_eq<'a, T: SemanticEq>(a: &CalcSum<'a, T>, b: &CalcSum<'a, T>, source_text: &str) -> bool {
676 if a.rest.len() != b.rest.len() {
677 return false;
678 }
679 calc_product_eq(&a.first, &b.first, source_text)
680 && a.rest
681 .iter()
682 .zip(b.rest.iter())
683 .all(|((op_a, p_a), (op_b, p_b))| op_a == op_b && calc_product_eq(p_a, p_b, source_text))
684}
685
686fn calc_product_eq<'a, T: SemanticEq>(a: &CalcProduct<'a, T>, b: &CalcProduct<'a, T>, source_text: &str) -> bool {
687 if a.rest.len() != b.rest.len() {
688 return false;
689 }
690 calc_operand_eq(&a.first, &b.first, source_text)
691 && a.rest
692 .iter()
693 .zip(b.rest.iter())
694 .all(|((op_a, p_a), (op_b, p_b))| op_a == op_b && calc_operand_eq(p_a, p_b, source_text))
695}
696
697fn calc_operand_substitution_eq<'a, T: SemanticEq>(
698 a: &CalcOperandSubstitutionFunction<'a, T>,
699 b: &CalcOperandSubstitutionFunction<'a, T>,
700 source_text: &str,
701) -> bool {
702 match (a, b) {
703 (CalcOperandSubstitutionFunction::Math(a), CalcOperandSubstitutionFunction::Math(b)) => {
704 math_function_eq(a, b, source_text)
705 }
706 (CalcOperandSubstitutionFunction::Var(a), CalcOperandSubstitutionFunction::Var(b)) => {
707 a.semantic_eq(b, source_text)
708 }
709 (CalcOperandSubstitutionFunction::Env(a), CalcOperandSubstitutionFunction::Env(b)) => {
710 a.semantic_eq(b, source_text)
711 }
712 (CalcOperandSubstitutionFunction::Attr(a), CalcOperandSubstitutionFunction::Attr(b)) => {
713 a.semantic_eq(b, source_text)
714 }
715 (CalcOperandSubstitutionFunction::If(a), CalcOperandSubstitutionFunction::If(b)) => {
716 a.semantic_eq(b, source_text)
717 }
718 (CalcOperandSubstitutionFunction::FirstValid(a), CalcOperandSubstitutionFunction::FirstValid(b)) => {
719 a.semantic_eq(b, source_text)
720 }
721 _ => false,
722 }
723}
724
725fn math_function_eq<'a, T: SemanticEq>(a: &MathFunction<'a, T>, b: &MathFunction<'a, T>, source_text: &str) -> bool {
726 use MathFunction::*;
727 match (a, b) {
728 (CalcFunction(a), CalcFunction(b)) => calc_sum_eq(&a.params, &b.params, source_text),
729 (MinFunction(a), MinFunction(b)) => a.params.semantic_eq(&b.params, source_text),
730 (MaxFunction(a), MaxFunction(b)) => a.params.semantic_eq(&b.params, source_text),
731 (ClampFunction(a), ClampFunction(b)) => {
732 calc_sum_or_none_eq(&a.min, &b.min, source_text)
733 && calc_sum_eq(&a.value, &b.value, source_text)
734 && calc_sum_or_none_eq(&a.max, &b.max, source_text)
735 }
736 (RoundFunction(a), RoundFunction(b)) => {
737 a.strategy == b.strategy
738 && calc_sum_eq(&a.value, &b.value, source_text)
739 && calc_sum_opt_eq(&a.step, &b.step, source_text)
740 }
741 (ModFunction(a), ModFunction(b)) => {
742 calc_sum_eq(&a.dividend, &b.dividend, source_text) && calc_sum_eq(&a.divisor, &b.divisor, source_text)
743 }
744 (RemFunction(a), RemFunction(b)) => {
745 calc_sum_eq(&a.dividend, &b.dividend, source_text) && calc_sum_eq(&a.divisor, &b.divisor, source_text)
746 }
747 (SinFunction(a), SinFunction(b)) => a.semantic_eq(b, source_text),
748 (CosFunction(a), CosFunction(b)) => a.semantic_eq(b, source_text),
749 (TanFunction(a), TanFunction(b)) => a.semantic_eq(b, source_text),
750 (AsinFunction(a), AsinFunction(b)) => a.semantic_eq(b, source_text),
751 (AcosFunction(a), AcosFunction(b)) => a.semantic_eq(b, source_text),
752 (AtanFunction(a), AtanFunction(b)) => a.semantic_eq(b, source_text),
753 (Atan2Function(a), Atan2Function(b)) => a.semantic_eq(b, source_text),
754 (PowFunction(a), PowFunction(b)) => a.semantic_eq(b, source_text),
755 (SqrtFunction(a), SqrtFunction(b)) => a.semantic_eq(b, source_text),
756 (HypotFunction(a), HypotFunction(b)) => a.semantic_eq(b, source_text),
757 (LogFunction(a), LogFunction(b)) => a.semantic_eq(b, source_text),
758 (ExpFunction(a), ExpFunction(b)) => a.semantic_eq(b, source_text),
759 (AbsFunction(a), AbsFunction(b)) => a.semantic_eq(b, source_text),
760 (SignFunction(a), SignFunction(b)) => calc_sum_eq(&a.params, &b.params, source_text),
761 _ => false,
762 }
763}
764
765fn calc_sum_or_none_eq<'a, T: SemanticEq>(
766 a: &NoneOr<Box<'a, CalcSum<'a, T>>>,
767 b: &NoneOr<Box<'a, CalcSum<'a, T>>>,
768 source_text: &str,
769) -> bool {
770 match (a, b) {
771 (NoneOr::Some(a), NoneOr::Some(b)) => calc_sum_eq(a, b, source_text),
772 (NoneOr::None(_), NoneOr::None(_)) => true,
773 _ => false,
774 }
775}
776
777fn calc_sum_opt_eq<'a, T: SemanticEq>(
778 a: &Option<Box<'a, CalcSum<'a, T>>>,
779 b: &Option<Box<'a, CalcSum<'a, T>>>,
780 source_text: &str,
781) -> bool {
782 match (a, b) {
783 (None, None) => true,
784 (Some(a), Some(b)) => calc_sum_eq(a, b, source_text),
785 _ => false,
786 }
787}
788
789#[node]
791#[derive(Peek, Parse, ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
792#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
793#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
794#[derive(csskit_derives::NodeWithMetadata)]
795#[metadata(declaration_kinds = Computed)]
796pub enum CalcOperandSubstitutionFunction<'a, T> {
797 Math(Box<'a, MathFunction<'a, T>>),
798 Var(VarFunction<'a, CalcOperand<'a, T>>),
799 Env(EnvFunction<'a, CalcOperand<'a, T>>),
800 Attr(Box<'a, AttrFunction<'a>>),
801 If(IfFunction<'a, CalcOperand<'a, T>>),
802 FirstValid(FirstValidFunction<'a, CalcOperand<'a, T>>),
803}
804
805#[node]
811#[derive(Parse, Peek, ToCursors, ToSpan, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
812#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
813#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
814#[derive(csskit_derives::NodeWithMetadata)]
815pub struct CalcProduct<'a, T> {
816 pub first: CalcOperand<'a, T>,
817 pub rest: Vec<'a, (CalcProductOperator, CalcOperand<'a, T>)>,
818}
819
820impl<'a, T: SemanticEq> SemanticEq for CalcProduct<'a, T> {
821 fn semantic_eq(&self, other: &Self, source_text: &str) -> bool {
822 calc_product_eq(self, other, source_text)
823 }
824}
825
826#[node]
829#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
830#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
831#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
832#[derive(csskit_derives::NodeWithMetadata)]
833pub enum CalcProductOperator {
834 Multiply(T![*]),
835 Divide(T![/]),
836}
837
838#[node]
845#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
846#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
847#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(children))]
848#[derive(csskit_derives::NodeWithMetadata)]
849pub enum CalcValue<'a, T> {
850 #[cfg_attr(feature = "visitable", visit(skip))]
851 Number(Number),
852 Typed(T),
853 Keyword(CalcKeyword),
854 TreeCounting(TreeCountingFunction),
855 Parenthesized(CalcInParens<'a, T>),
856}
857
858#[node]
860#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
861#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
862#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
863#[derive(csskit_derives::NodeWithMetadata)]
864pub struct CalcInParens<'a, T> {
865 #[cfg_attr(feature = "visitable", visit(skip))]
866 pub open: LeftParen,
867 pub sum: Box<'a, CalcSum<'a, T>>,
868 #[cfg_attr(feature = "visitable", visit(skip))]
869 #[semantic_eq(skip)]
870 pub close: RightParen,
871}
872
873#[node]
879#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
880#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
881#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
882#[derive(csskit_derives::NodeWithMetadata)]
883pub enum CalcKeyword {
884 #[atom(CssAtomSet::E)]
885 E(Ident),
886 #[atom(CssAtomSet::Pi)]
887 Pi(Ident),
888 #[atom(CssAtomSet::Infinity)]
889 Infinity(Ident),
890 #[atom(CssAtomSet::_NegInfinity)]
891 NegativeInfinity(Ident),
892 #[atom(CssAtomSet::NaN)]
893 NaN(Ident),
894}
895
896#[node]
902#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
903#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
904#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
905#[derive(csskit_derives::NodeWithMetadata)]
906pub enum RoundingStrategy {
907 #[atom(CssAtomSet::Nearest)]
908 Nearest(Ident),
909 #[atom(CssAtomSet::Up)]
910 Up(Ident),
911 #[atom(CssAtomSet::Down)]
912 Down(Ident),
913 #[atom(CssAtomSet::ToZero)]
914 ToZero(Ident),
915 #[atom(CssAtomSet::LineWidth)]
916 LineWidth(Ident),
917}
918
919#[cfg(test)]
920mod tests {
921 use super::*;
922 use crate::{CssAtomSet, Length};
923 use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
924
925 type LengthMathFunction<'a> = MathFunction<'a, Length>;
926
927 #[test]
928 fn test_calc_functions() {
929 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(2 + 3)");
930 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(1, 2)");
931 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "max(3, 4)");
932 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(0, 5, 10)");
933 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(5.5)");
934 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "abs(-5)");
935 }
936
937 #[test]
938 fn test_clamp() {
939 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(none, 5px, 10px)");
940 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(0px, 5px, none)");
941 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(1px, 2px, 3px)");
942 }
943
944 #[test]
945 fn test_round() {
946 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(5.5, 2)");
947 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(up, 5.5)");
948 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(nearest, 5.5, 2)");
949 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(to-zero, 5.5)");
950 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "round(line-width, 5.5)");
951 }
952
953 #[test]
954 fn test_mod_rem() {
955 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "mod(18, 5)");
956 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "rem(18, 5)");
957 }
958
959 #[test]
960 fn test_trig_and_exponential() {
961 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sin(45deg)");
962 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "cos(45deg)");
963 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "tan(45deg)");
964 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "asin(0.5)");
965 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "acos(0.5)");
966 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "atan(0.5)");
967 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "atan2(1, 1)");
968 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "pow(2, 3)");
969 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sqrt(2)");
970 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "hypot(1, 2, 3)");
971 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "log(8, 2)");
972 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "log(8)");
973 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "exp(1)");
974 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "sign(-5)");
975 }
976
977 #[test]
978 fn test_calc_keywords() {
979 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(e)");
980 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(pi)");
981 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(infinity)");
982 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(-infinity)");
983 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(NaN)");
984 }
985
986 #[test]
987 fn test_calc_nesting_and_precedence() {
988 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + 2px * 3)");
989 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc((1px + 2px) * 3)");
990 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px * 2 / 3)");
991 }
992
993 #[test]
994 fn test_calc_substitution() {
995 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(var(--foo) * 2)");
996 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + var(--foo))");
997 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(var(--foo, 1px) + var(--bar, 2px))");
998 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(var(--foo), 10px)");
999 }
1000
1001 #[test]
1002 fn test_calc_nested_math_function() {
1003 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(min(1px, 2px) + 3px)");
1004 }
1005
1006 #[test]
1007 fn test_calc_tree_counting_functions() {
1008 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(sibling-index() * 10px)");
1009 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(10px * sibling-count())");
1010 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "min(sibling-index(), 4)");
1011 }
1012
1013 #[test]
1014 fn test_calc_operator_whitespace_sensitivity() {
1015 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + -2px)");
1016 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px*2)");
1017 assert_parse!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px/2)");
1018 }
1019
1020 #[test]
1021 fn test_calc_errors() {
1022 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px -2px)");
1023 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc()");
1024 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "clamp(1px, 2px)");
1025 assert_parse_error!(CssAtomSet::ATOMS, LengthMathFunction, "calc(1px + 10%)");
1026 assert_peek_false!(CssAtomSet::ATOMS, LengthMathFunction, "--calc(1px)");
1027 }
1028}