Skip to content

Commit 6838749

Browse files
authored
Add dict literal syntax (rescript-lang#6774)
* Handle dict literal syntax Closes rescript-lang#6545 * Update test * Move isTupleArray to res_parsetree_viewer * Map locations correctly * Fix location in parsing comments printing * Make Dict limit to List grammar-wise * Add changelog entry
1 parent d89b3c8 commit 6838749

15 files changed

+425
-11
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#### :rocket: New Feature
1616

1717
- Allow coercing polyvariants to variants when we can guarantee that the runtime representation matches. https://github.com/rescript-lang/rescript-compiler/pull/6981
18+
- Add new dict literal syntax (`dict{"foo": "bar"}`). https://github.com/rescript-lang/rescript-compiler/pull/6774
1819

1920
#### :nail_care: Polish
2021

jscomp/syntax/src/res_comments_table.ml

+13
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,19 @@ and walk_expression expr t comments =
13451345
walk_list
13461346
[Expression parent_expr; Expression member_expr; Expression target_expr]
13471347
t comments
1348+
| Pexp_apply
1349+
( {
1350+
pexp_desc =
1351+
Pexp_ident
1352+
{
1353+
txt =
1354+
Longident.Ldot
1355+
(Longident.Ldot (Lident "Js", "Dict"), "fromArray");
1356+
};
1357+
},
1358+
[(Nolabel, key_values)] )
1359+
when Res_parsetree_viewer.is_tuple_array key_values ->
1360+
walk_list [Expression key_values] t comments
13481361
| Pexp_apply (call_expr, arguments) ->
13491362
let before, inside, after = partition_by_loc comments call_expr.pexp_loc in
13501363
let after =

jscomp/syntax/src/res_core.ml

+49-1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ let get_closing_token = function
222222
| Lbrace -> Rbrace
223223
| Lbracket -> Rbracket
224224
| List -> Rbrace
225+
| Dict -> Rbrace
225226
| LessThan -> GreaterThan
226227
| _ -> assert false
227228

@@ -233,7 +234,7 @@ let rec go_to_closing closing_token state =
233234
| GreaterThan, GreaterThan ->
234235
Parser.next state;
235236
()
236-
| ((Token.Lbracket | Lparen | Lbrace | List | LessThan) as t), _ ->
237+
| ((Token.Lbracket | Lparen | Lbrace | List | Dict | LessThan) as t), _ ->
237238
Parser.next state;
238239
go_to_closing (get_closing_token t) state;
239240
go_to_closing closing_token state
@@ -1896,6 +1897,9 @@ and parse_atomic_expr p =
18961897
| List ->
18971898
Parser.next p;
18981899
parse_list_expr ~start_pos p
1900+
| Dict ->
1901+
Parser.next p;
1902+
parse_dict_expr ~start_pos p
18991903
| Module ->
19001904
Parser.next p;
19011905
parse_first_class_module_expr ~start_pos p
@@ -3122,6 +3126,20 @@ and parse_record_expr_row p =
31223126
| _ -> None)
31233127
| _ -> None
31243128

3129+
and parse_dict_expr_row p =
3130+
match p.Parser.token with
3131+
| String s -> (
3132+
let loc = mk_loc p.start_pos p.end_pos in
3133+
Parser.next p;
3134+
let field = Location.mkloc (Longident.Lident s) loc in
3135+
match p.Parser.token with
3136+
| Colon ->
3137+
Parser.next p;
3138+
let fieldExpr = parse_expr p in
3139+
Some (field, fieldExpr)
3140+
| _ -> Some (field, Ast_helper.Exp.ident ~loc:field.loc field))
3141+
| _ -> None
3142+
31253143
and parse_record_expr_with_string_keys ~start_pos first_row p =
31263144
let rows =
31273145
first_row
@@ -3903,6 +3921,36 @@ and parse_list_expr ~start_pos p =
39033921
loc))
39043922
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc list_exprs)]
39053923

