Skip to content

Commit

Permalink
Merge pull request #1613 from DanielXMoore/reduction-patterns
Browse files Browse the repository at this point in the history
`for` reduction implicit body can destructure, fix implicitly returned patterns in some cases
  • Loading branch information
edemaine authored Nov 20, 2024
2 parents 551f2ba + 6ffd7e7 commit 452450e
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 101 deletions.
12 changes: 12 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1970,6 +1970,18 @@ all := for join item of array
`[${item.type}] ${item.title}`
</Playground>
Implicit bodies in `for sum/product/min/max/join` reductions
can use a single destructuring:
<Playground>
xMin := for min {x} of points
</Playground>
<Playground>
xMin := for min [x] of points
yMin := for min [, y] of points
</Playground>
### Object Comprehensions
Loops can also accumulate their body values into an object.
Expand Down
13 changes: 2 additions & 11 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -2239,22 +2239,13 @@ BindingElement

# NOTE: Merged in SingleNameBinding
_?:ws ( BindingIdentifier / BindingPattern ):binding BindingTypeSuffix?:typeSuffix Initializer?:initializer ->
// NOTE: RegExpLiteral doesn't have children so it will lose the initializer
// for now
if (binding.children) {
binding = {
...binding,
initializer,
children: [...binding.children, initializer],
}
}

return {
type: "BindingElement",
names: binding.names,
typeSuffix,
binding,
children: [ws, binding],
children: [ws, binding, initializer],
initializer,
}

# NOTE: Merged in ElisionElement
Expand Down
10 changes: 7 additions & 3 deletions source/parser/binding.civet
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type {
ASTNode
ASTNodeObject
AtBinding
BindingPattern
BindingRestElement
Children
ObjectBindingPattern
ThisAssignments
Expand Down Expand Up @@ -64,8 +66,8 @@ function adjustBindingElements(elements: ASTNodeObject[])
length
}
else if restCount is 1
rest := elements[restIndex]
after := elements.slice(restIndex + 1)
rest := elements[restIndex] as BindingRestElement
after := elements[restIndex + 1..]

restIdentifier := rest.binding.ref or rest.binding
names.push ...rest.names or []
Expand All @@ -76,9 +78,11 @@ function adjustBindingElements(elements: ASTNodeObject[])
// increment l if trailing comma
if (arrayElementHasTrailingComma(after[l - 1])) l++

elements := trimFirstSpace after
blockPrefix = {
type: "PostRestBindingElements"
children: ["[", trimFirstSpace(after), "] = ", restIdentifier, ".splice(-", l.toString(), ")"]
elements
children: ["[", elements, "] = ", restIdentifier, ".splice(-", l.toString(), ")"]
names: after.flatMap((p) => p.names)
}

