Skip to content

Commit 087fb9a

Browse files
committed
Add initial dict literal implementation
Remove comments Remove unused collect function Remove print
1 parent 7617b66 commit 087fb9a

File tree

7 files changed

+267
-11
lines changed

7 files changed

+267
-11
lines changed

jscomp/syntax/src/res_core.ml

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ module LoopProgress = struct
1414
| _ :: rest -> rest
1515
end
1616

17+
type ('a, 'b) spreadInline = Spread of 'a | Inline of 'b
18+
1719
let mkLoc startLoc endLoc =
1820
Location.{loc_start = startLoc; loc_end = endLoc; loc_ghost = false}
1921

@@ -184,6 +186,7 @@ let taggedTemplateLiteralAttr =
184186
(Location.mknoloc "res.taggedTemplate", Parsetree.PStr [])
185187

186188
let spreadAttr = (Location.mknoloc "res.spread", Parsetree.PStr [])
189+
let dictAttr = (Location.mknoloc "res.dict", Parsetree.PStr [])
187190

188191
type argument = {
189192
dotted: bool;
@@ -233,6 +236,7 @@ let getClosingToken = function
233236
| Lbrace -> Rbrace
234237
| Lbracket -> Rbracket
235238
| List -> Rbrace
239+
| Dict -> Rbrace
236240
| LessThan -> GreaterThan
237241
| _ -> assert false
238242

@@ -244,7 +248,7 @@ let rec goToClosing closingToken state =
244248
| GreaterThan, GreaterThan ->
245249
Parser.next state;
246250
()
247-
| ((Token.Lbracket | Lparen | Lbrace | List | LessThan) as t), _ ->
251+
| ((Token.Lbracket | Lparen | Lbrace | List | Dict | LessThan) as t), _ ->
248252
Parser.next state;
249253
goToClosing (getClosingToken t) state;
250254
goToClosing closingToken state
@@ -1921,6 +1925,9 @@ and parseAtomicExpr p =
19211925
| List ->
19221926
Parser.next p;
19231927
parseListExpr ~startPos p
1928+
| Dict ->
1929+
Parser.next p;
1930+
parseDictExpr ~startPos p
19241931
| Module ->
19251932
Parser.next p;
19261933
parseFirstClassModuleExpr ~startPos p
@@ -3876,6 +3883,18 @@ and parseSpreadExprRegionWithLoc p =
38763883
Some (false, parseConstrainedOrCoercedExpr p, startPos, p.prevEndPos)
38773884
| _ -> None
38783885

3886+
and parseSpreadRecordExprRowWithStringKeyRegionWithLoc p =
3887+
let startPos = p.Parser.prevEndPos in
3888+
match p.Parser.token with
3889+
| DotDotDot ->
3890+
Parser.next p;
3891+
let expr = parseConstrainedOrCoercedExpr p in
3892+
Some (Spread expr, startPos, p.prevEndPos)
3893+
| token when Grammar.isExprStart token ->
3894+
parseRecordExprRowWithStringKey p
3895+
|> Option.map (fun parsedRow -> (Inline parsedRow, startPos, p.prevEndPos))
3896+
| _ -> None
3897+
38793898
and parseListExpr ~startPos p =
38803899
let split_by_spread exprs =
38813900
List.fold_left
@@ -3920,6 +3939,100 @@ and parseListExpr ~startPos p =
39203939
loc))
39213940
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc listExprs)]
39223941

