Skip to content

Commit

Permalink
Merge pull request #1563 from peey/feature/object-comprehensions
Browse files Browse the repository at this point in the history
Object comprehensions via loops in braces
  • Loading branch information
edemaine authored Nov 7, 2024
2 parents 80a6707 + a3fc074 commit b9c6959
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 0 deletions.
34 changes: 34 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1962,6 +1962,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 @@ -3478,6 +3478,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 @@ -3596,6 +3630,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 @@ -6520,6 +6555,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 "={}"
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

0 comments on commit b9c6959

Please sign in to comment.