Expand Down
129 changes: 95 additions & 34 deletions source/parser/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import type {
ASTLeaf
ASTNode
ASTNodeObject
BindingIdentifier
BlockStatement
BreakStatement
CallExpression
Children
Declaration
ForStatement
FunctionNode
Expand Down Expand Up @@ -284,41 +286,83 @@ function processReturnValue(func: FunctionNode)

return true

function patternAsValue(pattern): ASTNode
switch (pattern.type) {
case "ArrayBindingPattern": {
const children = [...pattern.children]
const index = children.indexOf(pattern.elements)
function patternAsValue(pattern: ASTNodeObject): ASTNode
switch pattern.type
when "ArrayBindingPattern"
children := [...pattern.children]
index := children.indexOf pattern.elements
if (index < 0) throw new Error("failed to find elements in ArrayBindingPattern")
children[index] = pattern.elements.map((el) => {
const [ws, e, delim] = el.children
return { ...el, children: [ws, patternAsValue(e), delim] }
})
return { ...pattern, children }
}
case "ObjectBindingPattern": {
const children = [...pattern.children]
const index = children.indexOf(pattern.properties)
elements := children[index] = pattern.elements.map patternAsValue
{ ...pattern, elements, children }
when "ObjectBindingPattern"
children := [...pattern.children]
index := children.indexOf pattern.properties
if (index < 0) throw new Error("failed to find properties in ArrayBindingPattern")
children[index] = pattern.properties.map patternAsValue
return { ...pattern, children }
}
case "Identifier":
case "BindingProperty": {
const children = [
// { name: value } = ... declares value, not name
pattern.value ?? pattern.name
pattern.delim
]
// Check for leading whitespace
if (isWhitespaceOrEmpty(pattern.children[0])) {
children.unshift(pattern.children[0])
}
return { ...pattern, children }
}
default:
return pattern
}
properties := children[index] = pattern.properties.map patternAsValue
{ ...pattern, properties, children }
when "BindingProperty"
let children: Children
// { name: value } = ... declares value, not name
if pattern.value?.type is "Identifier"
children = [ pattern.value, pattern.delim ]
// Check for leading whitespace
if isWhitespaceOrEmpty pattern.children[0]
children.unshift pattern.children[0]
else
children = [...pattern.children]
if pattern.initializer?
index := children.indexOf pattern.initializer
assert.notEqual index, -1, "failed to find initializer in BindingElement"
children.splice index, 1
if pattern.value?
children = children.map & is pattern.value ?
patternAsValue pattern.value : &
{ ...pattern, children }
when "AtBindingProperty"
children := [...pattern.children]
if pattern.initializer?
index := children.indexOf pattern.initializer
assert.notEqual index, -1, "failed to find initializer in AtBindingProperty"
children.splice index, 1
{ ...pattern, children }
when "BindingElement"
children := [...pattern.children]
if pattern.initializer?
index := children.indexOf pattern.initializer
assert.notEqual index, -1, "failed to find initializer in BindingElement"
children.splice index, 1
index := children.indexOf pattern.binding
assert.notEqual index, -1, "failed to find binding in BindingElement"
children[index] = patternAsValue pattern.binding
{ ...pattern, children }
else
pattern

/**
Find all bound variables in a pattern and return as an array
Example: {x: [, y], z} -> [y, z]
*/
function patternBindings(pattern: ASTNodeObject): BindingIdentifier[]
bindings: BindingIdentifier[] := []
recurse pattern
return bindings

function recurse(pattern: ASTNodeObject): void
switch pattern.type
when "ArrayBindingPattern"
for each element of pattern.elements
recurse element
when "ObjectBindingPattern"
for each property of pattern.properties
recurse property
when "BindingElement"
recurse pattern.binding
when "BindingProperty"
recurse pattern.value ?? pattern.name
when "Binding"
recurse pattern.pattern
when "Identifier", "AtBinding"
bindings.push pattern

// NOTE: this is almost the same as insertReturn but doesn't remove `breaks` in `when` and
// does construct an else clause pushing undefined in if statements that lack them
Expand Down Expand Up @@ -740,7 +784,24 @@ function iterationDefaultBody(statement: IterationStatement | ForStatement): boo

if statement.type is "ForStatement" and
statement.declaration?.type is "ForDeclaration"
fillBlock [ "", patternAsValue statement.declaration.binding ]
// For regular loops and object comprehensions, use entire pattern
// For reductions, use the first binding
if reduction
bindings := patternBindings statement.declaration.binding.pattern
if bindings#
fillBlock [ "", bindings[0] ]
for binding of bindings[1..]
binding.children.unshift
type: "Error"
subtype: "Warning"
message: "Ignored binding in reduction loop with implicit body"
else
fillBlock [ "",
type: "Error"
message: "Empty binding pattern in reduction loop with implicit body"
]
else
fillBlock [ "", patternAsValue statement.declaration.binding.pattern ]
block.empty = false

return false
Expand Down
85 changes: 38 additions & 47 deletions source/parser/pattern-matching.civet
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type {
ArrayBindingPatternContent
ASTNode
ASTNodeObject
ASTRef
BindingProperty
BindingRestElement
BindingRestProperty
BlockStatement
Condition
ContinueStatement
ElseClause
Identifier
ParenthesizedExpression
ParseRule
PatternClause
Expand Down Expand Up @@ -314,24 +319,23 @@ function getPatternBlockPrefix(
...duplicateDeclarations.map ['', &, ";"]
]

function elideMatchersFromArrayBindings(elements) {
return elements.map((el) => {
// TODO: this isn't unified with the element [ws, e, sep] tuple yet
if (el.type is "BindingRestElement") {
return ["", el, undefined]
}
const { children: [ws, e, delim] } = el
switch (e.type) {
case "Literal":
case "RegularExpressionLiteral":
case "StringLiteral":
case "PinPattern":
return delim
default:
return [ws, nonMatcherBindings(e), delim]
}
})
}
function elideMatchersFromArrayBindings(elements: ArrayBindingPatternContent): ASTNode[]
for each element of elements
switch element.type
when "BindingRestElement", "ElisionElement"
element
when "BindingElement"
switch element.binding.type
when "Literal", "RegularExpressionLiteral", "StringLiteral", "PinPattern"
element.delim
else
binding := nonMatcherBindings element.binding
makeNode {
...element
binding
children: element.children.map (c) =>
c is element.binding ? binding : c
}

function elideMatchersFromPropertyBindings(properties) {
return properties.map((p) => {
Expand Down Expand Up @@ -373,28 +377,18 @@ function elideMatchersFromPropertyBindings(properties) {
})
}

function nonMatcherBindings(pattern)
function nonMatcherBindings(pattern: ASTNodeObject)
switch pattern.type
when "ArrayBindingPattern"
elements := elideMatchersFromArrayBindings(pattern.elements)
{
when "ArrayBindingPattern", "PostRestBindingElements"
elements := elideMatchersFromArrayBindings pattern.elements
makeNode {
...pattern
elements
children: pattern.children.map & is pattern.elements ? elements : &
}
when "PostRestBindingElements"
const els = elideMatchersFromArrayBindings(pattern.children[1])
{
...pattern,
children: [
pattern.children[0],
els,
...pattern.children.slice(2),
],
}
when "ObjectBindingPattern"
properties := elideMatchersFromPropertyBindings(pattern.properties)
{
properties := elideMatchersFromPropertyBindings pattern.properties
makeNode {
...pattern
properties
children: pattern.children.map & is pattern.properties ? properties : &
Expand All @@ -403,21 +397,18 @@ function nonMatcherBindings(pattern)
pattern

function aggregateDuplicateBindings(bindings)
props := gatherRecursiveAll bindings, .type is "BindingProperty"
arrayBindings := gatherRecursiveAll bindings, .type is "ArrayBindingPattern"

for each { elements } of arrayBindings
for each element of elements
if Array.isArray element
[, e] := element
if e.type is "Identifier"
props.push e
else if e.type is "BindingRestElement"
props.push e
props := gatherRecursiveAll bindings,
($): $ is BindingProperty | BindingRestProperty | Identifier | BindingRestElement => (or)
$.type is "BindingProperty"
// Don't deduplicate ...rest properties; user should do so manually
// because ...rest can be named arbitrarily
//$.type is "BindingRestProperty"
$.type is "Identifier" and $.parent?.type is "BindingElement"
$.type is "BindingRestElement"

declarations: ASTNode[] := []

propsGroupedByName: Map<string, ASTNodeObject[]> := new Map
propsGroupedByName := new Map<string, ASTNodeObject[]>

for each p of props
{ name, value } := p
Expand Down Expand Up @@ -475,7 +466,7 @@ function aggregateDuplicateBindings(bindings)
* Adjust the alias of a binding property, adding an alias if one doesn't exist or
* replacing an existing alias. This mutates the property in place.
*/
function aliasBinding(p, ref: ASTRef): void
function aliasBinding(p: ASTNodeObject, ref: ASTRef): void
if p.type is "Identifier"
// Array element binding
// TODO: This ignores `name` and `names` properties of Identifier and
Expand Down
Loading

0 comments on commit 452450e

Please sign in to comment.