Skip to content

Commit d0b6b4d

Browse files
committed
Add initial dict literal implementation
Remove comments Remove unused collect function Remove print Fix dead code
1 parent cfd4eb9 commit d0b6b4d

File tree

8 files changed

+257
-12
lines changed

8 files changed

+257
-12
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

@@ -180,6 +182,7 @@ let taggedTemplateLiteralAttr =
180182
(Location.mknoloc "res.taggedTemplate", Parsetree.PStr [])
181183

182184
let spreadAttr = (Location.mknoloc "res.spread", Parsetree.PStr [])
185+
let dictAttr = (Location.mknoloc "res.dict", Parsetree.PStr [])
183186

184187
type argument = {
185188
dotted: bool;
@@ -229,6 +232,7 @@ let getClosingToken = function
229232
| Lbrace -> Rbrace
230233
| Lbracket -> Rbracket
231234
| List -> Rbrace
235+
| Dict -> Rbrace
232236
| LessThan -> GreaterThan
233237
| _ -> assert false
234238

@@ -240,7 +244,7 @@ let rec goToClosing closingToken state =
240244
| GreaterThan, GreaterThan ->
241245
Parser.next state;
242246
()
243-
| ((Token.Lbracket | Lparen | Lbrace | List | LessThan) as t), _ ->
247+
| ((Token.Lbracket | Lparen | Lbrace | List | Dict | LessThan) as t), _ ->
244248
Parser.next state;
245249
goToClosing (getClosingToken t) state;
246250
goToClosing closingToken state
@@ -1917,6 +1921,9 @@ and parseAtomicExpr p =
19171921
| List ->
19181922
Parser.next p;
19191923
parseListExpr ~startPos p
1924+
| Dict ->
1925+
Parser.next p;
1926+
parseDictExpr ~startPos p
19201927
| Module ->
19211928
Parser.next p;
19221929
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
and parseArrayExp p =
39244037
let startPos = p.Parser.startPos in
39254038
Parser.expect Lbracket p;

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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,26 @@ let isSpreadBeltArrayConcat expr =
694694
hasSpreadAttr expr.pexp_attributes
695695
| _ -> false
696696

697+
let hasDictAttr attrs =
698+
List.exists
699+
(fun attr ->
700+
match attr with
701+
| {Location.txt = "res.dict"}, _ -> true
702+
| _ -> false)
703+
attrs
704+
705+
let isDictFromArray expr =
706+
match expr.pexp_desc with
707+
| Pexp_ident
708+
{
709+
txt =
710+
Longident.Ldot
711+
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray");
712+
} ->
713+
let v = hasDictAttr expr.pexp_attributes in
714+
v
715+
| _ -> false
716+
697717
(* Blue | Red | Green -> [Blue; Red; Green] *)
698718
let collectOrPatternChain pat =
699719
let rec loop pattern chain =

jscomp/syntax/src/res_parsetree_viewer.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ val isTemplateLiteral : Parsetree.expression -> bool
144144
val isTaggedTemplateLiteral : Parsetree.expression -> bool
145145
val hasTemplateLiteralAttr : Parsetree.attributes -> bool
146146

147+
val hasSpreadAttr : (string Location.loc * 'a) list -> bool
147148
val isSpreadBeltListConcat : Parsetree.expression -> bool
149+
val hasDictAttr : (string Location.loc * 'a) list -> bool
150+
val isDictFromArray : Parsetree.expression -> bool
148151

149152
val isSpreadBeltArrayConcat : Parsetree.expression -> bool
150153

jscomp/syntax/src/res_printer.ml

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

5300+
and printExpressionDictRow ~state cmtTbl (expr : Parsetree.expression) =
5301+
match expr with
5302+
| {
5303+
pexp_loc = lbl_loc;
5304+
pexp_desc = Pexp_tuple [{pexp_desc = Pexp_constant field}; rowExpr];
5305+
} ->
5306+
let cmtLoc = {lbl_loc with loc_end = rowExpr.pexp_loc.loc_end} in
5307+
let doc =
5308+
Doc.group
5309+
(Doc.concat
5310+
[
5311+
printConstant field;
5312+
Doc.text ": ";
5313+
(let doc = printExpressionWithComments ~state rowExpr cmtTbl in
5314+
match Parens.exprRecordRowRhs rowExpr with
5315+
| Parens.Parenthesized -> addParens doc
5316+
| Braced braces -> printBraces doc rowExpr braces
5317+
| Nothing -> doc);
5318+
])
5319+
in
5320+
printComments doc cmtTbl cmtLoc
5321+
| {pexp_desc = Pexp_apply ({pexp_attributes}, [(_, expr)])}
5322+
when Res_parsetree_viewer.hasDictAttr pexp_attributes ->
5323+
Doc.concat
5324+
[
5325+
Doc.dotdotdot;
5326+
(let doc = printExpressionWithComments ~state expr cmtTbl in
5327+
match Parens.expr expr with
5328+
| Parens.Parenthesized -> addParens doc
5329+
| Braced braces -> printBraces doc expr braces
5330+
| Nothing -> doc);
5331+
]
5332+
| {pexp_desc = Pexp_array rows} ->
5333+
printDictExpressionInner ~state ~rows cmtTbl
5334+
| _ -> Doc.nil
5335+
5336+
and printDictExpressionInner ~state ~rows cmtTbl =
5337+
Doc.concat
5338+
[
5339+
Doc.join
5340+
~sep:(Doc.concat [Doc.text ","; Doc.line])
5341+
(List.map (printExpressionDictRow ~state cmtTbl) rows);
5342+
]
5343+
5344+
and printDictExpression ~state ~loc ~rows cmtTbl =
5345+
if rows = [] then
5346+
Doc.concat [Doc.text "dict{"; printCommentsInside cmtTbl loc; Doc.rbrace]
5347+
else
5348+
(* If the dict is written over multiple lines, break automatically
5349+
* `let x = dict{"a": 1, "b": 3}` -> same line, break when line-width exceeded
5350+
* `let x = dict{
5351+
* "a": 1,
5352+
* "b": 2,
5353+
* }` -> record is written on multiple lines, break the group *)
5354+
let forceBreak = loc.loc_start.pos_lnum < loc.loc_end.pos_lnum in
5355+
Doc.breakableGroup ~forceBreak
5356+
(Doc.concat
5357+
[
5358+
Doc.text "dict{";
5359+
Doc.indent
5360+
(Doc.concat
5361+
[Doc.softLine; printDictExpressionInner ~state ~rows cmtTbl]);
5362+
Doc.trailingComma;
5363+
Doc.softLine;
5364+
Doc.rbrace;
5365+
])
5366+
52745367
and printExpressionRecordRow ~state (lbl, expr) cmtTbl punningAllowed =
52755368
let cmtLoc = {lbl.loc with loc_end = expr.pexp_loc.loc_end} in
52765369
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 =
@@ -195,11 +197,14 @@ let scanIdentifier scanner =
195197
let str =
196198
(String.sub [@doesNotRaise]) scanner.src startOff (scanner.offset - startOff)
197199
in
198-
if '{' == scanner.ch && str = "list" then (
199-
next scanner;
200-
(* TODO: this isn't great *)
201-
Token.lookupKeyword "list{")
202-
else Token.lookupKeyword str
200+
(if '{' == scanner.ch && objectLiterals |> List.mem str then (
201+
(*If string is an object literal ie list{.. or dict{..
202+
forward the scanner to include the opening '{' and
203+
lookup including the '{'*)
204+
next scanner;
205+
str ^ "{")
206+
else str)
207+
|> Token.lookupKeyword
203208

204209
let scanDigits scanner ~base =
205210
if base <= 10 then

0 commit comments

Comments
 (0)