3924+
and parse_dict_expr ~start_pos p =
3925+
let rows =
3926+
parse_comma_delimited_region ~grammar:Grammar.DictRows ~closing:Rbrace
3927+
~f:parse_dict_expr_row p
3928+
in
3929+
let loc = mk_loc start_pos p.end_pos in
3930+
let to_key_value_pair
3931+
(record_item : Longident.t Location.loc * Parsetree.expression) =
3932+
match record_item with
3933+
| ( {Location.txt = Longident.Lident key; loc = keyLoc},
3934+
({pexp_loc = value_loc} as value_expr) ) ->
3935+
Some
3936+
(Ast_helper.Exp.tuple
3937+
~loc:(mk_loc keyLoc.loc_start value_loc.loc_end)
3938+
[
3939+
Ast_helper.Exp.constant ~loc:keyLoc (Pconst_string (key, None));
3940+
value_expr;
3941+
])
3942+
| _ -> None
3943+
in
3944+
let key_value_pairs = List.filter_map to_key_value_pair rows in
3945+
Parser.expect Rbrace p;
3946+
Ast_helper.Exp.apply ~loc
3947+
(Ast_helper.Exp.ident ~loc
3948+
(Location.mkloc
3949+
(Longident.Ldot
3950+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray"))
3951+
loc))
3952+
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc key_value_pairs)]
3953+
39063954
and parse_array_exp p =
39073955
let start_pos = p.Parser.start_pos in
39083956
Parser.expect Lbracket p;

jscomp/syntax/src/res_grammar.ml

+10-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type t =
5959
| Pattern
6060
| AttributePayload
6161
| TagNames
62+
| DictRows
6263

6364
let to_string = function
6465
| OpenDescription -> "an open description"
@@ -120,6 +121,7 @@ let to_string = function
120121
| ExprFor -> "a for expression"
121122
| AttributePayload -> "an attribute payload"
122123
| TagNames -> "tag names"
124+
| DictRows -> "rows of a dict"
123125

124126
let is_signature_item_start = function
125127
| Token.At | Let | Typ | External | Exception | Open | Include | Module | AtAt
@@ -136,7 +138,7 @@ let is_atomic_pattern_start = function
136138
let is_atomic_expr_start = function
137139
| Token.True | False | Int _ | String _ | Float _ | Codepoint _ | Backtick
138140
| Uident _ | Lident _ | Hash | Lparen | List | Lbracket | Lbrace | LessThan
139-
| Module | Percent | Forwardslash | ForwardslashDot ->
141+
| Module | Percent | Forwardslash | ForwardslashDot | Dict ->
140142
true
141143
| _ -> false
142144

@@ -151,7 +153,7 @@ let is_expr_start = function
151153
| For | Hash | If | Int _ | Lbrace | Lbracket | LessThan | Lident _ | List
152154
| Lparen | Minus | MinusDot | Module | Percent | Plus | PlusDot | String _
153155
| Switch | True | Try | Uident _ | Underscore (* _ => doThings() *)
154-
| While | Forwardslash | ForwardslashDot ->
156+
| While | Forwardslash | ForwardslashDot | Dict ->
155157
true
156158
| _ -> false
157159

@@ -219,6 +221,10 @@ let is_mod_expr_start = function
219221
true
220222
| _ -> false
221223

224+
let is_dict_row_start = function
225+
| Token.String _ -> true
226+
| _ -> false
227+
222228
let is_record_row_start = function
223229
| Token.DotDotDot -> true
224230
| Token.Uident _ | Lident _ -> true
@@ -260,7 +266,7 @@ let is_block_expr_start = function
260266
| False | Float _ | For | Forwardslash | ForwardslashDot | Hash | If | Int _
261267
| Lbrace | Lbracket | LessThan | Let | Lident _ | List | Lparen | Minus
262268
| MinusDot | Module | Open | Percent | Plus | PlusDot | String _ | Switch
263-
| True | Try | Uident _ | Underscore | While ->
269+
| True | Try | Uident _ | Underscore | While | Dict ->
264270
true
265271
| _ -> false
266272

@@ -278,6 +284,7 @@ let is_list_element grammar token =
278284
| FunctorArgs -> is_functor_arg_start token
279285
| ModExprList -> is_mod_expr_start token
280286
| TypeParameters -> is_type_parameter_start token
287+
| DictRows -> is_dict_row_start token
281288
| RecordRows -> is_record_row_start token
282289
| RecordRowsStringKey -> is_record_row_string_key_start token
283290
| ArgumentList -> is_argument_start token

jscomp/syntax/src/res_parsetree_viewer.ml

