From 7789f4d18774b5e747578b5e3ddc64689390cfd3 Mon Sep 17 00:00:00 2001 From: "Hezekiah M. Carty" Date: Sat, 21 Oct 2017 23:45:04 -0600 Subject: [PATCH 1/2] Use suffixes to avoid potential keyword conflicts Related to #1361 --- src/syntax_util.ml | 122 +++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/src/syntax_util.ml b/src/syntax_util.ml index 3191444f7..268dbbb0e 100644 --- a/src/syntax_util.ml +++ b/src/syntax_util.ml @@ -137,34 +137,88 @@ let syntax_error_extension_node loc message = in (str, payload) -(** What does this do? Here's an example: Reason code uses `!` for logical not, - while ocaml uses `not`. So, for converting between reason and ocaml syntax, - ocaml `not` converts to `!`, reason `!` converts to `not`. This table is - used later on to do the actual swapping. - - The drawback of this technique is that if you're using an identifier such as - `pub` in the ocaml file Foo.ml (say, let pub = 5), for a piece of Reason code - to interop with it, said code needs to write `Foo.method` (because of the - method <-> pub keyword swap). This is pretty dumb... +(** Check to see if the string `s` is made up of `keyword` and zero or more + trailing `_` characters. *) +let potentially_conflicts_with ~keyword s = + let s_length = String.length s in + let keyword_length = String.length keyword in + (* It can't be a match if s is shorter than keyword *) + s_length >= keyword_length && ( + try + (* Ensure s starts with keyword... *) + for i = 0 to keyword_length - 1 do + if keyword.[i] <> s.[i] then raise Exit; + done; + (* ...and contains nothing else except trailing _ characters *) + for i = keyword_length to s_length - 1 do + if s.[i] <> '_' then raise Exit; + done; + (* If we've made it this far there's a potential conflict *) + true + with + | Exit -> false + ) + +(** Add/remove an appropriate suffix when mangling potential keywords *) +let string_add_suffix x = x ^ "_" +let string_drop_suffix x = String.sub x 0 (String.length x - 1) + +(** What do these *_swap functions do? Here's an example: Reason code uses `!` + for logical not, while ocaml uses `not`. So, for converting between reason + and ocaml syntax, ocaml `not` converts to `!`, reason `!` converts to + `not`. + + In more complicated cases where a keyword exists in one syntax but not the + other these functions translate any potentially conflicting identifier into + the same identifier with a suffix attached, or remove the suffix when + converting back. *) -let reason_to_ml_swapping_alist = [ - "!" , "not"; - "^" , "!"; - "++" , "^"; - "===" , "=="; - "==" , "="; +let reason_to_ml_swap = function + | "!" -> "not" + | "^" -> "!" + | "++" -> "^" + | "===" -> "==" + | "==" -> "=" (* ===\/ and !==\/ are not representable in OCaml but * representable in Reason *) - "\\!==" , "!=="; - "\\===" , "==="; - "!=" , "<>"; - "!==" , "!="; - "match" , "switch"; - "method" , "pub"; - "private" , "pri"; -] + | "\\!==" -> "!==" + | "\\===" -> "===" + | "!=" -> "<>" + | "!==" -> "!=" + | x when ( + potentially_conflicts_with ~keyword:"match" x + || potentially_conflicts_with ~keyword:"method" x + || potentially_conflicts_with ~keyword:"private" x) -> string_add_suffix x + | x when ( + potentially_conflicts_with ~keyword:"switch_" x + || potentially_conflicts_with ~keyword:"pub_" x + || potentially_conflicts_with ~keyword:"pri_" x) -> string_drop_suffix x + | everything_else -> everything_else + +let ml_to_reason_swap = function + | "not" -> "!" + | "!" -> "^" + | "^" -> "++" + | "==" -> "===" + | "=" -> "==" + (* ===\/ and !==\/ are not representable in OCaml but + * representable in Reason + *) + | "!==" -> "\\!==" + | "===" -> "\\===" + | "<>" -> "!=" + | "!=" -> "!==" + | x when ( + potentially_conflicts_with ~keyword:"match_" x + || potentially_conflicts_with ~keyword:"method_" x + || potentially_conflicts_with ~keyword:"private_" x) -> string_drop_suffix x + | x when ( + potentially_conflicts_with ~keyword:"switch" x + || potentially_conflicts_with ~keyword:"pub" x + || potentially_conflicts_with ~keyword:"pri" x) -> string_add_suffix x + | everything_else -> everything_else let swap_txt map txt = if StringMap.mem txt map then @@ -230,31 +284,13 @@ let escape_stars_slashes_mapper = in identifier_mapper escape_stars_slashes -(** - * swap_operator_mapper is a mapper that swaps two operators at parse/print time. - * We need this since we want to transform operator such as "=" in Ocaml to "==" in Reason. - * In this case, in the parser, everytime we see a token "==" in Reason, we transform it into "="; - * Similarly, in the printer, everytime we see a token "=", we transform it into "=="; - *) -let swap_operator_mapper map = identifier_mapper (swap_txt map) - -let reason_to_ml_swap_map = List.fold_left - (fun map (op1, op2) -> (StringMap.add op1 op2 map)) - StringMap.empty - reason_to_ml_swapping_alist - -let ml_to_reason_swap_map = List.fold_left - (fun map (op1, op2) -> (StringMap.add op2 op1 map)) - StringMap.empty - reason_to_ml_swapping_alist - (* To be used in parser, transform a token into an ast node with different identifier *) -let reason_to_ml_swap_operator_mapper = swap_operator_mapper reason_to_ml_swap_map +let reason_to_ml_swap_operator_mapper = identifier_mapper reason_to_ml_swap (* To be used in printer, transform an ast node into a token with different identifier *) -let ml_to_reason_swap_operator_mapper = swap_operator_mapper ml_to_reason_swap_map +let ml_to_reason_swap_operator_mapper = identifier_mapper ml_to_reason_swap (* attribute_equals tests an attribute is txt *) From a5ae6515d1fc8a2c9536442bc43a7b5e4cad41d4 Mon Sep 17 00:00:00 2001 From: "Hezekiah M. Carty" Date: Sun, 22 Oct 2017 22:20:57 -0600 Subject: [PATCH 2/2] Update format tests --- formatTest/typeCheckedTests/expected_output/mlSyntax.re | 6 +++--- .../typeCheckedTests/expected_output/mlSyntax.re.4.02.3 | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/formatTest/typeCheckedTests/expected_output/mlSyntax.re b/formatTest/typeCheckedTests/expected_output/mlSyntax.re index 9cd101a2e..68c98e7a2 100644 --- a/formatTest/typeCheckedTests/expected_output/mlSyntax.re +++ b/formatTest/typeCheckedTests/expected_output/mlSyntax.re @@ -100,8 +100,8 @@ module EM = { exception Ealias = EM.E; -let match = "match"; +let switch_ = "match"; -let method = "method"; +let pub_ = "method"; -let private = "private"; +let pri_ = "private"; diff --git a/formatTest/typeCheckedTests/expected_output/mlSyntax.re.4.02.3 b/formatTest/typeCheckedTests/expected_output/mlSyntax.re.4.02.3 index b2653ccb5..f7e37aae7 100644 --- a/formatTest/typeCheckedTests/expected_output/mlSyntax.re.4.02.3 +++ b/formatTest/typeCheckedTests/expected_output/mlSyntax.re.4.02.3 @@ -100,8 +100,8 @@ module EM = { exception Ealias = EM.E; -let match = "match"; +let switch_ = "match"; -let method = "method"; +let pub_ = "method"; -let private = "private"; +let pri_ = "private";