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

Change operator precedence to be left biased. #2685

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 3.9 (unreleased)

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

## 3.8.2

Expand Down
28 changes: 13 additions & 15 deletions formatTest/unit_tests/expected_output/infix.re
Original file line number Diff line number Diff line change
Expand Up @@ -87,29 +87,27 @@ let minParens =
let formatted =
a1 := a2 := b1 === (b2 === y !== x !== z);

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

let minParens =
a1 && a2 && b1 & b2 & y &|| x &|| z;
b1 & b2 & y &|| x &|| z && a2 && a1;

let formatted =
a1 && a2 && b1 & b2 & y &|| x &|| z;
b1 & b2 & y &|| x &|| z && a2 && a1;

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

let minParens =
((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
x &|| z &|| (y & (b2 & (b1 && (a1 && a2))));

let formatted =
((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
x &|| z &|| (y & (b2 & (b1 && (a1 && a2))));

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

/* This one *shouldn't* */
(first & second) & third;
first & (second & third);

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

/* This one *shouldn't* */
(first || second) || third;
first || (second || third);

/* No parens should be added/removed from the following when formatting */
let seeWhichCharacterHasHigherPrecedence =
Expand Down Expand Up @@ -328,7 +326,7 @@ let shouldRemoveParens = ident ++ ident ++ ident;
let shouldPreserveParens =
ident + (ident + ident);
let shouldPreserveParens =
(ident ++ ident) ++ ident;
ident ++ (ident ++ ident);
/**
* Since ++ is now INFIXOP1, it should have lower priority than INFIXOP2 (which
* includes the single plus sign). That means no parens are required in the
Expand Down Expand Up @@ -365,11 +363,11 @@ let parensRequired = ident + (ident +++ ident);
let parensRequired = ident + (ident ++- ident);
let parensRequired = ident +$ (ident ++- ident);

/* ++ and +++ have the same parsing precedence, so it's right associative.
* Parens are required if you want to group to the left, even when the tokens
/* ++ and +++ have the same parsing precedence, so it's left associative.
* Parens are required if you want to group to the right, even when the tokens
* are different.*/
let parensRequired = (ident ++ ident) +++ ident;
let parensRequired = (ident +++ ident) ++ ident;
let parensRequired = ident ++ (ident +++ ident);
let parensRequired = ident +++ (ident ++ ident);

/* Add tests with IF/then mixed with infix/constructor application on left and right sides */
/**
Expand Down
30 changes: 14 additions & 16 deletions formatTest/unit_tests/input/infix.re
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,21 @@ let minParens = a1 := a2 := b1 === ((b2 === y) !== x !== z);

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

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

let minParens = a1 && a2 && (b1 & b2 & y &|| x &|| z);
let minParens = (b1 & b2 & y &|| x &|| z) && a2 && a1;

let formatted = a1 && a2 && (b1 & b2 & y &|| x &|| z);
let formatted = (b1 & b2 & y &|| x &|| z) && a2 && a1;

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

let minParens = ((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
let minParens = (x &|| z) &|| (y & (b2 & (b1 && ((a1 && a2)))));

let formatted = ((((a1 && a2) && b1) & b2) & y) &|| (x &|| z);
let formatted = (x &|| z) &|| (y & (b2 & (b1 && ((a1 && a2)))));

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

/* This one *shouldn't* */
(first & second) & third;
first & (second & third);

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

/* This one *shouldn't* */
(first || second) || third;
first || (second || third);

/* No parens should be added/removed from the following when formatting */
let seeWhichCharacterHasHigherPrecedence = (first |> second |> third) ^> fourth;
Expand Down Expand Up @@ -267,9 +265,9 @@ let shouldSimplifyAnythingExceptApplicationAndConstruction = call("hi") ++ (swit
| _ => "hi"
}) ++ "yo";
let shouldRemoveParens = (ident + ident) + ident;
let shouldRemoveParens = ident ++ (ident ++ ident);
let shouldRemoveParens = (ident ++ ident) ++ ident;
let shouldPreserveParens = ident + (ident + ident);
let shouldPreserveParens = (ident ++ ident) ++ ident;
let shouldPreserveParens = ident ++ (ident ++ ident);
/**
* Since ++ is now INFIXOP1, it should have lower priority than INFIXOP2 (which
* includes the single plus sign). That means no parens are required in the
Expand Down Expand Up @@ -304,11 +302,11 @@ let parensRequired = ident + (ident +++ ident);
let parensRequired = ident + (ident ++- ident);
let parensRequired = ident +$ (ident ++- ident);

/* ++ and +++ have the same parsing precedence, so it's right associative.
* Parens are required if you want to group to the left, even when the tokens
/* ++ and +++ have the same parsing precedence, so it's left associative.
* Parens are required if you want to group to the right, even when the tokens
* are different.*/
let parensRequired = (ident ++ ident) +++ ident;
let parensRequired = (ident +++ ident) ++ ident;
let parensRequired = ident ++ (ident +++ ident);
let parensRequired = ident +++ (ident ++ ident);

/* Add tests with IF/then mixed with infix/constructor application on left and right sides */
/**
Expand Down
4 changes: 2 additions & 2 deletions src/reason-parser/reason_declarative_lexer.mll
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,8 @@ rule token state = parse
| "^" -> POSTFIXOP "^"
| op -> INFIXOP1 (unescape_operator op)
}
| "++" operator_chars*
{ INFIXOP1 (lexeme_operator lexbuf) }
| "++"
{ PLUSPLUS }
| '\\'? ['+' '-'] operator_chars*
{ INFIXOP2 (lexeme_operator lexbuf) }
(* SLASHGREATER is an INFIXOP3 that is handled specially *)
Expand Down
10 changes: 6 additions & 4 deletions src/reason-parser/reason_parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,7 @@ let add_brace_attr expr =
(* %token PARSER *)
%token PERCENT
%token PLUS
%token PLUSPLUS
%token PLUSDOT
%token PLUSEQ
%token <string> PREFIXOP [@recover.expr ""] [@recover.cost 2]
Expand Down Expand Up @@ -1244,13 +1245,13 @@ conflicts.
%nonassoc below_BAR (* Allows "building up" of many bars *)
%left BAR (* pattern (p|p|p) *)

%right OR BARBAR (* expr (e || e || e) *)
%right AMPERSAND AMPERAMPER (* expr (e && e && e) *)
%left OR BARBAR (* expr (e || e || e) *)
%left AMPERSAND AMPERAMPER (* expr (e && e && e) *)
%left INFIXOP0 LESS GREATER GREATERDOTDOTDOT (* expr (e OP e OP e) *)
%left LESSDOTDOTGREATER (* expr (e OP e OP e) *)
%right INFIXOP1 (* expr (e OP e OP e) *)
%right COLONCOLON (* expr (e :: e :: e) *)
%left INFIXOP2 PLUS PLUSDOT MINUS MINUSDOT PLUSEQ (* expr (e OP e OP e) *)
%left COLONCOLON (* expr (e :: e :: e) *)
%left INFIXOP2 PLUS PLUSDOT PLUSPLUS MINUS MINUSDOT PLUSEQ (* expr (e OP e OP e) *)
%left PERCENT INFIXOP3 SLASHGREATER STAR (* expr (e OP e OP e) *)
%right INFIXOP4 (* expr (e OP e OP e) *)

Expand Down Expand Up @@ -4681,6 +4682,7 @@ val_ident:
(* SLASHGREATER is INFIXOP3 but we needed to call it out specially *)
| SLASHGREATER { "/>" }
| INFIXOP4 { $1 }
| PLUSPLUS { "++" }
| PLUS { "+" }
| PLUSDOT { "+." }
| MINUS { "-" }
Expand Down
18 changes: 9 additions & 9 deletions src/reason-parser/reason_pprint_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ let rec sequentialIfBlocks x =

(*
Table 2.1. Precedence and associativity.
Precedence from highest to lowest: From RWOC, modified to include !=
Precedence from highest to lowest: From RWOC, modified to include !=, and modified to make &, &&, or and || left associative.
---------------------------------------

Operator prefix Associativity
Expand All @@ -424,8 +424,8 @@ let rec sequentialIfBlocks x =
=..., <..., >..., |..., &..., $... Left associative (INFIXOP0)
=, <, > Left associative (IN SAME row as INFIXOP0 listed after)
---
&, && Right associative
or, || Right associative
&, && Left associative
or, || Left associative
, -
:=, = Right associative
if -
Expand Down Expand Up @@ -590,12 +590,12 @@ let rules = [
(TokenPrecedence, (fun s -> (Left, s = "!" )));
];
[
(TokenPrecedence, (fun s -> (Right, s = "::")));
(TokenPrecedence, (fun s -> (Left, s = "::")));
];
[
(TokenPrecedence, (fun s -> (Right, s.[0] == '@')));
(TokenPrecedence, (fun s -> (Right, s.[0] == '^')));
(TokenPrecedence, (fun s -> (Right, String.length s > 1 && s.[0] == '+' && s.[1] == '+')));
(TokenPrecedence, (fun s -> (Left, String.length s > 1 && s.[0] == '+' && s.[1] == '+')));
];
[
(TokenPrecedence, (fun s -> (Left, s.[0] == '=' && not (s = "=") && not (s = "=>"))));
Expand All @@ -615,12 +615,12 @@ let rules = [
(CustomPrecedence, (fun s -> (Left, s = funToken)));
];
[
(TokenPrecedence, (fun s -> (Right, s = "&")));
(TokenPrecedence, (fun s -> (Right, s = "&&")));
(TokenPrecedence, (fun s -> (Left, s = "&")));
(TokenPrecedence, (fun s -> (Left, s = "&&")));
];
[
(TokenPrecedence, (fun s -> (Right, s = "or")));
(TokenPrecedence, (fun s -> (Right, s = "||")));
(TokenPrecedence, (fun s -> (Left, s = "or")));
(TokenPrecedence, (fun s -> (Left, s = "||")));
];
[
(* The Left shouldn't ever matter in practice. Should never get in a
Expand Down