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

Keyword swap changes #1539

Merged
merged 2 commits into from
Oct 24, 2017
Merged
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
6 changes: 3 additions & 3 deletions formatTest/typeCheckedTests/expected_output/mlSyntax.re
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Original file line number Diff line number Diff line change
Expand Up @@ -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";
122 changes: 79 additions & 43 deletions src/syntax_util.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doing quite a few checks. Shouldn't it be calculated once then have the result pattern-matched on, in a nested match?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of checks there. What do you mean by calculated once?

| 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
Expand Down Expand Up @@ -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
*)
Expand Down