Skip to content

Commit

Permalink
Optimize spread iteration inside iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
edemaine committed Dec 23, 2024
1 parent b4a8b52 commit 2923003
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 9 deletions.
7 changes: 7 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1861,6 +1861,13 @@ function flatJoin<T>(list: T[][], sep: T): T[]
...sublist
</Playground>
<Playground>
flatImage :=
for x of [0...nx]
...for y of [0...ny]
image.get x, y
</Playground>
If you don't specify a body, `for` loops list the item being iterated over:
<Playground>
Expand Down
41 changes: 35 additions & 6 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,17 @@ CommaExpression
# Variation on CommaExpression that allows ... spreads,
# which we allow in statements for the sake of expressionized iterations
CommaExpressionSpread
_?:ws DotDotDot _?:ws2 IterationActualStatement:iteration ->
if (iteration.subtype === "do" || iteration.subtype === "comptime") return $skip
if (ws2) {
if (ws) {
ws = [ws, ws2]
} else {
ws = ws2
}
}
iteration = { ...iteration, resultsParent: true }
return prepend(ws, iteration)
AssignmentExpressionSpread ( CommaDelimiter AssignmentExpressionSpread )* ->
if($2.length == 0) return $1
return $0
Expand All @@ -349,6 +360,21 @@ AssignmentExpressionSpread
expression,
names: expression.names,
}
_? DotDotDot AssignmentExpression:expression ->
return {
type: "SpreadElement",
children: $0,
expression,
names: expression.names,
}
AssignmentExpression:expression ( _? DotDotDot )? ->
if (!$2) return $1
return {
type: "SpreadElement",
children: [ ...$2, $1 ],
expression,
names: expression.names,
}

# https://262.ecma-international.org/#prod-Arguments
Arguments
Expand Down Expand Up @@ -4386,12 +4412,7 @@ Statement
KeywordStatement
VariableStatement
IfStatement !ShouldExpressionize -> $1
IterationStatement !ShouldExpressionize ->
// Leave generator forms for IterationExpression
if ($1.generator) return $skip
// Leave `for` reductions for IterationExpression
if ($1.reduction) return $skip
return $1
IterationActualStatement
SwitchStatement !ShouldExpressionize -> $1
TryStatement !ShouldExpressionize -> $1
FinallyClause
Expand All @@ -4407,6 +4428,14 @@ Statement

# NOTE: no WithStatement

IterationActualStatement
IterationStatement !ShouldExpressionize ->
// Leave generator forms for IterationExpression
if ($1.generator) return $skip
// Leave `for` reductions for IterationExpression
if ($1.reduction) return $skip
return $1

# NOTE: Leave statement with trailing call expressions or pipe operator
# to be expressionized by
# ExpressionizedStatementWithTrailingCallExpressions
Expand Down
2 changes: 1 addition & 1 deletion source/parser/declaration.civet
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function prependStatementExpressionBlock(initializer: Initializer, statement: AS
ws = exp[0]
exp = exp[1]!

return unless exp?.type is "StatementExpression"
return unless exp is like {type: "StatementExpression"}, {type: "SpreadElement", expression: {type: "StatementExpression"}}

pre: ASTNode[] := []
statementExp := exp.statement
Expand Down
20 changes: 19 additions & 1 deletion source/parser/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,24 @@ function wrapIterationReturningResults(
// to implement `implicitlyReturned`
return if statement.resultsRef?

resultsRef := statement.resultsRef = makeRef "results"
// Inherit parent's resultsRef if requested
if statement.resultsParent
{ ancestor } := findAncestor statement,
.type is "ForStatement" or .type is "IterationStatement"
isFunction
unless ancestor
statement.children.unshift
type: "Error"
message: "Could not find ancestor of spread iteration"
return
resultsRef := statement.resultsRef = ancestor.resultsRef
iterationDefaultBody statement
{ block } := statement
unless block.empty
assignResults block, (node) => [ resultsRef, ".push(", node, ")" ]
return

resultsRef := statement.resultsRef ?= makeRef "results"

declaration := iterationDeclaration statement
{ ancestor, child } := findAncestor statement, .type is "BlockStatement"
Expand Down Expand Up @@ -1145,6 +1162,7 @@ function expressionizeIteration(exp: IterationExpression): void

statements =
. ["", statement]

else
resultsRef := statement.resultsRef ??= makeRef "results"

Expand Down
2 changes: 1 addition & 1 deletion source/parser/lib.civet
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,7 @@ function processAssignments(statements): void

let block?: BlockStatement
// Extract StatementExpression as block
if exp.parent?.type is "BlockStatement" and !$1.-1?.-1?.special// can only prepend to assignments that are children of blocks
if blockContainingStatement(exp) and !$1.-1?.-1?.special// can only prepend to assignments that are children of blocks
block = makeBlockFragment()
if ref := prependStatementExpressionBlock(
{type: "Initializer", expression: $2, children: [undefined, undefined, $2]}
Expand Down
2 changes: 2 additions & 0 deletions source/parser/types.civet
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ export type IterationStatement
generator?: ASTNode
object?: boolean
resultsRef: ASTRef?
resultsParent?: boolean // inherit resultsRef from parent loop

export type BreakStatement
type: "BreakStatement"
Expand Down Expand Up @@ -425,6 +426,7 @@ export type ForStatement
hoistDec: ASTNode
generator?: ASTNode
resultsRef: ASTRef?
resultsParent?: boolean // inherit resultsRef from parent loop
reduction?: ForReduction
object?: boolean

Expand Down
27 changes: 27 additions & 0 deletions test/for.civet
Original file line number Diff line number Diff line change
Expand Up @@ -1582,3 +1582,30 @@ describe "for", ->
results.push(item.pre, ...item.mid, item.post, ... item.tail)
};const result =results
"""

testCase """
spread iteration
---
function flatSquare(arrays)
for array of arrays
... for item of array
item * item
---
function flatSquare(arrays) {
const results=[];for (const array of arrays) {
for (const item of array) {
results.push(item * item)
}
};return results;
}
"""

throws """
spread iteration without parent
---
function flatSquare(arrays)
... for item of array
item * item
---
ParseErrors: unknown:2:3 Could not find ancestor of spread iteration
"""

0 comments on commit 2923003

Please sign in to comment.