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

Add dict literal syntax #6774

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
8 changes: 4 additions & 4 deletions jscomp/others/belt_HashSetInt.resi
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

/***
This module is [`Belt.HashSet`]() specialized with key type to be a primitive type.
This module is [`Belt.HashSet`]() specialized with key type to be a primitive type.

It is more efficient in general, the API is the same with [`Belt.HashSet`]() except its key type is fixed,
and identity is not needed(using the built-in one)
It is more efficient in general, the API is the same with [`Belt.HashSet`]() except its key type is fixed,
and identity is not needed(using the built-in one)

**See** [`Belt.HashSet`]()
**See** [`Belt.HashSet`]()
*/

type key = int
Expand Down
8 changes: 4 additions & 4 deletions jscomp/others/belt_HashSetString.resi
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

/***
This module is [`Belt.HashSet`]() specialized with key type to be a primitive type.
This module is [`Belt.HashSet`]() specialized with key type to be a primitive type.

It is more efficient in general, the API is the same with [`Belt.HashSet`]() except its key type is fixed,
and identity is not needed(using the built-in one)
It is more efficient in general, the API is the same with [`Belt.HashSet`]() except its key type is fixed,
and identity is not needed(using the built-in one)

**See** [`Belt.HashSet`]()
**See** [`Belt.HashSet`]()
*/

type key = string
Expand Down
44 changes: 43 additions & 1 deletion jscomp/syntax/src/res_core.ml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ let getClosingToken = function
| Lbrace -> Rbrace
| Lbracket -> Rbracket
| List -> Rbrace
| Dict -> Rbrace
| LessThan -> GreaterThan
| _ -> assert false

Expand All @@ -240,7 +241,7 @@ let rec goToClosing closingToken state =
| GreaterThan, GreaterThan ->
Parser.next state;
()
| ((Token.Lbracket | Lparen | Lbrace | List | LessThan) as t), _ ->
| ((Token.Lbracket | Lparen | Lbrace | List | Dict | LessThan) as t), _ ->
Parser.next state;
goToClosing (getClosingToken t) state;
goToClosing closingToken state
Expand Down Expand Up @@ -1918,6 +1919,9 @@ and parseAtomicExpr p =
| List ->
Parser.next p;
parseListExpr ~startPos p
| Dict ->
Parser.next p;
parseDictExpr ~startPos p
| Module ->
Parser.next p;
parseFirstClassModuleExpr ~startPos p
Expand Down Expand Up @@ -3130,6 +3134,20 @@ and parseRecordExprRow p =
| _ -> None)
| _ -> None

and parseDictExprRow p =
match p.Parser.token with
| String s -> (
let loc = mkLoc p.startPos p.endPos in
Parser.next p;
let field = Location.mkloc (Longident.Lident s) loc in
match p.Parser.token with
| Colon ->
Parser.next p;
let fieldExpr = parseExpr p in
Some (field, fieldExpr)
| _ -> Some (field, Ast_helper.Exp.ident ~loc:field.loc field))
| _ -> None

and parseRecordExprWithStringKeys ~startPos firstRow p =
let rows =
firstRow
Expand Down Expand Up @@ -3923,6 +3941,30 @@ and parseListExpr ~startPos p =
loc))
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc listExprs)]

and parseDictExpr ~startPos p =
let exprs =
parseCommaDelimitedRegion ~grammar:Grammar.DictRows ~closing:Rbrace
~f:parseDictExprRow p
in
let loc = mkLoc startPos p.prevEndPos in
let toKeyValuePair recordItem =
match recordItem with
| {Location.txt = Longident.Lident key}, valueExpr ->
Some
(Ast_helper.Exp.tuple ~loc
bloodyowl marked this conversation as resolved.
Show resolved Hide resolved
[Ast_helper.Exp.constant ~loc (Pconst_string (key, None)); valueExpr])
IwanKaramazow marked this conversation as resolved.
Show resolved Hide resolved
| _ -> None
in
let keyValuePairs = List.filter_map toKeyValuePair exprs in
Parser.expect Rbrace p;
Ast_helper.Exp.apply ~loc
IwanKaramazow marked this conversation as resolved.
Show resolved Hide resolved
(Ast_helper.Exp.ident ~loc
(Location.mkloc
(Longident.Ldot
(Longident.Ldot (Longident.Lident "Js", "Dict"), "fromArray"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related #6836

@cknitt I think this needs to be adapted, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but this can be done independently later.

loc))
[(Asttypes.Nolabel, Ast_helper.Exp.array ~loc keyValuePairs)]

and parseArrayExp p =
let startPos = p.Parser.startPos in
Parser.expect Lbracket p;
Expand Down
7 changes: 7 additions & 0 deletions jscomp/syntax/src/res_grammar.ml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type t =
| Pattern
| AttributePayload
| TagNames
| DictRows

let toString = function
| OpenDescription -> "an open description"
Expand Down Expand Up @@ -120,6 +121,7 @@ let toString = function
| ExprFor -> "a for expression"
| AttributePayload -> "an attribute payload"
| TagNames -> "tag names"
| DictRows -> "rows of a dict"

let isSignatureItemStart = function
| Token.At | Let | Typ | External | Exception | Open | Include | Module | AtAt
Expand Down Expand Up @@ -219,6 +221,10 @@ let isModExprStart = function
true
| _ -> false

let isDictRowStart = function
| Token.String _ -> true
| _ -> false

let isRecordRowStart = function
| Token.DotDotDot -> true
| Token.Uident _ | Lident _ -> true
Expand Down Expand Up @@ -278,6 +284,7 @@ let isListElement grammar token =
| FunctorArgs -> isFunctorArgStart token
| ModExprList -> isModExprStart token
| TypeParameters -> isTypeParameterStart token
| DictRows -> isDictRowStart token
| RecordRows -> isRecordRowStart token
| RecordRowsStringKey -> isRecordRowStringKeyStart token
| ArgumentList -> isArgumentStart token
Expand Down
10 changes: 10 additions & 0 deletions jscomp/syntax/src/res_parsetree_viewer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -747,3 +747,13 @@ let isRewrittenUnderscoreApplySugar expr =
match expr.pexp_desc with
| Pexp_ident {txt = Longident.Lident "_"} -> true
| _ -> false

let isTupleArray (expr : Parsetree.expression) =
let isPlainTuple (expr : Parsetree.expression) =
match expr with
| {pexp_desc = Pexp_tuple _} -> true
| _ -> false
in
match expr with
| {pexp_desc = Pexp_array items} -> List.for_all isPlainTuple items
| _ -> false
2 changes: 2 additions & 0 deletions jscomp/syntax/src/res_parsetree_viewer.mli
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,5 @@ val hasIfLetAttribute : Parsetree.attributes -> bool
val isRewrittenUnderscoreApplySugar : Parsetree.expression -> bool

val isFunNewtype : Parsetree.expression -> bool

val isTupleArray : Parsetree.expression -> bool
57 changes: 57 additions & 0 deletions jscomp/syntax/src/res_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,45 @@ and printRecordDeclaration ~state (lds : Parsetree.label_declaration list)
Doc.rbrace;
])

and printLiteralDictExpr ~state (e : Parsetree.expression) cmtTbl =
let forceBreak =
e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum
in
let tupleToRow (e : Parsetree.expression) =
match e with
| {
pexp_desc =
Pexp_tuple
[
{pexp_desc = Pexp_constant (Pconst_string (name, _)); pexp_loc}; value;
];
} ->
Some (Location.mkloc (Longident.Lident name) pexp_loc, value)
| _ -> None
in
let rows =
match e with
| {pexp_desc = Pexp_array expressions} ->
List.filter_map tupleToRow expressions
| _ -> []
in
Doc.breakableGroup ~forceBreak
(Doc.concat
[
Doc.indent
(Doc.concat
[
Doc.softLine;
Doc.join
~sep:(Doc.concat [Doc.text ","; Doc.line])
(List.map
(fun row -> printBsObjectRow ~state row cmtTbl)
rows);
]);
Doc.trailingComma;
Doc.softLine;
])

and printConstructorDeclarations ~state ~privateFlag
(cds : Parsetree.constructor_declaration list) cmtTbl =
let forceBreak =
Expand Down Expand Up @@ -3956,6 +3995,24 @@ and printPexpApply ~state expr cmtTbl =
| [] -> doc
| attrs -> Doc.group (Doc.concat [printAttributes ~state attrs cmtTbl; doc])
)
| Pexp_apply
( {
pexp_desc =
Pexp_ident
{
txt =
Longident.Ldot
(Longident.Ldot (Lident "Js", "Dict"), "fromArray");
};
},
[(Nolabel, keyValues)] )
when Res_parsetree_viewer.isTupleArray keyValues ->
Doc.concat
[
Doc.text "dict{";
printLiteralDictExpr ~state keyValues cmtTbl;
Doc.rbrace;
]
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Longident.Ldot (Lident "Array", "get")}},
[(Nolabel, parentExpr); (Nolabel, memberExpr)] )
Expand Down
11 changes: 8 additions & 3 deletions jscomp/syntax/src/res_scanner.ml
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,16 @@ let scanIdentifier scanner =
let str =
(String.sub [@doesNotRaise]) scanner.src startOff (scanner.offset - startOff)
in
if '{' == scanner.ch && str = "list" then (
match (scanner, str) with
| {ch = '{'}, "list" ->
next scanner;
(* TODO: this isn't great *)
Token.lookupKeyword "list{")
else Token.lookupKeyword str
Token.lookupKeyword "list{"
| {ch = '{'}, "dict" ->
next scanner;
(* TODO: this isn't great *)
Token.lookupKeyword "dict{"
| _ -> Token.lookupKeyword str

let scanDigits scanner ~base =
if base <= 10 then
Expand Down
5 changes: 4 additions & 1 deletion jscomp/syntax/src/res_token.ml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ type t =
| PercentPercent
| Comment of Comment.t
| List
| Dict
| TemplateTail of string * Lexing.position
| TemplatePart of string * Lexing.position
| Backtick
Expand Down Expand Up @@ -198,6 +199,7 @@ let toString = function
| PercentPercent -> "%%"
| Comment c -> "Comment" ^ Comment.toString c
| List -> "list{"
| Dict -> "dict{"
| TemplatePart (text, _) -> text ^ "${"
| TemplateTail (text, _) -> "TemplateTail(" ^ text ^ ")"
| Backtick -> "`"
Expand All @@ -222,6 +224,7 @@ let keywordTable = function
| "include" -> Include
| "let" -> Let
| "list{" -> List
| "dict{" -> Dict
| "module" -> Module
| "mutable" -> Mutable
| "of" -> Of
Expand All @@ -240,7 +243,7 @@ let keywordTable = function
let isKeyword = function
| Await | And | As | Assert | Constraint | Else | Exception | External | False
| For | If | In | Include | Land | Let | List | Lor | Module | Mutable | Of
| Open | Private | Rec | Switch | True | Try | Typ | When | While ->
| Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict ->
true
| _ -> false

Expand Down
11 changes: 11 additions & 0 deletions jscomp/syntax/tests/parsing/grammar/expressions/dict.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// empty dict
let x = dict{}

// one value
let x = dict{"foo": "bar"}

// two values
let x = dict{"foo": "bar", "bar": "baz"}

let baz = "foo"
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let x = Js.Dict.fromArray [||]
let x = Js.Dict.fromArray [|("foo", {js|bar|js})|]
let x = Js.Dict.fromArray [|("foo", {js|bar|js});("bar", {js|baz|js})|]
let baz = {js|foo|js}
let x =
Js.Dict.fromArray
[|("foo", {js|bar|js});("bar", {js|baz|js});("baz", baz)|]
27 changes: 27 additions & 0 deletions jscomp/syntax/tests/printer/expr/dict.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// empty dict
let x = dict{}

// one value
let x = dict{"foo": "bar"}

// two values
let x = dict{"foo": "bar", "bar": "baz"}

let baz = "foo"
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}

// multiline
let x = dict{
"foo": "bar",
"bar": "baz",
"baz": baz
}

let x = Js.Dict.fromArray([("foo", "bar"), ("bar", "baz")])
let x = Js.Dict.fromArray([("foo", "bar"), ("bar", "baz"), ("baz", baz)])

let x = Js.Dict.fromArray([
("foo", "bar"),
("bar", "baz"),
("baz", baz)
])
27 changes: 27 additions & 0 deletions jscomp/syntax/tests/printer/expr/expected/dict.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// empty dict
let x = dict{}

// one value
let x = dict{"foo": "bar"}

// two values
let x = dict{"foo": "bar", "bar": "baz"}

let baz = "foo"
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}

// multiline
let x = dict{
"foo": "bar",
"bar": "baz",
"baz": baz,
}

let x = dict{"foo": "bar", "bar": "baz"}
let x = dict{"foo": "bar", "bar": "baz", "baz": baz}

let x = dict{
"foo": "bar",
"bar": "baz",
"baz": baz,
}
Loading