Skip to content

Commit 559681e

Browse files
committed
Change operator precedence to be left biased.
1 parent d744542 commit 559681e

File tree

6 files changed

+45
-46
lines changed

6 files changed

+45
-46
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 3.9 (unreleased)
22

33
- Fix missing patterns around contraint pattern (a pattern with a type annotation).
4+
- Make &, &&, |, ||, ++, and :: operators left associative.
45

56
## 3.8.2
67

formatTest/unit_tests/expected_output/infix.re

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,29 +87,27 @@ let minParens =
8787
let formatted =
8888
a1 := a2 := b1 === (b2 === y !== x !== z);
8989

90-
/* &...(left) is higher than &(right). &(right) is equal to &&(right) */
9190
let parseTree =
92-
a1 && a2 && b1 & b2 & y &|| x &|| z;
91+
b1 & b2 & y &|| x &|| z && a2 && a1;
9392

9493
let minParens =
95-
a1 && a2 && b1 & b2 & y &|| x &|| z;
94+
b1 & b2 & y &|| x &|| z && a2 && a1;
9695

9796
let formatted =
98-
a1 && a2 && b1 & b2 & y &|| x &|| z;
97+
b1 & b2 & y &|| x &|| z && a2 && a1;
9998

10099
/**
101100
* Now, let's try an example that resembles the above, yet would require
102101
* parenthesis everywhere.
103102
*/
104-
/* &...(left) is higher than &(right). &(right) is equal to &&(right) */
105103
let parseTree =
106-
((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
104+
x &|| z &|| (y & (b2 & (b1 && (a1 && a2))));
107105

108106
let minParens =
109-
((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
107+
x &|| z &|| (y & (b2 & (b1 && (a1 && a2))));
110108

111109
let formatted =
112-
((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
110+
x &|| z &|| (y & (b2 & (b1 && (a1 && a2))));
113111

114112
/* **...(right) is higher than *...(left) */
115113
let parseTree = b1 *| b2 *| (y **| (x **| z));
@@ -149,13 +147,13 @@ first + second + third;
149147
first & second & third;
150148

151149
/* This one *shouldn't* */
152-
(first & second) & third;
150+
first & (second & third);
153151

154152
/* || is basically the same as &/&& */
155153
first || second || third;
156154

157155
/* This one *shouldn't* */
158-
(first || second) || third;
156+
first || (second || third);
159157

160158
/* No parens should be added/removed from the following when formatting */
161159
let seeWhichCharacterHasHigherPrecedence =
@@ -328,7 +326,7 @@ let shouldRemoveParens = ident ++ ident ++ ident;
328326
let shouldPreserveParens =
329327
ident + (ident + ident);
330328
let shouldPreserveParens =
331-
(ident ++ ident) ++ ident;
329+
ident ++ (ident ++ ident);
332330
/**
333331
* Since ++ is now INFIXOP1, it should have lower priority than INFIXOP2 (which
334332
* includes the single plus sign). That means no parens are required in the
@@ -365,11 +363,11 @@ let parensRequired = ident + (ident +++ ident);
365363
let parensRequired = ident + (ident ++- ident);
366364
let parensRequired = ident +$ (ident ++- ident);
367365

368-
/* ++ and +++ have the same parsing precedence, so it's right associative.
369-
* Parens are required if you want to group to the left, even when the tokens
366+
/* ++ and +++ have the same parsing precedence, so it's left associative.
367+
* Parens are required if you want to group to the right, even when the tokens
370368
* are different.*/
371-
let parensRequired = (ident ++ ident) +++ ident;
372-
let parensRequired = (ident +++ ident) ++ ident;
369+
let parensRequired = ident ++ (ident +++ ident);
370+
let parensRequired = ident +++ (ident ++ ident);
373371

374372
/* Add tests with IF/then mixed with infix/constructor application on left and right sides */
375373
/**

formatTest/unit_tests/input/infix.re

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,21 @@ let minParens = a1 := a2 := b1 === ((b2 === y) !== x !== z);
6969

7070
let formatted = a1 := a2 := b1 === ((b2 === y) !== x !== z);
7171

72-
/* &...(left) is higher than &(right). &(right) is equal to &&(right) */
73-
let parseTree = a1 && (a2 && (b1 & b2 & y &|| x &|| z));
72+
let parseTree = ((b1 & b2 & y &|| x &|| z) && a2) && a1;
7473

75-
let minParens = a1 && a2 && (b1 & b2 & y &|| x &|| z);
74+
let minParens = (b1 & b2 & y &|| x &|| z) && a2 && a1;
7675

77-
let formatted = a1 && a2 && (b1 & b2 & y &|| x &|| z);
76+
let formatted = (b1 & b2 & y &|| x &|| z) && a2 && a1;
7877

7978
/**
8079
* Now, let's try an example that resembles the above, yet would require
8180
* parenthesis everywhere.
8281
*/
83-
/* &...(left) is higher than &(right). &(right) is equal to &&(right) */
84-
let parseTree = ((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
82+
let parseTree = (x &|| z) &|| (y & (b2 & (b1 && ((a1 && a2)))));
8583

86-
let minParens = ((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
84+
let minParens = (x &|| z) &|| (y & (b2 & (b1 && ((a1 && a2)))));
8785

88-
let formatted = ((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
86+
let formatted = (x &|| z) &|| (y & (b2 & (b1 && ((a1 && a2)))));
8987

9088
/* **...(right) is higher than *...(left) */
9189
let parseTree = ((b1 *| b2) *| (y *\*| (x *\*| z)));
@@ -124,13 +122,13 @@ first + second + third;
124122
first & second & third;
125123

126124
/* This one *shouldn't* */
127-
(first & second) & third;
125+
first & (second & third);
128126

129127
/* || is basically the same as &/&& */
130128
first || second || third;
131129

132130
/* This one *shouldn't* */
133-
(first || second) || third;
131+
first || (second || third);
134132

135133
/* No parens should be added/removed from the following when formatting */
136134
let seeWhichCharacterHasHigherPrecedence = (first |> second |> third) ^> fourth;
@@ -267,9 +265,9 @@ let shouldSimplifyAnythingExceptApplicationAndConstruction = call("hi") ++ (swit
267265
| _ => "hi"
268266
}) ++ "yo";
269267
let shouldRemoveParens = (ident + ident) + ident;
270-
let shouldRemoveParens = ident ++ (ident ++ ident);
268+
let shouldRemoveParens = (ident ++ ident) ++ ident;
271269
let shouldPreserveParens = ident + (ident + ident);
272-
let shouldPreserveParens = (ident ++ ident) ++ ident;
270+
let shouldPreserveParens = ident ++ (ident ++ ident);
273271
/**
274272
* Since ++ is now INFIXOP1, it should have lower priority than INFIXOP2 (which
275273
* includes the single plus sign). That means no parens are required in the
@@ -304,11 +302,11 @@ let parensRequired = ident + (ident +++ ident);
304302
let parensRequired = ident + (ident ++- ident);
305303
let parensRequired = ident +$ (ident ++- ident);
306304

307-
/* ++ and +++ have the same parsing precedence, so it's right associative.
308-
* Parens are required if you want to group to the left, even when the tokens
305+
/* ++ and +++ have the same parsing precedence, so it's left associative.
306+
* Parens are required if you want to group to the right, even when the tokens
309307
* are different.*/
310-
let parensRequired = (ident ++ ident) +++ ident;
311-
let parensRequired = (ident +++ ident) ++ ident;
308+
let parensRequired = ident ++ (ident +++ ident);
309+
let parensRequired = ident +++ (ident ++ ident);
312310

313311
/* Add tests with IF/then mixed with infix/constructor application on left and right sides */
314312
/**

src/reason-parser/reason_declarative_lexer.mll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,8 @@ rule token state = parse
606606
| "^" -> POSTFIXOP "^"
607607
| op -> INFIXOP1 (unescape_operator op)
608608
}
609-
| "++" operator_chars*
610-
{ INFIXOP1 (lexeme_operator lexbuf) }
609+
| "++"
610+
{ PLUSPLUS }
611611
| '\\'? ['+' '-'] operator_chars*
612612
{ INFIXOP2 (lexeme_operator lexbuf) }
613613
(* SLASHGREATER is an INFIXOP3 that is handled specially *)

src/reason-parser/reason_parser.mly

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,7 @@ let add_brace_attr expr =
11741174
(* %token PARSER *)
11751175
%token PERCENT
11761176
%token PLUS
1177+
%token PLUSPLUS
11771178
%token PLUSDOT
11781179
%token PLUSEQ
11791180
%token <string> PREFIXOP [@recover.expr ""] [@recover.cost 2]
@@ -1244,13 +1245,13 @@ conflicts.
12441245
%nonassoc below_BAR (* Allows "building up" of many bars *)
12451246
%left BAR (* pattern (p|p|p) *)
12461247

1247-
%right OR BARBAR (* expr (e || e || e) *)
1248-
%right AMPERSAND AMPERAMPER (* expr (e && e && e) *)
1248+
%left OR BARBAR (* expr (e || e || e) *)
1249+
%left AMPERSAND AMPERAMPER (* expr (e && e && e) *)
12491250
%left INFIXOP0 LESS GREATER GREATERDOTDOTDOT (* expr (e OP e OP e) *)
12501251
%left LESSDOTDOTGREATER (* expr (e OP e OP e) *)
12511252
%right INFIXOP1 (* expr (e OP e OP e) *)
1252-
%right COLONCOLON (* expr (e :: e :: e) *)
1253-
%left INFIXOP2 PLUS PLUSDOT MINUS MINUSDOT PLUSEQ (* expr (e OP e OP e) *)
1253+
%left COLONCOLON (* expr (e :: e :: e) *)
1254+
%left INFIXOP2 PLUS PLUSDOT PLUSPLUS MINUS MINUSDOT PLUSEQ (* expr (e OP e OP e) *)
12541255
%left PERCENT INFIXOP3 SLASHGREATER STAR (* expr (e OP e OP e) *)
12551256
%right INFIXOP4 (* expr (e OP e OP e) *)
12561257

@@ -4681,6 +4682,7 @@ val_ident:
46814682
(* SLASHGREATER is INFIXOP3 but we needed to call it out specially *)
46824683
| SLASHGREATER { "/>" }
46834684
| INFIXOP4 { $1 }
4685+
| PLUSPLUS { "++" }
46844686
| PLUS { "+" }
46854687
| PLUSDOT { "+." }
46864688
| MINUS { "-" }

src/reason-parser/reason_pprint_ast.ml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ let rec sequentialIfBlocks x =
406406

407407
(*
408408
Table 2.1. Precedence and associativity.
409-
Precedence from highest to lowest: From RWOC, modified to include !=
409+
Precedence from highest to lowest: From RWOC, modified to include !=, and modified to make &, &&, or and || left associative.
410410
---------------------------------------
411411
412412
Operator prefix Associativity
@@ -424,8 +424,8 @@ let rec sequentialIfBlocks x =
424424
=..., <..., >..., |..., &..., $... Left associative (INFIXOP0)
425425
=, <, > Left associative (IN SAME row as INFIXOP0 listed after)
426426
---
427-
&, && Right associative
428-
or, || Right associative
427+
&, && Left associative
428+
or, || Left associative
429429
, -
430430
:=, = Right associative
431431
if -
@@ -590,12 +590,12 @@ let rules = [
590590
(TokenPrecedence, (fun s -> (Left, s = "!" )));
591591
];
592592
[
593-
(TokenPrecedence, (fun s -> (Right, s = "::")));
593+
(TokenPrecedence, (fun s -> (Left, s = "::")));
594594
];
595595
[
596596
(TokenPrecedence, (fun s -> (Right, s.[0] == '@')));
597597
(TokenPrecedence, (fun s -> (Right, s.[0] == '^')));
598-
(TokenPrecedence, (fun s -> (Right, String.length s > 1 && s.[0] == '+' && s.[1] == '+')));
598+
(TokenPrecedence, (fun s -> (Left, String.length s > 1 && s.[0] == '+' && s.[1] == '+')));
599599
];
600600
[
601601
(TokenPrecedence, (fun s -> (Left, s.[0] == '=' && not (s = "=") && not (s = "=>"))));
@@ -615,12 +615,12 @@ let rules = [
615615
(CustomPrecedence, (fun s -> (Left, s = funToken)));
616616
];
617617
[
618-
(TokenPrecedence, (fun s -> (Right, s = "&")));
619-
(TokenPrecedence, (fun s -> (Right, s = "&&")));
618+
(TokenPrecedence, (fun s -> (Left, s = "&")));
619+
(TokenPrecedence, (fun s -> (Left, s = "&&")));
620620
];
621621
[
622-
(TokenPrecedence, (fun s -> (Right, s = "or")));
623-
(TokenPrecedence, (fun s -> (Right, s = "||")));
622+
(TokenPrecedence, (fun s -> (Left, s = "or")));
623+
(TokenPrecedence, (fun s -> (Left, s = "||")));
624624
];
625625
[
626626
(* The Left shouldn't ever matter in practice. Should never get in a

0 commit comments

Comments
 (0)