This production is parsed in a SassScript context when an expression is expected
and the input stream starts with an identifier with value calc
or clamp
(ignoring case) followed immediately by (
.
The grammar for this production is:
CalculationExpression ::= CalcExpression | ClampExpression CalcExpression ::= 'calc('¹ CalcArgument ')' ClampExpression ::= 'clamp('¹ CalcArgument ( ',' CalcArgument ){2} ')' CalcArgument² ::= InterpolatedDeclarationValue† | CalcSum CalcSum ::= CalcProduct (('+' | '-')³ CalcProduct)* CalcProduct ::= CalcValue (('*' | '/') CalcValue)* CalcValue ::= ParenthesizedVar | '(' CalcArgument⁴ ')' | CalculationExpression | MinMaxExpression | FunctionExpression⁵ | Number | Variable† ParenthesizedVar ::= '(' 'var('¹ ArgumentInvocation ')' ')'
1: The strings calc(
, clamp(
, and var(
are matched case-insensitively.
2: A CalcArgument
is only parsed as an InterpolatedDeclarationValue
if it
includes interpolation, unless that interpolation is within a region bounded by
parentheses (a FunctionExpression
counts as parentheses).
3: Whitespace is required around these "+"
and "-"
tokens.
4: This CalcArgument
cannot begin with var(
, case-insensitively.
5: This FunctionExpression
cannot begin with min(
, max(
, or clamp(
,
case-insensitively.
†: These productions are invalid in plain CSS syntax.
The
CalcArgument
production provides backwards-compatibility with the historical use of interpolation to inject SassScript values intocalc()
expressions. Because interpolation could inject any part of acalc()
expression regardless of syntax, for full compatibility it's necessary to parse it very expansively.
CssMinMax ::= ('min(' | 'max(')¹ CalcArgument (',' CalcArgument)* ')'
1: The strings min(
and max(
are matched case-insensitively.
The value type known as a "calculation" has the following structure:
interface Calculation {
name: string;
arguments: CalculationValue[];
}
type CalculationValue =
| Number
| UnquotedString
| CalculationInterpolation
| CalculationOperation
| Calculation;
interface CalculationInterpolation {
value: string;
}
interface CalculationOperation {
operator: '+' | '-' | '*' | '/';
left: CalculationValue;
right: CalculationValue;
}
A calculation follows the default behavior of all SassScript operations, except that it throws an error if used as an operand of a:
- unary or binary
-
operation, - unary
+
operation, - binary
+
operation where the other operand is not a string,
and equality is defined as below.
This helps ensure that if a user expects a number and receives a calculation instead, it will throw an error quickly rather than propagating as an unquoted string. Binary
+
with a string is allowed specifically for backwards-compatibility with the$variable + ""
pattern for converting a value to a string to dynamically inspect it.
Two calculations are considered equal if their names are equal, they have the same number of arguments, and each argument in one calculation is equal to the corresponding argument in the other.
CalculationOperation
and CalculationInterpolation
values are equal if each
field in one value is equal to the corresponding field in the other.
To serialize a calculation, emit its name followed by "(", then each of its arguments separated by ",", then ")".
To serialize a CalculationOperation
:
-
Let
left
andright
be the result of serializing the left and right values, respectively. -
If either:
- the left value is a
CalculationInterpolation
, or - the operator is
"*"
or"/"
and the left value is aCalculationOperation
with operator"+"
or"-"
,
emit
"("
followed byleft
followed by")"
. Otherwise, emitleft
. - the left value is a
-
Emit
" "
, then the operator, then" "
. -
If either:
- the right value is a
CalculationInterpolation
, or - the operator is
"*"
or"-"
and the right value is aCalculationOperation
with operator"+"
or"-"
, or - the operator is
"/"
and the right value is aCalculationOperation
,
emit
"("
followed byright
followed by")"
. Otherwise, emitright
. - the right value is a
To serialize a CalculationInterpolation
, emit its value
.
This algorithm takes a calculation calc
and returns a number or a calculation.
This algorithm is intended to return a value that's CSS-semantically identical to the input.
-
If
calc
was parsed from an expression within aSupportsDeclaration
'sExpression
, but outside any interpolation, return acalc
as-is. -
Let
arguments
be the result of simplifying each ofcalc
's arguments. -
If
calc
's name is"calc"
, the syntax guarantees thatarguments
contain only a single argument. If that argument is a number or calculation, return it. -
If
calc
's name is"clamp"
,arguments
has fewer than three elements, and none of those are unquoted strings orCalculationInterpolation
s, throw an error.It's valid to write
clamp(var(--three-args))
orclamp(#{"1, 2, 3"})
, but otherwiseclamp()
has to have three physical arguments. -
If
calc
's name is"clamp"
andarguments
are all numbers:-
If those arguments' are mutually compatible, return the result of calling
math.clamp()
with those arguments. -
Otherwise, if any two of those arguments are definitely-incompatible, throw an error.
-
-
If
calc
's name is"min"
or"max"
andarguments
are all numbers:-
If the arguments with units are all mutually compatible, call
math.min()
ormath.max()
(respectively) with those arguments. If this doesn't throw an error, return its result.min()
andmax()
allow unitless numbers to be mixed with units because they need to be backwards-compatible with Sass's old globalmin()
andmax()
functions. -
Otherwise, if any two of those arguments are definitely-incompatible, throw an error.
-
-
Otherwise, return a calculation with the same name as
calc
andarguments
as its arguments.
This algorithm takes a CalculationValue
value
and returns a
CalculationValue
.
This algorithm is intended to return a value that's CSS-semantically identical to the input.
-
If
value
is a number, unquoted string, orCalculationInterpolation
, return it as-is. -
If
value
is a calculation:-
Let
result
be the result of simplifyingvalue
. -
If
result
is a calculation whose name is"calc"
, returnresult
's single argument. -
Otherwise, return
result
.
-
-
Otherwise,
value
must be aCalculationOperation
. Letleft
andright
be the result of simplifyingvalue.left
andvalue.right
, respectively. -
Let
operator
bevalue.operator
. -
If
operator
is"+"
or"-"
:-
If
left
andright
are both numbers with compatible units, returnleft + right
orleft - right
, respectively. -
Otherwise, if
left
andright
are both numbers, thename
of the innermostCalculation
that containsvalue
is"min"
or"max"
, and eitherleft
orright
is unitless, returnleft + right
orleft - right
, respectively.This preserves backwards-compatibility with Sass's old global
min()
andmax()
functions, most of which are now parsed asCssMinMax
es. -
Otherwise, if either
left
orright
is a number with more than one numerator unit or more than zero denominator units, throw an error. -
Otherwise, if
left
andright
are definitely-incompatible numbers, throw an error. -
If
right
is a number whose value is fuzzy-less-than zero, setright
toright * -1
and setoperator
to"-"
or"+"
, respectively. -
Return a
CalculationOperation
withoperator
,left
, andright
.
-
-
If
operator
is"*"
or"/"
:-
If
left
andright
are both numbers, returnleft * right
ormath.div(left, right)
, respectively. -
Otherwise, return a
CalculationOperation
withoperator
,left
, andright
.
-
To evaluate a CalcExpression
:
-
Let
calc
be a calculation whose name is"calc"
and whose only argument is the result of evaluating the expression'sCalcArgument
. -
Return the result of simplifying
calc
.
To evaluate a ClampExpression
:
-
Let
clamp
be a calculation whose name is"clamp"
and whose arguments are the results of evaluating the expression'sCalcArgument
s. -
Return the result of simplifying
clamp
.
To evaluate a CssMinMax
:
-
Let
calc
be a calculation whose name is"min"
or"max"
according to theCssMinMax
's first token, and whose arguments are the results of evaluating the expression'sCalcArgument
s. -
Return the result of simplifying
calc
.
To evaluate a CalcArgument
production argument
into a CalculationValue
object:
-
If
argument
is anInterpolatedDeclarationValue
, evaluate it and return aCalculationInterpolation
whosevalue
is the resulting string. -
Otherwise, return the result of evaluating
argument
'sCalcValue
.
To evaluate a CalcSum
production sum
into a CalculationValue
object:
-
Left
left
be the result of evaluating the firstCalcProduct
. -
For each remaining "+" or "-" token
operator
andCalcProduct
product
:-
Let
right
be the result of evaluatingproduct
. -
Set
left
to aCalcOperation
withoperator
,left
, andright
.
-
-
Return
left
.
To evaluate a CalcProduct
production product
into a CalculationValue
object:
-
Left
left
be the result of evaluating the firstCalcValue
. -
For each remaining "*" or "/" token
operator
andCalcValue
value
:-
Let
right
be the result of evaluatingvalue
. -
Set
left
to aCalcOperation
withoperator
,left
, andright
as its values.
-
-
Return
left
.
To evaluate a CalcValue
production value
into a CalculationValue
object:
-
If
value
is aCalcArgument
,CssMinMax
,Number
, orParenthesizedVar
, return the result of evaluating it. -
If
value
is aFunctionExpression
orVariable
, evaluate it. If the result is a number, an unquoted string, or a calculation, return it. Otherwise, throw an error.Allowing variables to return unquoted strings here supports referential transparency, so that
$var: fn(); calc($var)
works the same ascalc(fn())
.
If a
var()
is written directly within parentheses, it's necessary to preserve those parentheses. CSS resolvesvar()
by literally replacing the function with the value of the variable and then parsing the surrounding context.For example, if
--ratio: 2/3
,calc(1 / (var(--ratio)))
is parsed ascalc(1 / (2/3)) = calc(3/2)
butcalc(1 / var(--ratio))
is parsed ascalc(1 / 2/3) = calc(1/6)
.
To evaluate a ParenthesizedVar
production value
into an unquoted string:
-
Let
function
be aFunctionCall
with"var"
as itsNamespacedIdentifier
and withvalue
'sArgumentInvocation
. -
Let
result
be the result of evaluatingfunction
. -
If
result
is a number or a calculation, return it.This could happen if the user defines a
var
function in Sass. -
If
result
is not an unquoted string, throw an error. -
Return
"(" + result + ")"
as an unquoted string.