+10
Original file line numberDiff line numberDiff line change
@@ -743,3 +743,13 @@ let is_rewritten_underscore_apply_sugar expr =
743743
match expr.pexp_desc with
744744
| Pexp_ident {txt = Longident.Lident "_"} -> true
745745
| _ -> false
746+
747+
let is_tuple_array (expr : Parsetree.expression) =
748+
let is_plain_tuple (expr : Parsetree.expression) =
749+
match expr with
750+
| {pexp_desc = Pexp_tuple _} -> true
751+
| _ -> false
752+
in
753+
match expr with
754+
| {pexp_desc = Pexp_array items} -> List.for_all is_plain_tuple items
755+
| _ -> false

jscomp/syntax/src/res_parsetree_viewer.mli

+2
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,5 @@ val has_if_let_attribute : Parsetree.attributes -> bool
164164
val is_rewritten_underscore_apply_sugar : Parsetree.expression -> bool
165165

166166
val is_fun_newtype : Parsetree.expression -> bool
167+
168+
val is_tuple_array : Parsetree.expression -> bool

jscomp/syntax/src/res_printer.ml

+64-3
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,49 @@ and print_record_declaration ~state (lds : Parsetree.label_declaration list)
14061406
Doc.rbrace;
14071407
])
14081408

1409+
and print_literal_dict_expr ~state (e : Parsetree.expression) cmt_tbl =
1410+
let force_break =
1411+
e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum
1412+
in
1413+
let tuple_to_row (e : Parsetree.expression) =
1414+
match e with
1415+
| {
1416+
pexp_desc =
1417+
Pexp_tuple
1418+
[
1419+
{pexp_desc = Pexp_constant (Pconst_string (name, _)); pexp_loc}; value;
1420+
];
1421+
} ->
1422+
Some ((Location.mkloc (Longident.Lident name) pexp_loc, value), e)
1423+
| _ -> None
1424+
in
1425+
let rows =
1426+
match e with
1427+
| {pexp_desc = Pexp_array expressions} ->
1428+
List.filter_map tuple_to_row expressions
1429+
| _ -> []
1430+
in
1431+
Doc.breakable_group ~force_break
1432+
(Doc.concat
1433+
[
1434+
Doc.indent
1435+
(Doc.concat
1436+
[
1437+
Doc.soft_line;
1438+
Doc.join
1439+
~sep:(Doc.concat [Doc.text ","; Doc.line])
1440+
(List.map
1441+
(fun ((row, e) :
1442+
(Longident.t Location.loc * Parsetree.expression)
1443+
* Parsetree.expression) ->
1444+
let doc = print_bs_object_row ~state row cmt_tbl in
1445+
print_comments doc cmt_tbl e.pexp_loc)
1446+
rows);
1447+
]);
1448+
Doc.trailing_comma;
1449+
Doc.soft_line;
1450+
])
1451+
14091452
and print_constructor_declarations ~state ~private_flag
14101453
(cds : Parsetree.constructor_declaration list) cmt_tbl =
14111454
let force_break =
@@ -4031,6 +4074,24 @@ and print_pexp_apply ~state expr cmt_tbl =
40314074
| [] -> doc
40324075
| attrs ->
40334076
Doc.group (Doc.concat [print_attributes ~state attrs cmt_tbl; doc]))
4077+
| Pexp_apply
4078+
( {
4079+
pexp_desc =
4080+
Pexp_ident
4081+
{
4082+
txt =
4083+
Longident.Ldot
4084+
(Longident.Ldot (Lident "Js", "Dict"), "fromArray");
4085+
};
4086+
},
4087+
[(Nolabel, key_values)] )
4088+
when Res_parsetree_viewer.is_tuple_array key_values ->
4089+
Doc.concat
4090+
[
4091+
Doc.text "dict{";
4092+
print_literal_dict_expr ~state key_values cmt_tbl;
4093+
Doc.rbrace;
4094+
]
40344095
| Pexp_apply
40354096
( {pexp_desc = Pexp_ident {txt = Longident.Ldot (Lident "Array", "get")}},
40364097
[(Nolabel, parent_expr); (Nolabel, member_expr)] )
@@ -4541,7 +4602,7 @@ and print_jsx_name {txt = lident} =
45414602
Doc.join ~sep:Doc.dot segments
45424603

