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

[First-Class Calc] Add an initial draft #2983

Merged
merged 7 commits into from Jan 9, 2021
Merged

[First-Class Calc] Add an initial draft #2983

merged 7 commits into from Jan 9, 2021

Conversation

nex3
Copy link
Contributor

@nex3 nex3 commented Dec 31, 2020

See #818

Note that this is not expected to be the finalized proposal, and it
has a number of outstanding TODOs. However, since it's a highly
impactful proposal, I want to get a draft committed so we can start
sending it around to stakeholders, getting feedback, and iterating.

proposal/first-class-calc.md Outdated Show resolved Hide resolved
proposal/first-class-calc.md Outdated Show resolved Hide resolved
### Operations

A calc follows the default behavior of all SassScript operations, except that it
throws an error if used as an operand of a unary or binary `+` or `-` operation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the * operator ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default behavior for *, / and % is to throw an error. + and - are special in that they default to converting their operands to strings.

* Let `arguments` be the result of [simplifying](#simplifying-a-calcvalue) each
of `calc`'s arguments.

* If `calc`'s name is `"calc"`, `arguments` must contain a single argument. If
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must here looks weird to me. It could be interpreted 2 ways:

  • it triggers an error if not satisfied (the usual meaning of must in a spec)
  • it must be valid to satisfy the if here.

From the explanation in the PR, I understand that you mean the second case. But that would be better written as a condition in the if instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually means "the parser only allows a calc with name "calc" that has one argument". I'll make that more explicit.


* If `calc`'s name is `min`, `max`, or `clamp` and `arguments` are all numbers
whose units are mutually [compatible], return the result of calling
[`math.min()`], [`math.max()`], or `math.clamp()` (respectively) with those
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no link for math.clamp here ? Why ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It hasn't been added to the math spec yet.


* Emit `" "`, then the operator, then `" "`.

* If the operator is `"*"` or `"/"` and the right value is a `CalcOperation`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not sound when the left or right values are interpolated calcs. To be safe, parenthesis should be added in such cases (similar to the TODO you have in the simplification algorithm)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a TODO.

Copy link
Member

@jathak jathak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall, though I wonder if it makes sense to refer to the data type by a different name than just calc to avoid confusion between ones named "calc" and ones with other names (even just calling the type by the full word "calculation" could make things a bit clearer).

proposal/first-class-calc.md Outdated Show resolved Hide resolved
proposal/first-class-calc.md Outdated Show resolved Hide resolved
proposal/first-class-calc.md Outdated Show resolved Hide resolved
@stof
Copy link
Contributor

stof commented Jan 6, 2021

LGTM overall, though I wonder if it makes sense to refer to the data type by a different name than just calc to avoid confusion between ones named "calc" and ones with other names (even just calling the type by the full word "calculation" could make things a bit clearer).

I tend to agree here

proposal/first-class-calc.md Outdated Show resolved Hide resolved
> numbers of any units, and numbers across different known types (length +
> time) are also invalid.

> TODO: Should we try to simplify `calc(1px + 1% + 1px)`?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For here and other similar TODOs below: what are the cons of simplifying this as much as possible? It seems to me that simplifying to calc(2px + 1%) would be the most logically consistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require a more complex structure for the AST, because the 1px + 1% + 1px would be represented as CalcOperation(1px, '+', CalcOperation(1%, '+', 1px)) currently while simplifying such case would require recombining the sum to try another variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternately, it would require complexity in the simplification logic to whether CalcOperations can be "flattened" usefully. The CSS spec actually does cover this sort of simplification logic, so there's prior art, but it's definitely more complex.

return-on-investment would also be inherently limited, since we're planning
on gradually transitioning users away from interpolation in `calc()` anyway.

2. Treating interpolation as a `CalcValue` that participates in the normal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are formally defined below, but it could be helpful to introduce them earlier on and use the terms throughout.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't think of a good way to summarize them here, so I added a link to their formal definitions. Let me know if you have a better idea of how to describe them.

**CalcExpression** ::= `calc(`¹ CalcArgument ')'
**ClampExpression** ::= `clamp(`¹ CalcArgument ',' CalcArgument ',' CalcArgument ')'
**CalcArgument**² ::= InterpolatedDeclarationValue | CalcSum
**CalcSum** ::= CalcProduct (('+' | '-')³ CalcProduct)?
Copy link
Contributor

@stof stof Jan 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the right hand side actually be CalcSum to support 1px + 1% + 1px ? the current syntax would require braces to achieve that as 1px + (1% + 1px). (making the CalcProduct a single CalcValue containing a CalcArgument enclosed in braces).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that this is wrong, but I actually intended to make the second clause repeated rather than using recursion to repeat them (that's the structure that's assumed in the Semantics section below).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then the simplification algorithm needs to be updated, as it was describing only operations with 2 terms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, CalculationOperation only has left and right so the necessary change is explaining how this repeated clause gets turned into the struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it works as written, the CalcSum and CalcProduct semantics sections assemble the lists into a sequence of CalcOperations with two operands each.

proposal/first-class-calc.md Outdated Show resolved Hide resolved
> numbers of any units, and numbers across different known types (length +
> time) are also invalid.

> TODO: Should we try to simplify `calc(1px + 1% + 1px)`?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require a more complex structure for the AST, because the 1px + 1% + 1px would be represented as CalcOperation(1px, '+', CalcOperation(1%, '+', 1px)) currently while simplifying such case would require recombining the sum to try another variant.

@nex3 nex3 requested a review from Awjin January 9, 2021 01:02
Copy link
Contributor

@Awjin Awjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nex3 nex3 merged commit 5e3feee into master Jan 9, 2021
@nex3 nex3 deleted the first-class-calc branch January 9, 2021 01:35
mirisuzanne pushed a commit that referenced this pull request Feb 10, 2022
See #818

Co-authored-by: Jennifer Thakar <jathak@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants