Skip to content

Commit

Permalink
Change operator precedence to be left biased.
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderSpies committed Mar 6, 2023
1 parent d744542 commit 559681e
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 46 deletions.
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

0 comments on commit 559681e

Please sign in to comment.