Skip to content

Commit 20dc6ab

Browse files
anmonteirochenglou
authored andcommitted
Recover from parsing two postfix ops (#2228)
* WIP: Recover from parsing two postfix ops fixes #126 * Tests and refactor
1 parent 4d20e5b commit 20dc6ab

File tree

5 files changed

+80
-0
lines changed

5 files changed

+80
-0
lines changed

formatTest/unit_tests/expected_output/infix.re

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,3 +1311,10 @@ let not = x => !x;
13111311
let other = x => not(x);
13121312

13131313
let derefInsideArray = [|a^|];
1314+
1315+
/* https://github.com/facebook/reason/issues/126 */
1316+
foo^ ^;
1317+
1318+
let x = foo^ ^;
1319+
1320+
foo ^^ bar;

formatTest/unit_tests/input/infix.re

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,3 +1004,10 @@ let not = (x) => !x;
10041004
let other = (x) => not(x);
10051005

10061006
let derefInsideArray = [|a^|];
1007+
1008+
/* https://github.com/facebook/reason/issues/126 */
1009+
foo^^;
1010+
1011+
let x = foo^^;
1012+
1013+
foo^^bar;

src/reason-parser/reason_syntax_util.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,4 +518,9 @@ let location_contains loc1 loc2 =
518518
loc1.loc_start.Lexing.pos_cnum <= loc2.loc_start.Lexing.pos_cnum &&
519519
loc1.loc_end.Lexing.pos_cnum >= loc2.loc_end.Lexing.pos_cnum
520520

521+
let explode_str str =
522+
let rec loop acc i =
523+
if i < 0 then acc else loop (str.[i] :: acc) (i - 1)
524+
in
525+
loop [] (String.length str - 1)
521526
(* #end *)

src/reason-parser/reason_syntax_util.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,5 @@ val location_is_before : Ast_404.Location.t -> Ast_404.Location.t -> bool
108108

109109
val location_contains : Ast_404.Location.t -> Ast_404.Location.t -> bool
110110

111+
val explode_str : string -> char list
111112
(* #end *)

src/reason-parser/reason_toolchain.ml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,17 @@ module Reason_syntax = struct
607607
let offer_normalize checkpoint triple =
608608
normalize_checkpoint (I.offer checkpoint triple)
609609

610+
let offer_normalize_many checkpoint triples =
611+
let rec loop acc xs = match xs with
612+
| [] -> Some acc
613+
| triple :: xs ->
614+
begin match normalize_checkpoint (I.offer acc triple) with
615+
| I.InputNeeded _ as checkpoint' -> loop checkpoint' xs
616+
| _ -> None
617+
end
618+
in
619+
loop checkpoint triples
620+
610621
(* Insert a semicolon before submitting a token to the parser *)
611622
let try_inserting_semi_on = function
612623
| Reason_parser.LET
@@ -662,6 +673,21 @@ module Reason_syntax = struct
662673
Some (offer_normalize checkpoint' (token, pos, pos))
663674
| _ -> None
664675

676+
let try_inserting_postfix checkpoint infix ((_, pos, _) as triple) =
677+
(* we know that the infix was exclusively composed of '^' *)
678+
let rec mk_postfixes acc i =
679+
if i < 0 then
680+
acc
681+
else
682+
let triple = (Reason_parser.POSTFIXOP "^", pos, pos) in
683+
mk_postfixes (triple :: acc) (i - 1)
684+
in
685+
let infixes = mk_postfixes [] (String.length infix - 1) in
686+
match offer_normalize_many checkpoint infixes with
687+
| Some (I.InputNeeded _ as checkpoint') ->
688+
Some (offer_normalize checkpoint' triple)
689+
| _ -> None
690+
665691
(* Offer and insert a semicolon in case of failure *)
666692
let offer_normalize checkpoint triple =
667693
match offer_normalize checkpoint triple with
@@ -784,6 +810,40 @@ module Reason_syntax = struct
784810
in
785811
handle_inputs_needed supplier (List.map process_checkpoint checkpoints)
786812

813+
(*
814+
* This catches the `foo^^` case (which the parser thinks is an error – infix
815+
* without 2nd operand) and inserts as many postfix ops as there are carets
816+
* in the operator. We catch the token here and don't actually fork checkpoints
817+
* because catching this in `handle_other` would be too late – we would only
818+
* see the token where the error was (e.g. semicolon).
819+
*)
820+
| Reason_parser.INFIXOP1 op, _, _ as triple
821+
when List.for_all (fun x -> x == '^') (Reason_syntax_util.explode_str op) ->
822+
let rec process_checkpoints inputs_needed checkpoints =
823+
match checkpoints with
824+
| [] -> handle_inputs_needed supplier inputs_needed
825+
| (invalid_docstrings, checkpoint) :: tl ->
826+
begin match offer_normalize checkpoint triple with
827+
| I.InputNeeded _ as checkpoint' ->
828+
let next_triple = read supplier in
829+
begin match offer_normalize checkpoint' next_triple with
830+
| I.HandlingError _ ->
831+
begin match try_inserting_postfix checkpoint op next_triple with
832+
| Some (I.Accepted _ as cp) -> handle_other supplier cp
833+
| Some cp ->
834+
process_checkpoints ((invalid_docstrings, cp) :: inputs_needed) tl
835+
| None ->
836+
process_checkpoints ((invalid_docstrings, checkpoint') :: inputs_needed) tl
837+
end
838+
| checkpoint'' ->
839+
process_checkpoints ((invalid_docstrings, checkpoint'') :: inputs_needed) tl
840+
end
841+
| checkpoint' ->
842+
process_checkpoints ((invalid_docstrings, checkpoint') :: inputs_needed) tl
843+
end
844+
in
845+
process_checkpoints [] checkpoints
846+
787847
| triple ->
788848
begin match checkpoints with
789849
| [] -> assert false

0 commit comments

Comments
 (0)