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

Object comprehensions via loops in braces #1563

Merged
merged 11 commits into from
Nov 7, 2024
34 changes: 34 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,40 @@ min := for min item of array
max := for max item of array
</Playground>

### Object Comprehensions

Loops can also accumulate their body values into an object.
When any loop is found is found within a braced object expression,
its body value is spread into the containing object.

<Playground>
object := {a: 1, b: 2, c: 3}
doubled := {
for key in object
[key]: 2 * object[key]
}
</Playground>

<Playground>
i .= 1
squares := {
do
[i]: i * i
while i++ < 10
}
</Playground>

The loop can exist anywhere a property is expected.
It can be freely mixed with other object properties.

<Playground>
rateLimits := {
admin: Infinity,
for user of users
[user.name]: getRemainingLimit(user)
}
</Playground>

### Infinite Loop

<Playground>
Expand Down
39 changes: 39 additions & 0 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -3422,6 +3422,40 @@ ObjectPropertyDelimiter
# https://262.ecma-international.org/#prod-PropertyDefinition
# NOTE: Must start on same line
PropertyDefinition
# NOTE: This needs to be above NamedProperty so that
# a `do` block doesn't get treated as {do} property
_?:ws InsertDotDotDot:dots IterationExpression:exp ->
let { statement } = exp
// Treat empty-bodied `do` and `loop` (which require no expression)
// as keys instead of iterations
if (exp.block.implicit &&
(statement.type === "DoStatement" || statement.subtype === "loop")) {
return $skip
}

// immutably set exp.statement.object = true
statement = { ...statement, object: true }
exp = {
...exp,
statement,
children: exp.children.map(($) => $ === exp.statement ? statement : $),
}

const children = [ws, dots, exp]
if (statement.reduction) {
children.unshift({
type: "Error",
message: "Reduction loops are forbidden in object literals",
})
}

return {
type: "SpreadProperty",
children,
names: exp.names,
dots,
value: exp,
}
_?:ws NamedProperty:prop ->
return prepend(ws, prop)

Expand Down Expand Up @@ -3540,6 +3574,7 @@ PropertyDefinition
#_?:ws IdentifierReference:id ->
# return prepend(ws, id)


NamedProperty
# NOTE: CoverInitializedName early error doesn't seem necessary with this parser
# NOTE: Using PostfixedExpression to allow If/Switch expressions and postfixes
Expand Down Expand Up @@ -6453,6 +6488,10 @@ DotDotDot
"…" ->
return { $loc, token: "..." }

InsertDotDotDot
"" ->
return { $loc, token: "..." }

DoubleColon
"::" ->
return { $loc, token: $1 }
Expand Down
3 changes: 3 additions & 0 deletions source/parser/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ function iterationDeclaration(statement: IterationStatement | ForStatement)
when "max" then "-Infinity"
when "product" then "1"
else "0"
else if statement.object
declaration.children.push "={}"
peey marked this conversation as resolved.
Show resolved Hide resolved
else
// Assign [] directly only in const case, so TypeScript can better infer
if decl is "const"
Expand All @@ -684,6 +686,7 @@ function iterationDeclaration(statement: IterationStatement | ForStatement)
return declaration
unless block.empty
assignResults block, (node) =>
return [ "Object.assign(", resultsRef, ",", node, ")" ] if statement.object
return [ resultsRef, ".push(", node, ")" ] unless reduction
switch reduction.subtype
when "some"
Expand Down
Loading
Loading