3942+
and parseDictExpr ~startPos p =
3943+
let makeDictRowTuples ~loc idExps =
3944+
idExps
3945+
|> List.map (fun ((id, exp) : Ast_helper.lid * Parsetree.expression) ->
3946+
Ast_helper.Exp.tuple
3947+
[
3948+
Ast_helper.Exp.constant ~loc:id.loc
3949+
(Pconst_string (Longident.last id.txt, None));
3950+
exp;
3951+
])
3952+
|> Ast_helper.Exp.array ~loc
3953+
in
3954+
3955+
let makeSpreadDictRowTuples ~loc spreadDict =
3956+
Ast_helper.Exp.apply ~loc
3957+
(Ast_helper.Exp.ident ~loc ~attrs:[dictAttr]
3958+
(Location.mkloc
3959+
(Longident.Ldot
3960+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "entries"))
3961+
loc))
3962+
[(Asttypes.Nolabel, spreadDict)]
3963+
in
3964+
3965+
let concatManyExpr ~loc listExprs =
3966+
Ast_helper.Exp.apply ~loc
3967+
(Ast_helper.Exp.ident ~loc ~attrs:[spreadAttr]
3968+
(Location.mkloc
3969+
(Longident.Ldot
3970+
(Longident.Ldot (Longident.Lident "Belt", "Array"), "concatMany"))
3971+
loc))
3972+
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc listExprs)]
3973+
in
3974+
3975+
let makeDictFromRowTuples ~loc arrayEntriesExp =
3976+
Ast_helper.Exp.apply ~loc
3977+
(Ast_helper.Exp.ident ~loc ~attrs:[dictAttr]
3978+
(Location.mkloc
3979+
(Longident.Ldot
3980+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray"))
3981+
loc))
3982+
[(Asttypes.Nolabel, arrayEntriesExp)]
3983+
in
3984+
let split_by_spread exprs =
3985+
List.fold_left
3986+
(fun acc curr ->
3987+
match (curr, acc) with
3988+
| (Spread expr, startPos, endPos), _ ->
3989+
(* find a spread expression, prepend a new sublist *)
3990+
([], Some expr, startPos, endPos) :: acc
3991+
| ( (Inline fieldExprTuple, startPos, _endPos),
3992+
(no_spreads, spread, _accStartPos, accEndPos) :: acc ) ->
3993+
(* find a non-spread expression, and the accumulated is not empty,
3994+
* prepend to the first sublist, and update the loc of the first sublist *)
3995+
(fieldExprTuple :: no_spreads, spread, startPos, accEndPos) :: acc
3996+
| (Inline fieldExprTuple, startPos, endPos), [] ->
3997+
(* find a non-spread expression, and the accumulated is empty *)
3998+
[([fieldExprTuple], None, startPos, endPos)])
3999+
[] exprs
4000+
in
4001+
let rec getListOfEntryArraysReversed ?(accum = []) ~loc spreadSplit =
4002+
match spreadSplit with
4003+
| [] -> accum
4004+
| (idExps, None, _, _) :: tail ->
4005+
let accum = (idExps |> makeDictRowTuples ~loc) :: accum in
4006+
tail |> getListOfEntryArraysReversed ~loc ~accum
4007+
| ([], Some spread, _, _) :: tail ->
4008+
let accum = (spread |> makeSpreadDictRowTuples ~loc) :: accum in
4009+
tail |> getListOfEntryArraysReversed ~loc ~accum
4010+
| (idExps, Some spread, _, _) :: tail ->
4011+
let accum =
4012+
(spread |> makeSpreadDictRowTuples ~loc)
4013+
:: (idExps |> makeDictRowTuples ~loc)
4014+
:: accum
4015+
in
4016+
tail |> getListOfEntryArraysReversed ~loc ~accum
4017+
in
4018+
4019+
let dictExprsRev =
4020+
parseCommaDelimitedReversedList ~grammar:Grammar.RecordRowsStringKey
4021+
~closing:Rbrace ~f:parseSpreadRecordExprRowWithStringKeyRegionWithLoc p
4022+
in
4023+
Parser.expect Rbrace p;
4024+
let loc = mkLoc startPos p.prevEndPos in
4025+
let arrDictEntries =
4026+
match
4027+
dictExprsRev |> split_by_spread |> getListOfEntryArraysReversed ~loc
4028+
with
4029+
| [] -> Ast_helper.Exp.array ~loc []
4030+
| [singleArrDictEntries] -> singleArrDictEntries
4031+
| multipleArrDictEntries ->
4032+
multipleArrDictEntries |> List.rev |> concatManyExpr ~loc
4033+
in
4034+
makeDictFromRowTuples ~loc arrDictEntries
4035+
39234036
(* Overparse ... and give a nice error message *)
39244037
and parseNonSpreadExp ~msg p =
39254038
let () =

jscomp/syntax/src/res_grammar.ml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type t =
5656
| TypeConstraint
5757
| AtomicTypExpr
5858
| ListExpr
59+
| DictExpr
5960
| Pattern
6061
| AttributePayload
6162
| TagNames
@@ -114,6 +115,7 @@ let toString = function
114115
| TypeConstraint -> "constraints on a type"
115116
| AtomicTypExpr -> "a type"
116117
| ListExpr -> "an ocaml list expr"
118+
| DictExpr -> "a dict literal expr"
117119
| PackageConstraint -> "a package constraint"
118120
| JsxChild -> "jsx child"
119121
| Pattern -> "pattern"
@@ -168,8 +170,8 @@ let isStructureItemStart = function
168170

169171
let isPatternStart = function
170172
| Token.Int _ | Float _ | String _ | Codepoint _ | Backtick | True | False
171-
| Minus | Plus | Lparen | Lbracket | Lbrace | List | Underscore | Lident _
172-
| Uident _ | Hash | Exception | Lazy | Percent | Module | At ->
173+
| Minus | Plus | Lparen | Lbracket | Lbrace | List | Dict | Underscore
174+
| Lident _ | Uident _ | Hash | Exception | Lazy | Percent | Module | At ->
173175
true
174176
| _ -> false
175177

@@ -267,7 +269,7 @@ let isBlockExprStart = function
267269
let isListElement grammar token =
268270
match grammar with
269271
| ExprList -> token = Token.DotDotDot || isExprStart token
270-
| ListExpr -> token = DotDotDot || isExprStart token
272+
| ListExpr | DictExpr -> token = DotDotDot || isExprStart token
271273
| PatternList -> token = DotDotDot || isPatternStart token
272274
| ParameterList -> isParameterStart token
273275
| StringFieldDeclarations -> isStringFieldDeclStart token
@@ -324,3 +326,7 @@ let isListTerminator grammar token =
324326

325327
let isPartOfList grammar token =
326328
isListElement grammar token || isListTerminator grammar token
329+
330+
let isDictElement = isListElement
331+
let isDictTerminator = isListTerminator
332+
let isPartOfDict = isPartOfList

jscomp/syntax/src/res_parsetree_viewer.ml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,37 @@ let isSpreadBeltListConcat expr =
678678
hasSpreadAttr expr.pexp_attributes
679679
| _ -> false
680680

681+
let isSpreadBeltArrayConcat expr =
682+
match expr.pexp_desc with
683+
| Pexp_ident
684+
{
685+
txt =
686+
Longident.Ldot
687+
(Longident.Ldot (Longident.Lident "Belt", "Array"), "concatMany");
688+
} ->
689+
hasSpreadAttr expr.pexp_attributes
690+
| _ -> false
691+
692+
let hasDictAttr attrs =
693+
List.exists
694+
(fun attr ->
695+
match attr with
696+
| {Location.txt = "res.dict"}, _ -> true
697+
| _ -> false)
698+
attrs
699+
700+
let isDictFromArray expr =
701+
match expr.pexp_desc with
702+
| Pexp_ident
703+
{
704+
txt =
705+
Longident.Ldot
706+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray");
707+
} ->
708+
let v = hasDictAttr expr.pexp_attributes in
709+
v
710+
| _ -> false
711+
681712
(* Blue | Red | Green -> [Blue; Red; Green] *)
682713
let collectOrPatternChain pat =
683714
let rec loop pattern chain =

jscomp/syntax/src/res_parsetree_viewer.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,11 @@ val isTemplateLiteral : Parsetree.expression -> bool
140140
val isTaggedTemplateLiteral : Parsetree.expression -> bool
141141
val hasTemplateLiteralAttr : Parsetree.attributes -> bool
142142

143+
val hasSpreadAttr : (string Location.loc * 'a) list -> bool
143144
val isSpreadBeltListConcat : Parsetree.expression -> bool
145+
val isSpreadBeltArrayConcat : Parsetree.expression -> bool
146+
val hasDictAttr : (string Location.loc * 'a) list -> bool
147+
val isDictFromArray : Parsetree.expression -> bool
144148

145149
val collectOrPatternChain : Parsetree.pattern -> Parsetree.pattern list
146150

jscomp/syntax/src/res_printer.ml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3046,6 +3046,32 @@ and printExpression ~state (e : Parsetree.expression) cmtTbl =
30463046
Doc.rbrace;
30473047
])
30483048
| extension -> printExtension ~state ~atModuleLvl:false extension cmtTbl)
3049+
| Pexp_apply (dictFromArray, [(Nolabel, dictEntries)])
3050+
when ParsetreeViewer.isDictFromArray dictFromArray ->
3051+
let rows =
3052+
match dictEntries.pexp_desc with
3053+
| Pexp_apply (e, [(Nolabel, {pexp_desc = Pexp_array rows})])
3054+
when ParsetreeViewer.isSpreadBeltArrayConcat e ->
3055+
(*
3056+
There are one or more spreads in the dict
3057+
and the rows are nested in Belt.Array.concatMany
3058+
ie. dict{...otherDict, "first": 1, "second": 2 }
3059+
*)
3060+
rows
3061+
| Pexp_array rows ->
3062+
(*
3063+
There is only an array of key value paires defined in the dict
3064+
ie. dict{"first": 1, "second": 2 }
3065+
*)
3066+
rows
3067+
| _ ->
3068+
(*
3069+
This case only happens when there is a single spread dict inside an empty
3070+
ie. dict{...otherDict }
3071+
*)
3072+
[dictEntries]
3073+
in
3074+
printDictExpression ~state ~loc:dictEntries.pexp_loc ~rows cmtTbl
30493075
| Pexp_apply (e, [(Nolabel, {pexp_desc = Pexp_array subLists})])
30503076
when ParsetreeViewer.isSpreadBeltListConcat e ->
30513077
printBeltListConcatApply ~state subLists cmtTbl
@@ -5211,6 +5237,73 @@ and printDirectionFlag flag =
52115237
| Asttypes.Downto -> Doc.text " downto "
52125238
| Asttypes.Upto -> Doc.text " to "
52135239

