Skip to content

Commit 9b034ea

Browse files
bloodyowlcknitt
authored andcommitted
Handle dict literal syntax
Closes #6545
1 parent d89b3c8 commit 9b034ea

File tree

9 files changed

+207
-8
lines changed

9 files changed

+207
-8
lines changed

jscomp/syntax/src/res_core.ml

Lines changed: 45 additions & 1 deletion
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,32 @@ 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 exprs =
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.prev_end_pos in
3930+
let to_key_value_pair record_item =
3931+
match record_item with
3932+
| {Location.txt = Longident.Lident key}, value_expr ->
3933+
Some
3934+
(Ast_helper.Exp.tuple ~loc
3935+
[
3936+
Ast_helper.Exp.constant ~loc (Pconst_string (key, None)); value_expr;
3937+
])
3938+
| _ -> None
3939+
in
3940+
let key_value_pairs = List.filter_map to_key_value_pair exprs in
3941+
Parser.expect Rbrace p;
3942+
Ast_helper.Exp.apply ~loc
3943+
(Ast_helper.Exp.ident ~loc
3944+
(Location.mkloc
3945+
(Longident.Ldot
3946+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray"))
3947+
loc))
3948+
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc key_value_pairs)]
3949+
39063950
and parse_array_exp p =
39073951
let start_pos = p.Parser.start_pos in
39083952
Parser.expect Lbracket p;

jscomp/syntax/src/res_grammar.ml

Lines changed: 7 additions & 0 deletions
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
@@ -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
@@ -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_printer.ml

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,16 @@ let is_valid_numeric_polyvar_number (x : string) =
429429
| _ -> false)
430430
else a >= 48
431431

432+
let is_tuple_array (expr : Parsetree.expression) =
433+
let is_plain_tuple (expr : Parsetree.expression) =
434+
match expr with
435+
| {pexp_desc = Pexp_tuple _} -> true
436+
| _ -> false
437+
in
438+
match expr with
439+
| {pexp_desc = Pexp_array items} -> List.for_all is_plain_tuple items
440+
| _ -> false
441+
432442
(* Exotic identifiers in poly-vars have a "lighter" syntax: #"ease-in" *)
433443
let print_poly_var_ident txt =
434444
(* numeric poly-vars don't need quotes: #644 *)
@@ -1406,6 +1416,45 @@ and print_record_declaration ~state (lds : Parsetree.label_declaration list)
14061416
Doc.rbrace;
14071417
])
14081418

1419+
and print_literal_dict_expr ~state (e : Parsetree.expression) cmt_tbl =
1420+
let force_break =
1421+
e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum
1422+
in
1423+
let tuple_to_row (e : Parsetree.expression) =
1424+
match e with
1425+
| {
1426+
pexp_desc =
1427+
Pexp_tuple
1428+
[
1429+
{pexp_desc = Pexp_constant (Pconst_string (name, _)); pexp_loc}; value;
1430+
];
1431+
} ->
1432+
Some (Location.mkloc (Longident.Lident name) pexp_loc, value)
1433+
| _ -> None
1434+
in
1435+
let rows =
1436+
match e with
1437+
| {pexp_desc = Pexp_array expressions} ->
1438+
List.filter_map tuple_to_row expressions
1439+
| _ -> []
1440+
in
1441+
Doc.breakable_group ~force_break
1442+
(Doc.concat
1443+
[
1444+
Doc.indent
1445+
(Doc.concat
1446+
[
1447+
Doc.soft_line;
1448+
Doc.join
1449+
~sep:(Doc.concat [Doc.text ","; Doc.line])
1450+
(List.map
1451+
(fun row -> print_bs_object_row ~state row cmt_tbl)
1452+
rows);
1453+
]);
1454+
Doc.trailing_comma;
1455+
Doc.soft_line;
1456+
])
1457+
14091458
and print_constructor_declarations ~state ~private_flag
14101459
(cds : Parsetree.constructor_declaration list) cmt_tbl =
14111460
let force_break =
@@ -4031,6 +4080,24 @@ and print_pexp_apply ~state expr cmt_tbl =
40314080
| [] -> doc
40324081
| attrs ->
40334082
Doc.group (Doc.concat [print_attributes ~state attrs cmt_tbl; doc]))
4083+
| Pexp_apply
4084+
( {
4085+
pexp_desc =
4086+
Pexp_ident
4087+
{
4088+
txt =
4089+
Longident.Ldot
4090+
(Longident.Ldot (Lident "Js", "Dict"), "fromArray");
4091+
};
4092+
},
4093+
[(Nolabel, key_values)] )
4094+
when is_tuple_array key_values ->
4095+
Doc.concat
4096+
[
4097+
Doc.text "dict{";
4098+
print_literal_dict_expr ~state key_values cmt_tbl;
4099+
Doc.rbrace;
4100+
]
40344101
| Pexp_apply
40354102
( {pexp_desc = Pexp_ident {txt = Longident.Ldot (Lident "Array", "get")}},
40364103
[(Nolabel, parent_expr); (Nolabel, member_expr)] )
@@ -4541,7 +4608,7 @@ and print_jsx_name {txt = lident} =
45414608
Doc.join ~sep:Doc.dot segments
45424609