45434604
and print_arguments_with_callback_in_first_position ~state args cmt_tbl =
4544-
(* Because the same subtree gets printed twice, we need to copy the cmtTbl.
4605+
(* Because the same subtree gets printed twice, we need to copy the cmt_tbl.
45454606
* consumed comments need to be marked not-consumed and reprinted…
45464607
* Cheng's different comment algorithm will solve this. *)
45474608
let state = State.next_custom_layout state in
@@ -4624,7 +4685,7 @@ and print_arguments_with_callback_in_first_position ~state args cmt_tbl =
46244685
Doc.custom_layout [Lazy.force fits_on_one_line; Lazy.force break_all_args]
46254686

46264687
and print_arguments_with_callback_in_last_position ~state args cmt_tbl =
4627-
(* Because the same subtree gets printed twice, we need to copy the cmtTbl.
4688+
(* Because the same subtree gets printed twice, we need to copy the cmt_tbl.
46284689
* consumed comments need to be marked not-consumed and reprinted…
46294690
* Cheng's different comment algorithm will solve this. *)
46304691
let state = state |> State.next_custom_layout in
@@ -5822,7 +5883,7 @@ let print_pattern p = print_pattern ~state:(State.init ()) p
58225883
let print_implementation ~width (s : Parsetree.structure) ~comments =
58235884
let cmt_tbl = CommentTable.make () in
58245885
CommentTable.walk_structure s cmt_tbl comments;
5825-
(* CommentTable.log cmtTbl; *)
5886+
(* CommentTable.log cmt_tbl; *)
58265887
let doc = print_structure ~state:(State.init ()) s cmt_tbl in
58275888
(* Doc.debug doc; *)
58285889
Doc.to_string ~width doc ^ "\n"

jscomp/syntax/src/res_scanner.ml

+8-3
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,16 @@ let scan_identifier scanner =
196196
(String.sub [@doesNotRaise]) scanner.src start_off
197197
(scanner.offset - start_off)
198198
in
199-
if '{' == scanner.ch && str = "list" then (
199+
match (scanner, str) with
200+
| {ch = '{'}, "list" ->
200201
next scanner;
201202
(* TODO: this isn't great *)
202-
Token.lookup_keyword "list{")
203-
else Token.lookup_keyword str
203+
Token.lookup_keyword "list{"
204+
| {ch = '{'}, "dict" ->
205+
next scanner;
206+
(* TODO: this isn't great *)
207+
Token.lookup_keyword "dict{"
208+
| _ -> Token.lookup_keyword str
204209

205210
let scan_digits scanner ~base =
206211
if base <= 10 then

jscomp/syntax/src/res_token.ml

+4-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ type t =
8989
| PercentPercent
9090
| Comment of Comment.t
9191
| List
92+
| Dict
9293
| TemplateTail of string * Lexing.position
9394
| TemplatePart of string * Lexing.position
9495
| Backtick
@@ -200,6 +201,7 @@ let to_string = function
200201
| PercentPercent -> "%%"
201202
| Comment c -> "Comment" ^ Comment.to_string c
202203
| List -> "list{"
204+
| Dict -> "dict{"
203205
| TemplatePart (text, _) -> text ^ "${"
204206
| TemplateTail (text, _) -> "TemplateTail(" ^ text ^ ")"
205207
| Backtick -> "`"
@@ -224,6 +226,7 @@ let keyword_table = function
224226
| "include" -> Include
225227
| "let" -> Let
226228
| "list{" -> List
229+
| "dict{" -> Dict
227230
| "module" -> Module
228231
| "mutable" -> Mutable
229232
| "of" -> Of
@@ -242,7 +245,7 @@ let keyword_table = function
242245
let is_keyword = function
243246
| Await | And | As | Assert | Constraint | Else | Exception | External | False
244247
| For | If | In | Include | Land | Let | List | Lor | Module | Mutable | Of
245-
| Open | Private | Rec | Switch | True | Try | Typ | When | While ->
248+
| Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict ->
246249
true
247250
| _ -> false
248251

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// empty dict
2+
let x = dict{}
3+
4+
// one value
5+
let x = dict{"foo": "bar"}
6+
7+
// two values
8+
let x = dict{"foo": "bar", "bar": "baz"}
9+
10+
let baz = "foo"
11+
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let x = Js.Dict.fromArray [||]
2+
let x = Js.Dict.fromArray [|("foo", {js|bar|js})|]
3+
let x = Js.Dict.fromArray [|("foo", {js|bar|js});("bar", {js|baz|js})|]
4+
let baz = {js|foo|js}
5+
let x =
6+
Js.Dict.fromArray
7+
[|("foo", {js|bar|js});("bar", {js|baz|js});("baz", baz)|]

0 commit comments

Comments
 (0)