Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-values] nested calc() is not defined by its grammar #6506

Closed
cdoublev opened this issue Aug 10, 2021 · 6 comments
Closed

[css-values] nested calc() is not defined by its grammar #6506

cdoublev opened this issue Aug 10, 2021 · 6 comments

Comments

@cdoublev
Copy link
Collaborator

cdoublev commented Aug 10, 2021

Hello,

The grammar of <calc-value> (Mathematical expressions - Syntax, CSS Values) does not seem to allow calc() to be used recursively and in other math functions. This is only possible with simple blocks.

<calc-sum>     = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
<calc-product> = <calc-value> [ [ '*' | '/' ] <calc-value> ]*
<calc-value>   = <number> | <dimension> | <percentage> | <calc-constant> | ( <calc-sum> )

<calc()> = calc( <calc-sum> )
<min()> = min( <calc-sum># )
... other math functions

Would the following change be appropriate?

- <calc-value> = <number> | <dimension> | <percentage> | <calc-constant> | ( <calc-sum> )
+ <calc-value> = <number> | <dimension> | <percentage> | <calc-constant> | ( <calc-sum> ) | <calc()>

EDIT 1: or maybe the following change, to also allow other math functions?

- <calc-value> = <number> | <dimension> | <percentage> | <calc-constant> | ( <calc-sum> )
+ <calc-value> = <number> | <dimension> | <percentage> | <calc-constant> | ( <calc-sum> ) | <math-function>
+ <math-function> = <calc()> | <min()> | <max()> | <clamp()> | ...

EDIT 2: and now I realize that the following part answer this (part of my) issue.

A math function represents a numeric value [...] and can be used wherever such a value would be valid.

EDIT 3: duplicate of #1781.


To be honest, I'm creating this issue as a pretext to ask for clarifications about other math functions related parts of the specification that I'm not sure to understand correctly. I hope I will not waste anyone's time by asking stupid questions, and that they will help to improve some parts instead.

In the introduction of The internal representation (of math functions), it is written:

The internal representation [of any math function other than calc()] is an operator node with the same name as the function, whose children are the result of parsing a calculation from each of the function’s arguments, in the order they appear.

The round() function accepts a component value that is not a calculation as its arguments: <round()> = round( <rounding-strategy>?, <calc-sum>, <calc-sum> ). Am I right to think that the above definition was written after adding round() to the specification? round() does not seem to be handled also in the procedure to serialize a math function, which starts with "If the root of the calculation tree fn represents is a numeric value [...]".

For step 3 of the procedure to parse a calculation, it is written:

For every consecutive run of value items in values separated by "*" or "/" operators: [...]

What is a "run" in "every consecutive run of values"? Is it different from "every consecutive values"? Should 1 * 2 * 3 * 4 be reduced to a Product [[1, 2], [3, 4]], [[[[1, 2], 3], 4], or [1, 2, 3, 4]? Does it really matter? I believe that the procedure to simplify a calculation tree will be able to process and return the same result for these 3 different structures anyway, but I'm just not sure why this "consecutive run" term is used.

For step 4 of this same procedure, it is written:

If values has only one item, and it is a Product node or a parenthesized simple block [...]

Given the grammar of <calc-sum>, theoretically a sum can only receive <calc-product>. Is it correct that this means that a Product data structure is different from a component value matching the grammar of <calc-product>? If so, that's quite confusing to me. Is it about to avoid writing the grammar of <calc-sum> with <calc-value> | <calc-product> [ ['*' | '/'] <calc-product> ]?

In step 4 of the procedure to simplify a calculation tree, it is written:

If root is an operator node that’s not one of the calc-operator nodes, and all of its children are numeric values with enough information to compute the operation root represents [...]

Is it correct that root can only be a math function? If so, what is the reason for not using the math function term instead of an operator node that’s not one of the calc-operator nodes?

Also, it seems that none of the remaining steps of this procedure handle a math function containing non numeric values. Does a step 10. Return root is missing?

I see this note above the procedure to serialize a mathematical function:

This section is still under discussion.

But access to the link target is restricted to W3C members. Is it possible to give me clues for the following terms that appear in the different steps of this procedure:

  • the root of the calculation tree fn represents
  • fn represents
  • the calculation tree’s root node is

Do they mean different things?

@cdoublev cdoublev changed the title [css-values] nested calc () is not defined by its grammar [css-values] nested calc() is not defined by its grammar Aug 10, 2021
@tabatkins
Copy link
Member

The grammar of <calc-value> (Mathematical expressions - Syntax, CSS Values) does not seem to allow calc() to be used recursively and in other math functions.

The <number> | <dimension> | <percentage> part of the grammar covers that - those aren't the token productions, but the more generic value productions defined in V&U. In particular, <dimension> covers <length>, <time>, etc if you follow the link. Any function that resolves to a numeric value works there, such as attr() if it's specified to parse to a numeric type.

The round() function accepts a component value that is not a calculation as its arguments: <round()> = round( <rounding-strategy>?, <calc-sum>, <calc-sum> ). Am I right to think that the above definition was written after adding round() to the specification?

Ah, yeah, that text predates round() appearing with its non-numeric argument. I'll fix.

What is a "run" in "every consecutive run of values"?

I'm just using it as it standard English meaning, implying you take the maximal amount. In 1 * 2 * 3 * 4, it's all four values.

Does it really matter? I believe that the procedure to simplify a calculation tree will be able to process and return the same result for these 3 different structures anyway, but I'm just not sure why this "consecutive run" term is used.

In normal CSS it does not matter because it can't really be observed, but in Typed OM it's important because they'll produce different object graphs.

Given the grammar of , theoretically a sum can only receive . Is it correct that this means that a Product data structure is different from a component value matching the grammar of ? If so, that's quite confusing to me. Is it about to avoid writing the grammar of with | [ ['*' | '/'] ]?

No, it's just to properly handle the immediately preceding step. Starting with 1 * 2 * 3, this is turned into the list [1, *, 2, *, 3]. Step 3 collects those into [Product(1,2,3)]. Step 4 then sees that there's only a single child, and lifts it up so that values is just the Product(1,2,3) node, rather than wrapping it in a useless Sum(Product(1,2,3)).

Contrast with 1 * 2 + 3, where it starts as [1, *, 2, +, 3], step 3 collects the first few into [Product(1,2), +, 3], then Step 4 just dumps all of those items into a Sum(Product(1,2), 3) which is meaningful.

Is it correct that root can only be a math function? If so, what is the reason for not using the math function term instead of an operator node that’s not one of the calc-operator nodes?

Yes, that's correct. The following steps handle the calc-operator nodes, so it seemed clearer and less prone to later editing mistakes to explicitly exclude them from this step, rather than use an inclusive term that happens to not include them.

Also, it seems that none of the remaining steps of this procedure handle a math function containing non numeric values. Does a step 10. Return root is missing?

Are you referring to round() and its keyword argument? If so, then as I said above, that's just a missed edit that I'll fix.

If you're referring to anything else, can you provide an example? Non-numeric values in any other case should cause a parsing failure and thus never reach this algo.

I see this note above the procedure to serialize a mathematical function:

This section is still under discussion.

But access to the link target is restricted to W3C members.

Ugh, yeah it is; someone made a mistake and sent technical feedback to the private list in reply to a telcon agenda announcement. Don't worry, it's not relevant anymore and I should just remove it. (The behavior under discussion was resolved years ago and is widely implemented.)

Is it possible to give me clues for the following terms that appear in the different steps of this procedure:

  • the root of the calculation tree fn represents
  • fn represents
  • the calculation tree’s root node is

Do they mean different things?

No, they all mean the same thing. I'm just being a little loose with terminology there.

@tabatkins tabatkins added the css-values-4 Current Work label Aug 11, 2021
@cdoublev
Copy link
Collaborator Author

Thanks a lot. I think everything is clear to me now. I leave it to you to close this issue and/or change its title so that it targets the parts that must considerate the non-numeric argument of round ().

@chris-morgan
Copy link

The number case is expressed well:

Number values are denoted by ‘<number>’, and represent real numbers, possibly with a fractional component.

When written literally, a number is either an integer, […]. It corresponds to the <number-token> production in the CSS Syntax Module [CSS-SYNTAX-3].

This shows that it’s talking about values rather than tokens, obviously leaving it open for non-literals.

But, to potentially expand the scope of this issue a bit with a closely-related issue, some others don’t make this clear; strings, for example:

Strings are denoted by ‘<string>’ and consist of a sequence of characters delimited by double quotes or single quotes. They correspond to the <string-token> production in the CSS Syntax Module [CSS-SYNTAX-3].

This would seem to declare <string> to only be string literals. But I honestly don’t know if it allows non-literals or not, because some places that take <string> seem to allow non-literal strings implicitly, and others don’t. For example, as well as mentioning <string>, content spells out <counter> and <target>, which correspond to functions that all produce strings, but doesn’t make any mention of attr(), which can also produce a string and which works. But counters() says it takes a <string> as its second argument, but Firefox at least refuses to accept counters(name, attr(join, ".")), which I gather means it’s only accepting a literal string there.

This suggests to me that something needs to be changed in spec or implementation on content or counters(), because they seem to be inconsistent.

It’d be good for these sorts of types to spell it out like number does. e.g.

Strings are denoted by ‘<string>’ and represent a sequence of characters.

When written literally, a string is a sequence of characters delimited by double quotes or single quotes. It corresponds to the <string-token> production in the CSS Syntax Module [CSS-SYNTAX-3].

Or if it does exclude non-literal strings:

Literal strings are denoted by ‘<string>’ and consist of a sequence of characters delimited by double quotes or single quotes. They correspond to the <string-token> production in the CSS Syntax Module [CSS-SYNTAX-3].

@cdoublev
Copy link
Collaborator Author

Recently at the end of my days, the difference between what a type of value (literally) is vs. what a type of value represents, often becomes blurry to me. 🤪

This shows that it’s talking about values rather than tokens, obviously leaving it open for non-literals.

I currently believe that a <number> is a component value that represent a number. A component value is either one of the preverved tokens (which include <number-token>), a function, or a block.

My struggle was more related to missing the quoted definition below, which means to me that a CSS type representing a numeric (CSS component) value, ie. a <number-token>, a <dimension-token>, or a <percentage-token>, can be replaced (alternatively parsed with the grammar of) a math function, and I learned last week that a math function (a component value that is a function) that replaces a <number>, is itself a <number>:

A math function represents a numeric value [...] and can be used wherever such a value would be valid.

I'm not very used to reading technical specs and english is not my native language, so please take it as my very humble opinion: isn't attr() defined as a type that can replace a property value when a <string> is expected, ie. it can not replace a function argument that expects a <string>?

The attr() function substitutes the value of an attribute on an element into a property, similar to how the var() function substitutes a custom property value into a function.

@tabatkins
Copy link
Member

This would seem to declare <string> to only be string literals.

That's not intended, it's just not using the parallel better text from the number section. <string> is a generic production that can be literals or other functional values that return strings.

I'm not very used to reading technical specs and english is not my native language, so please take it as my very humble opinion: isn't attr() defined as a type that can replace a property value when a is expected, ie. it can not replace a function argument that expects a ?

Nope, it's just a value that represents a <string>, so it can be used anywhere a <string> can be used, such as in a function argument.

@tabatkins
Copy link
Member

All right, addressed the two lingering issues brought up here:

  • The calc simplification text now only tells you to recurse on calculation arguments, and asks if the calculation arguments are plain numbers, allowing non-calculation arguments (like the keyword arg to round() to be handled correctly.
  • The <string> section no longer implies that it's only for literal strings; instead, it uses similar wording to <number> and just describes "When written literally...".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants