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

feat: add raw identifier syntax #2796

Open
wants to merge 5 commits 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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[#2800](https://github.com/reasonml/reason/pull/2800))
- Fix: don't print all extension strings as quoted extensions (@anmonteiro,
[#2809](https://github.com/reasonml/reason/pull/2809))
- Add support for raw identifier syntax (@anmonteiro,
[#2796](https://github.com/reasonml/reason/pull/2796))

## 3.13.0

Expand Down
5 changes: 4 additions & 1 deletion src/reason-parser/reason_declarative_lexer.mll
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ let hex_float_literal =
(['p' 'P'] ['+' '-']? ['0'-'9'] ['0'-'9' '_']* )?

let literal_modifier = ['G'-'Z' 'g'-'z']
let raw_ident_escape = "\\#"

rule token state = parse
| "\\" newline {
Expand All @@ -408,6 +409,8 @@ rule token state = parse
{ QUESTION }
| "=?"
{ set_lexeme_length lexbuf 1; EQUAL }
| raw_ident_escape (lowercase identchar * as name)
{ LIDENT name }
| lowercase identchar *
{ let s = Lexing.lexeme lexbuf in
try Hashtbl.find keyword_table s
Expand Down Expand Up @@ -521,7 +524,7 @@ rule token state = parse
{ CHAR (char_for_decimal_code lexbuf 2) }
| "'\\" 'x' ['0'-'9' 'a'-'f' 'A'-'F'] ['0'-'9' 'a'-'f' 'A'-'F'] "'"
{ CHAR (char_for_hexadecimal_code lexbuf 3) }
| "'" (("\\" _) as esc)
| "'" (("\\" [^ '#']) as esc)
{ raise_error (Location.curr lexbuf) (Illegal_escape esc);
token state lexbuf
}
Expand Down
76 changes: 54 additions & 22 deletions src/reason-parser/reason_pprint_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -718,11 +718,15 @@ type construct =
| `normal
| `simple of Longident.t
| `tuple
| `btrue
| `bfalse
]

let view_expr x =
match x.pexp_desc with
| Pexp_construct ({ txt = Lident "()" }, _) -> `tuple
| Pexp_construct ({ txt = Lident "true"; _ }, _) -> `btrue
| Pexp_construct ({ txt = Lident "false"; _ }, _) -> `bfalse
| Pexp_construct ({ txt = Lident "[]" }, _) -> `nil
| Pexp_construct ({ txt = Lident "::" }, Some _) ->
let rec loop exp acc =
Expand All @@ -745,7 +749,7 @@ let is_simple_list_expr x =
match view_expr x with `list _ | `cons _ -> true | _ -> false

let is_simple_construct : construct -> bool = function
| `nil | `tuple | `list _ | `simple _ | `cons _ -> true
| `nil | `tuple | `list _ | `simple _ | `btrue | `bfalse | `cons _ -> true
| `normal -> false

let uncurriedTable = Hashtbl.create 42
Expand Down Expand Up @@ -2062,14 +2066,22 @@ let createFormatter () =
settings.funcApplicationLabelStyle
typeApplicationItems

let add_raw_identifier_prefix txt =
let prefix =
match Hashtbl.find Reason_declarative_lexer.keyword_table txt with
| _ -> "\\#"
| exception Not_found -> ""
in
prefix ^ txt

(* add parentheses to binders when they are in fact infix or prefix
operators *)
let protectIdentifier txt =
let needs_parens = needs_parens txt in
let txt =
if Reason_syntax_util.is_andop txt || Reason_syntax_util.is_letop txt
then Reason_syntax_util.compress_letop_identifier txt
else txt
else txt |> add_raw_identifier_prefix
in
if not needs_parens
then atom txt
Expand Down Expand Up @@ -2926,7 +2938,9 @@ let createFormatter () =
let itm =
self#formatOneTypeDef
prepend
(atom ~loc:td.ptype_name.loc td.ptype_name.txt)
(atom
~loc:td.ptype_name.loc
(add_raw_identifier_prefix td.ptype_name.txt))
(atom eq_symbol)
td
in
Expand Down Expand Up @@ -3099,9 +3113,8 @@ let createFormatter () =
let recordRow pld =
let hasPunning = recordRowIsPunned pld in
let name =
if hasPunning
then [ atom pld.pld_name.txt ]
else [ atom pld.pld_name.txt; atom ":" ]
let name = add_raw_identifier_prefix pld.pld_name.txt in
if hasPunning then [ atom name ] else [ atom name; atom ":" ]
in
let name = source_map ~loc:pld.pld_name.loc (makeList name) in
let withMutable =
Expand Down Expand Up @@ -3412,6 +3425,9 @@ let createFormatter () =
let variant_helper i rf =
match rf.prf_desc with
| Rtag (label, opt_ampersand, ctl) ->
let label =
{ label with txt = add_raw_identifier_prefix label.txt }
in
let pcd_args = Pcstr_tuple ctl in
let all_attrs =
List.concat [ pcd_attributes; rf.prf_attributes ]
Expand Down Expand Up @@ -3599,6 +3615,9 @@ let createFormatter () =
~arityIsClear:true)
| Ppat_lazy p ->
label (atom "lazy") (formatPrecedence (self#simple_pattern p))
| Ppat_construct
({ txt = Lident (("true" | "false") as txt); _ }, None) ->
atom txt
| Ppat_construct (({ txt } as li), po) when not (txt = Lident "::")
->
(* FIXME The third field always false *)
Expand Down Expand Up @@ -3747,8 +3766,9 @@ let createFormatter () =
else
let itm =
match x.ppat_desc with
| Ppat_construct ({ loc; txt = Lident (("()" | "[]") as x) }, _)
->
| Ppat_construct
( { loc; txt = Lident (("()" | "[]" | "true" | "false") as x) }
, _ ) ->
(* Patterns' locations might include a leading bar depending on the
* context it was parsed in. Therefore, we need to include further
* information about the contents of the pattern such as tokens etc,
Expand Down Expand Up @@ -5007,7 +5027,7 @@ let createFormatter () =
~polyVariant:true
~arityIsClear:true
stdAttrs
(atom ("`" ^ l))
(atom ("`" ^ add_raw_identifier_prefix l))
eo
]
(* TODO: Should protect this identifier *)
Expand Down Expand Up @@ -7657,6 +7677,8 @@ let createFormatter () =
(match view_expr x with
| `nil -> atom "[]"
| `tuple -> atom "()"
| `btrue -> atom "true"
| `bfalse -> atom "false"
| `list xs ->
(* LIST EXPRESSION *)
self#unparseSequence ~construct:`List xs
Expand Down Expand Up @@ -7712,7 +7734,9 @@ let createFormatter () =
(self#core_type ct)
])
| Pexp_variant (l, None) ->
Some (ensureSingleTokenSticksToLabel (atom ("`" ^ l)))
Some
(ensureSingleTokenSticksToLabel
(atom ("`" ^ add_raw_identifier_prefix l)))
| Pexp_record (l, eo) -> Some (self#unparseRecord l eo)
| Pexp_array l -> Some (self#unparseSequence ~construct:`Array l)
| Pexp_let _ | Pexp_sequence _ | Pexp_letmodule _
Expand Down Expand Up @@ -8233,12 +8257,13 @@ let createFormatter () =
in

let upToName =
let name = add_raw_identifier_prefix pci_name.txt in
if ls == []
then label ~space:true (atom opener) (atom pci_name.txt)
then label ~space:true (atom opener) (atom name)
else
label
~space:true
(label ~space:true (atom opener) (atom pci_name.txt))
(label ~space:true (atom opener) (atom name))
(self#class_params_def ls)
in
let includingEqual =
Expand Down Expand Up @@ -8807,14 +8832,17 @@ let createFormatter () =
()

method modtype x ~delim =
let name = atom x.pmtd_name.txt in
let letPattern =
makeList ~postSpace:true [ atom "module type"; name; atom delim ]
in
let name = atom (add_raw_identifier_prefix x.pmtd_name.txt) in
let main =
match x.pmtd_type with
| None -> makeList ~postSpace:true [ atom "module type"; name ]
| Some mt -> self#module_type letPattern mt
| Some mt ->
let letPattern =
makeList
~postSpace:true
[ atom "module type"; name; atom delim ]
in
self#module_type letPattern mt
in
let { Reason_attributes.stdAttrs; docAttrs } =
Reason_attributes.partitionAttributes
Expand Down Expand Up @@ -9381,6 +9409,7 @@ let createFormatter () =
, None )

method class_opening class_keyword name pci_virt ls =
let name = add_raw_identifier_prefix name in
let firstToken = if class_keyword then "class" else "and" in
match pci_virt, ls with
(* When no class params, it's a very simple formatting for the
Expand Down Expand Up @@ -9436,14 +9465,17 @@ let createFormatter () =
self#attach_std_item_attrs binding.pmb_attributes module_binding
| Pstr_open od -> self#pstr_open od
| Pstr_modtype x ->
let name = atom x.pmtd_name.txt in
let letPattern =
makeList ~postSpace:true [ atom "module type"; name; atom "=" ]
in
let name = atom (add_raw_identifier_prefix x.pmtd_name.txt) in
let main =
match x.pmtd_type with
| None -> makeList ~postSpace:true [ atom "module type"; name ]
| Some mt -> self#module_type letPattern mt
| Some mt ->
let letPattern =
makeList
~postSpace:true
[ atom "module type"; name; atom "=" ]
in
self#module_type letPattern mt
in
self#attach_std_item_attrs x.pmtd_attributes main
| Pstr_class l -> self#class_declaration_list l
Expand Down
43 changes: 43 additions & 0 deletions test/raw-identifiers.t/input.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

let \#let = 2;
let \#let = \#let
and \#and = \#let;

/* labeled arguments */
let \#let = (~\#let) => {
\#let;
};

let \#let = (~\#let: \#let = \#let) => {
\#let;
};

/* Types */
type \#type = \#type;

module type \#module = \#module;

class \#class = \#class;

class type \#class = \#class;

type x = [ | `\#module ]
type y = [ | \#module ]
let x = `\#module

external \#external: unit => unit = "external";

type \#rec = {
\#type: \#type,
\#module: module_
};

let \#rec = {
\#type: \#type,
\#module: module_
}

let true = x => x;

let \#true = x => x;

49 changes: 49 additions & 0 deletions test/raw-identifiers.t/run.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Test raw identifiers in Reason syntax

$ refmt ./input.re | tee input2.re
let \#let = 2;
let \#let = \#let
and \#and = \#let;

/* labeled arguments */
let \#let = (~\#let) => {
\#let;
};

let \#let = (~\#let: \#let=\#let) => {
\#let;
};

/* Types */
type \#type = \#type;

module type \#module = \#module;

class \#class = class \#class;

class type \#class = \#class;

type x = [ | `\#module];
type y = [ \#module];
let x = `\#module;

external \#external: unit => unit = "external";

type \#rec = {
\#type,
\#module: module_,
};

let \#rec = {
\#type,
\#module: module_,
};

let true = x => x;

let \#true = x => x;

Check idempotency

$ refmt ./input2.re > out.re

Loading