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

operator precedence enforcement #5074

Open
Tracked by #13002
ghost opened this issue Oct 3, 2017 · 14 comments
Open
Tracked by #13002

operator precedence enforcement #5074

ghost opened this issue Oct 3, 2017 · 14 comments
Labels
help wanted This issue is generally accepted and needs someone to pick it up kind:feature topic:compiler:parser topic:tools:formatter

Comments

@ghost
Copy link

ghost commented Oct 3, 2017

PREFACE: I have searched and not found an issue about considerations that may have already been made about this, if I missed the issue I'd close this one.
blogpost - reasoning behind this
in short: x = a & b + c * d && e ^ f == 7 is something nobody wants to read

And even for operators that are used together - like && and || -
it is difficult to give them a relative precedence while keeping it intuitive.
So instead of picking a side, we can just pick none:
Let them have the same precedence and make it an error to mix them without parentheses.

goal:

A carefully designed operator precedence enforces the common guideline of “use parentheses when it is not intuitive”. It is now impossible to write unclear expressions as the compiler will simply reject them.

a suggestion as to how to order the precedence is at the end of the post


from learning crystal so far I like strong typedefs and very reasonable auto-format which really helps the programmer and thus I think enforced clear operator precedence would fit in very well and keep code clean and safe

and it would definitely be better to post in a request for comment pool

@bew
Copy link
Contributor

bew commented Oct 3, 2017

In crystal, it parses as x = (a & (b + (c * d))) && ((e ^ f) == 7) so yeah I also think it's wrong!

@asterite
Copy link
Member

asterite commented Oct 4, 2017

Sure. Please write down the algorithm to implement this.

@ghost
Copy link
Author

ghost commented Oct 4, 2017

what algorithm do you mean exactly?
I am afraid this may be more than I am able to do at the moment.

@asterite
Copy link
Member

asterite commented Oct 4, 2017

I mean, you propose that the compiler enforces parentheses where it would be otherwise ambiguous for a user to understand it. For that to work, the compiler has to have a formal algorithm for this, or maybe a bunch of heuristics. We don't have time to develop this, and I personally think that if it's too confusing to read, just add parentheses, it's not that terrible. No other programming language that I know of enforces parentheses. So if you want to see this happen you'll have to write the code or the algorithm that implements this. But if you don't have time for this, I think we can close this.

@ghost
Copy link
Author

ghost commented Oct 4, 2017

this was the proposed ordering:

#The final operator precedence
If we just take the most common operators, I identify the following “categories” of operators:

Logical operators: &&, ||, !

Comparison operators: ==, !=, <, <=¸ …

Mathematical operators: binary/unary + and -, *, and /.

Bitwise operators: ~, &, |, ^, << and >>

Other unary operators like function call, array subscript or member access

It makes sense to assign them the following relative precedence:

unary operators > mathematical/bitwise operators > comparison operators > logical operators

I personally think that if it's too confusing to read, just add parentheses, it's not that terrible.

same goes for formatting code, if it is not formatted well, I just format it well
...the issue arises not only if oneself cannot read the expression but the main reason is when someone else is reading/ debugging/ altering the code that seemed to be perfectly valid/ obvious to someone else

but if there is nobody who can work this out in practice than you can probably close it, agreed

@asterite
Copy link
Member

asterite commented Oct 6, 2017

I'm pretty sure we are never going to implement this, so I'm closing this.

For example, is this confusing?

1 * 2 + 3 * 4

Maybe for some it is, if they don't know that * has a higher precedence. Do we force parentheses?

(1 * 2) + (3 * 4)

I don't know, the first one was probably easy to understand. The same probably applies to other operators. And in doubt, just use parentheses to make sure you get the correct association.

@asterite asterite closed this as completed Oct 6, 2017
@ghost
Copy link
Author

ghost commented Oct 6, 2017

like in the blogpost

Then the following expressions are all well-formed:

a * b + c == foo & a
a && (!b || c)
array[a] + 32 < ~a | b

would work, it is not about enforcing brackets on every operator but mainly between classes of operators

@asterite
Copy link
Member

asterite commented Oct 6, 2017

OK, I'll reopen this. Maybe some day someone will implement this...

@asterite asterite reopened this Oct 6, 2017
@ghost
Copy link
Author

ghost commented Oct 6, 2017

keep faith ;)
I'll try to come back with more details!

@ghost
Copy link
Author

ghost commented Dec 18, 2017

So I made some more examples, tried to come of with more explicit rules.
If you think this helps to specify a possible approach/ need different examples I can try to work out further information.

sorted by strength of binding: very strong to weak

1. member access and function calls

Bar.foo(3)
i = Bar.member
i = foobar[0]

2. Math und Binary ops

+ - * /         # these two rows (Math and Binary) must not mix without braces
~ ^ | & << >>   # these two rows (Math and Binary) must not mix without braces

3. comparison ops

== != <= <

4. logical ops

! `>` && ||

=

=   # is not a valid op, has to be put in braces

no mixed chaining rules

1. ^ | &    # bitwise
2. << >>    # bitshift
3. && ||    # logical ops

a ^ b & c	# all of those mixed chains of (1),(2),(3) are vorbidden
a << b >> c # all of those mixed chains of (1),(2),(3) are vorbidden
a && b || c # all of those mixed chains of (1),(2),(3) are vorbidden

a && b && c # allowed, no mixed chain

examples

!a && b         # all other combinations of !a ...xyz are vorbidden
!a || b         # all other combinations of !a ...xyz are vorbidden
a  && b || c    # vorbidden, chaining of || and &&
!a.foo()		# vorbidden, mixed ! with other than && or || without braces

~a & b          # allowed, like !a || b
~a + b          # vorbidden, mixed math and binary ops
~( a + b )      # allowed

a & b == c      <=> ( a & b ) == c

Mult* and Division/ bind stronger than Plus+ and Minus-
4 * 3 + 1 # works
4 * 3 ^ 1 # does not work, mixed binary and math


a = 1 + b = c       # does not work, assignment has no precedence as op
a = 1 + ( b = c )   # works

examples of all allowed combinations

locigal <-> comp

a && b < c      <=> a && ( b < c )

bin_math <-> comp

a + b == c      <=> ( a + b ) == c
a << b == c     <=> ( a << b ) == c

bin_math <-> logical

a + b && c      <=> ( a + b ) && c
a << b && c     <=> ( a << b ) && c

all the following work:

because 1. member access and function calls have highest precedence.

where Bar.foo() can be one of the following three:

Bar.foo(3)
Bar.member
foobar[0]
a + Bar.foo()
a == Bar.foo()
a || Bar.foo()
a || !Bar.foo()

@straight-shoota
Copy link
Member

straight-shoota commented Dec 18, 2017

Just a small note: A logical operator should have higher precedence than a comparator, making 1 && b < c equal a && (b < c). This fits with your ordering of having comparators between logical ops and math/binary ops.

@ghost
Copy link
Author

ghost commented Dec 18, 2017

thanks, I updated the notes accordingly

@RX14
Copy link
Contributor

RX14 commented Dec 18, 2017

I would support this if it was a PR. Not sure if we'll have time to implement this for a long time though.

@straight-shoota
Copy link
Member

I would see this as a feature for the formatter because it's just a formatting issue. The parser doesn't need parenthesis to disambiguate because the precedence rules are clear, so it doesn't need to enforce it. It only helps humans to follow the code better, so that's a job for the formatter. As long as it's not too intrusive, this should be fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted This issue is generally accepted and needs someone to pick it up kind:feature topic:compiler:parser topic:tools:formatter
Projects
None yet
Development

No branches or pull requests

4 participants