5240+
and printExpressionDictRow ~state cmtTbl (expr : Parsetree.expression) =
5241+
match expr with
5242+
| {
5243+
pexp_loc = lbl_loc;
5244+
pexp_desc = Pexp_tuple [{pexp_desc = Pexp_constant field}; rowExpr];
5245+
} ->
5246+
let cmtLoc = {lbl_loc with loc_end = rowExpr.pexp_loc.loc_end} in
5247+
let doc =
5248+
Doc.group
5249+
(Doc.concat
5250+
[
5251+
printConstant field;
5252+
Doc.text ": ";
5253+
(let doc = printExpressionWithComments ~state rowExpr cmtTbl in
5254+
match Parens.exprRecordRowRhs rowExpr with
5255+
| Parens.Parenthesized -> addParens doc
5256+
| Braced braces -> printBraces doc rowExpr braces
5257+
| Nothing -> doc);
5258+
])
5259+
in
5260+
printComments doc cmtTbl cmtLoc
5261+
| {pexp_desc = Pexp_apply ({pexp_attributes}, [(_, expr)])}
5262+
when Res_parsetree_viewer.hasDictAttr pexp_attributes ->
5263+
Doc.concat
5264+
[
5265+
Doc.dotdotdot;
5266+
(let doc = printExpressionWithComments ~state expr cmtTbl in
5267+
match Parens.expr expr with
5268+
| Parens.Parenthesized -> addParens doc
5269+
| Braced braces -> printBraces doc expr braces
5270+
| Nothing -> doc);
5271+
]
5272+
| {pexp_desc = Pexp_array rows} ->
5273+
printDictExpressionInner ~state ~rows cmtTbl
5274+
| _ -> Doc.nil
5275+
5276+
and printDictExpressionInner ~state ~rows cmtTbl =
5277+
Doc.concat
5278+
[
5279+
Doc.join
5280+
~sep:(Doc.concat [Doc.text ","; Doc.line])
5281+
(List.map (printExpressionDictRow ~state cmtTbl) rows);
5282+
]
5283+
5284+
and printDictExpression ~state ~loc ~rows cmtTbl =
5285+
if rows = [] then
5286+
Doc.concat [Doc.text "dict{"; printCommentsInside cmtTbl loc; Doc.rbrace]
5287+
else
5288+
(* If the dict is written over multiple lines, break automatically
5289+
* `let x = dict{"a": 1, "b": 3}` -> same line, break when line-width exceeded
5290+
* `let x = dict{
5291+
* "a": 1,
5292+
* "b": 2,
5293+
* }` -> record is written on multiple lines, break the group *)
5294+
let forceBreak = loc.loc_start.pos_lnum < loc.loc_end.pos_lnum in
5295+
Doc.breakableGroup ~forceBreak
5296+
(Doc.concat
5297+
[
5298+
Doc.text "dict{";
5299+
Doc.indent
5300+
(Doc.concat
5301+
[Doc.softLine; printDictExpressionInner ~state ~rows cmtTbl]);
5302+
Doc.trailingComma;
5303+
Doc.softLine;
5304+
Doc.rbrace;
5305+
])
5306+
52145307
and printExpressionRecordRow ~state (lbl, expr) cmtTbl punningAllowed =
52155308
let cmtLoc = {lbl.loc with loc_end = expr.pexp_loc.loc_end} in
52165309
let doc =

jscomp/syntax/src/res_scanner.ml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ let digitValue ch =
179179

180180
(* scanning helpers *)
181181

182+
let objectLiterals = ["list"; "dict"]
183+
182184
let scanIdentifier scanner =
183185
let startOff = scanner.offset in
184186
let rec skipGoodChars scanner =
@@ -192,11 +194,14 @@ let scanIdentifier scanner =
192194
let str =
193195
(String.sub [@doesNotRaise]) scanner.src startOff (scanner.offset - startOff)
194196
in
195-
if '{' == scanner.ch && str = "list" then (
196-
next scanner;
197-
(* TODO: this isn't great *)
198-
Token.lookupKeyword "list{")
199-
else Token.lookupKeyword str
197+
(if '{' == scanner.ch && objectLiterals |> List.mem str then (
198+
(*If string is an object literal ie list{.. or dict{..
199+
forward the scanner to include the opening '{' and
200+
lookup including the '{'*)
201+
next scanner;
202+
str ^ "{")
203+
else str)
204+
|> Token.lookupKeyword
200205

201206
let scanDigits scanner ~base =
202207
if base <= 10 then

0 commit comments

Comments
 (0)