45434610
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.
4611+
(* Because the same subtree gets printed twice, we need to copy the cmt_tbl.
45454612
* consumed comments need to be marked not-consumed and reprinted…
45464613
* Cheng's different comment algorithm will solve this. *)
45474614
let state = State.next_custom_layout state in
@@ -4624,7 +4691,7 @@ and print_arguments_with_callback_in_first_position ~state args cmt_tbl =
46244691
Doc.custom_layout [Lazy.force fits_on_one_line; Lazy.force break_all_args]
46254692

46264693
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.
4694+
(* Because the same subtree gets printed twice, we need to copy the cmt_tbl.
46284695
* consumed comments need to be marked not-consumed and reprinted…
46294696
* Cheng's different comment algorithm will solve this. *)
46304697
let state = state |> State.next_custom_layout in
@@ -5822,7 +5889,7 @@ let print_pattern p = print_pattern ~state:(State.init ()) p
58225889
let print_implementation ~width (s : Parsetree.structure) ~comments =
58235890
let cmt_tbl = CommentTable.make () in
58245891
CommentTable.walk_structure s cmt_tbl comments;
5825-
(* CommentTable.log cmtTbl; *)
5892+
(* CommentTable.log cmt_tbl; *)
58265893
let doc = print_structure ~state:(State.init ()) s cmt_tbl in
58275894
(* Doc.debug doc; *)
58285895
Doc.to_string ~width doc ^ "\n"

jscomp/syntax/src/res_scanner.ml

Lines changed: 8 additions & 3 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 11 additions & 0 deletions
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}
Lines changed: 7 additions & 0 deletions
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)|]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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}
12+
13+
// multiline
14+
let x = dict{
15+
"foo": "bar",
16+
"bar": "baz",
17+
"baz": baz
18+
}
19+
20+
let x = Js.Dict.fromArray([("foo", "bar"), ("bar", "baz")])
21+
let x = Js.Dict.fromArray([("foo", "bar"), ("bar", "baz"), ("baz", baz)])
22+
23+
let x = Js.Dict.fromArray([
24+
("foo", "bar"),
25+
("bar", "baz"),
26+
("baz", baz)
27+
])
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
// punning
11+
let baz = "foo"
12+
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}
13+
14+
// multiline
15+
let x = dict{
16+
"foo": "bar",
17+
"bar": "baz",
18+
"baz": baz,
19+
}
20+
21+
let x = dict{"foo": "bar", "bar": "baz"}
22+
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}
23+
24+
let x = dict{
25+
"foo": "bar",
26+
"bar": "baz",
27+
"baz": baz,
28+
}

0 commit comments

Comments
 (0)