Skip to main content

css_ast/functions/
math_function.rs

1use super::prelude::*;
2use crate::{
3	AngleOrNumber, AttrFunction, EnvFunction, FirstValidFunction, IfFunction, NoneOr, Number, TreeCountingFunction,
4	Unresolved, VarFunction,
5};
6use css_parse::SemanticEq;
7
8/// ```text,ignore
9/// <calc()>  = calc( <calc-sum> )
10/// <min()>   = min( <calc-sum># )
11/// <max()>   = max( <calc-sum># )
12/// <clamp()> = clamp( [ <calc-sum> | none ], <calc-sum>, [ <calc-sum> | none ] )
13/// <round()> = round( <rounding-strategy>?, <calc-sum>, <calc-sum>? )
14/// <mod()>   = mod( <calc-sum>, <calc-sum> )
15/// <rem()>   = rem( <calc-sum>, <calc-sum> )
16/// <sin()>   = sin( <calc-sum> )
17/// <cos()>   = cos( <calc-sum> )
18/// <tan()>   = tan( <calc-sum> )
19/// <asin()>  = asin( <calc-sum> )
20/// <acos()>  = acos( <calc-sum> )
21/// <atan()>  = atan( <calc-sum> )
22/// <atan2()> = atan2( <calc-sum>, <calc-sum> )
23/// <pow()>   = pow( <calc-sum>, <calc-sum> )
24/// <sqrt()>  = sqrt( <calc-sum> )
25/// <hypot()> = hypot( <calc-sum># )
26/// <log()>   = log( <calc-sum>, <calc-sum>? )
27/// <exp()>   = exp( <calc-sum> )
28/// <abs()>   = abs( <calc-sum> )
29/// <sign()>  = sign( <calc-sum> )
30/// <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
31/// <calc-product> = <calc-value> [ [ '*' | / ] <calc-value> ]*
32/// <calc-value> = <number> | <dimension> | <percentage> |
33///                <calc-keyword> | ( <calc-sum> )
34/// <calc-keyword> = e | pi | infinity | -infinity | NaN
35/// <rounding-strategy> = nearest | up | down | to-zero | line-width
36/// ```
37#[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
67/// Returns true if the given atom is a CSS math function name.
68pub 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/// <https://drafts.csswg.org/css-values-4/#funcdef-clamp>
153///
154/// ```text,ignore
155/// clamp( [ <calc-sum> | none ], <calc-sum>, [ <calc-sum> | none ] )
156/// ```
157#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-round>
182///
183/// ```text,ignore
184/// round( <rounding-strategy>?, <calc-sum>, <calc-sum>? )
185/// ```
186#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-mod>
211///
212/// ```text,ignore
213/// mod( <calc-sum>, <calc-sum> )
214/// ```
215#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-rem>
236///
237/// ```text,ignore
238/// rem( <calc-sum>, <calc-sum> )
239/// ```
240#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-sin>
261///
262/// ```text,ignore
263/// sin( <calc-sum> )
264/// ```
265#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-cos>
282///
283/// ```text,ignore
284/// cos( <calc-sum> )
285/// ```
286#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-tan>
303///
304/// ```text,ignore
305/// tan( <calc-sum> )
306/// ```
307#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-asin>
324///
325/// ```text,ignore
326/// asin( <calc-sum> )
327/// ```
328#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-acos>
345///
346/// ```text,ignore
347/// acos( <calc-sum> )
348/// ```
349#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-atan>
366///
367/// ```text,ignore
368/// atan( <calc-sum> )
369/// ```
370#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-atan2>
387///
388/// ```text,ignore
389/// atan2( <calc-sum>, <calc-sum> )
390/// ```
391#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-pow>
412///
413/// ```text,ignore
414/// pow( <calc-sum>, <calc-sum> )
415/// ```
416#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-sqrt>
437///
438/// ```text,ignore
439/// sqrt( <calc-sum> )
440/// ```
441#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-hypot>
458///
459/// ```text,ignore
460/// hypot( <calc-sum># )
461/// ```
462#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-log>
479///
480/// ```text,ignore
481/// log( <calc-sum>, <calc-sum>? )
482/// ```
483#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-exp>
504///
505/// ```text,ignore
506/// exp( <calc-sum> )
507/// ```
508#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-abs>
525///
526/// ```text,ignore
527/// abs( <calc-sum> )
528/// ```
529#[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/// <https://drafts.csswg.org/css-values-4/#funcdef-sign>
546///
547/// ```text,ignore
548/// sign( <calc-sum> )
549/// ```
550#[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/// <https://drafts.csswg.org/css-values-4/#typedef-calc-sum>
567///
568/// ```text,ignore
569/// <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
570/// ```
571#[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/// The `+` or `-` operator inside a `<calc-sum>`.
582///
583/// Per <https://drafts.csswg.org/css-values-4/#calc-syntax>, whitespace is required on both sides
584/// of these operators (unlike `*` and `/`), since `1px -2px` (no space before `-`) must lex as two
585/// adjacent values rather than a subtraction. This is why `Parse`/`Peek` are hand-written here.
586#[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		// `+`/`-` in a `<calc-sum>` require whitespace on both sides (spec) and that whitespace is
612		// significant: `calc(1px+1px)` is invalid and stripping it would change tokenisation.
613		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/// A single operand of a `<calc-product>`: either a literal `<calc-value>` (number, dimension,
627/// percentage, calc-keyword, or parenthesized `<calc-sum>`), or an arbitrary substitution
628/// function (`var()`/`env()`/`attr()`/`if()`/`first-valid()`) per
629/// <https://drafts.csswg.org/css-values-5/#arbitrary-substitution-function>.
630#[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/// A substitution or nested math function appearing in a [`CalcOperand`] slot.
766#[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/// <https://drafts.csswg.org/css-values-4/#typedef-calc-product>
782///
783/// ```text,ignore
784/// <calc-product> = <calc-value> [ [ '*' | / ] <calc-value> ]*
785/// ```
786#[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/// The `*` or `/` operator inside a `<calc-product>`. Unlike `+`/`-`, no surrounding whitespace is
803/// required.
804#[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/// <https://drafts.csswg.org/css-values-4/#typedef-calc-value>
815///
816/// ```text,ignore
817/// <calc-value> = <number> | <dimension> | <percentage> | <calc-keyword> | ( <calc-sum> )
818///              | <sibling-count()> | <sibling-index()>
819/// ```
820#[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/// A parenthesized `<calc-sum>`, e.g. the `( 1px + 2px )` in `calc((1px + 2px) * 2)`.
835#[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/// <https://drafts.csswg.org/css-values-4/#typedef-calc-keyword>
850///
851/// ```text,ignore
852/// <calc-keyword> = e | pi | infinity | -infinity | NaN
853/// ```
854#[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/// <https://drafts.csswg.org/css-values-4/#typedef-rounding-strategy>
873///
874/// ```text,ignore
875/// <rounding-strategy> = nearest | up | down | to-zero | line-width
876/// ```
877#[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}