Skip to content

Commit

Permalink
Merge pull request #1397 from DanielXMoore/break-continue-with-2
Browse files Browse the repository at this point in the history
`break/continue [label] with` and better error handling
  • Loading branch information
edemaine committed Sep 12, 2024
2 parents 3ea9e9f + 2f80bed commit a67393f
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 23 deletions.
10 changes: 10 additions & 0 deletions civet.dev/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,16 @@ found :=
process item
</Playground>
<Playground>
function process(lists)
:outer for list of lists
for item of list
if item is "abort"
break outer with item
if item is "length"
continue :outer with list.length
</Playground>
## Other Blocks
### Try Blocks
Expand Down
26 changes: 23 additions & 3 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -4224,7 +4224,11 @@ LabelledStatement
Label
# NOTE: `:label` instead of `label:` to not clash with implicit object literal
Colon:colon Identifier:id Whitespace:w ->
return [ id, colon, w ]
return {
type: "Label",
name: id.name,
children: [ id, colon, w ]
}

# Argument to break/continue, which can include colon or not in input,
# but should not have colon in output
Expand Down Expand Up @@ -5049,10 +5053,18 @@ KeywordStatement
# https://262.ecma-international.org/#prod-BreakStatement
# NOTE: Also allow `break :label` for symmetry
Break ( _ LabelIdentifier )? ( _ With MaybeNestedExtendedExpression )? ->
const children = [ $1 ]
if ($2) children.push($2)
if ($3) children.push({
type: "Error",
subtype: "Warning",
message: "'break with' outside of loop that returns a value",
})
return {
type: "BreakStatement",
label: $2?.[1],
with: $3?.[2],
children: [ $1, $2 ],
children,
}

# NOTE: `continue switch` for fallthrough
Expand All @@ -5066,10 +5078,18 @@ KeywordStatement
# https://262.ecma-international.org/#prod-ContinueStatement
# NOTE: Also allow `continue :label` for symmetry
Continue ( _ LabelIdentifier )? ( _ With MaybeNestedExtendedExpression )? ->
const children = [ $1 ]
if ($2) children.push($2)
if ($3) children.push({
type: "Error",
subtype: "Warning",
message: "'continue with' outside of loop that returns a value",
})
return {
type: "ContinueStatement",
label: $2?.[1],
with: $3?.[2],
children: [ $1, $2 ],
children,
}

DebuggerStatement
Expand Down
37 changes: 26 additions & 11 deletions source/parser/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import type {
BlockStatement
BreakStatement
CallExpression
ContinueStatement
Declaration
ForStatement
FunctionNode
IterationExpression
IterationFamily
IterationStatement
LabeledStatement
LabelledStatement
Parameter
ParametersNode
StatementTuple
SwitchStatement
TypeIdentifierNode
TypeNode
Whitespace
Expand Down Expand Up @@ -269,7 +268,7 @@ function assignResults(node: StatementTuple[] | ASTNode, collect: (node: ASTNode
outer := exp
{type} .= exp
if type is "LabelledStatement"
exp = (exp as LabeledStatement).statement
exp = exp.statement
{type} = exp

switch type
Expand Down Expand Up @@ -370,7 +369,7 @@ function insertReturn(node: ASTNode, outerNode: ASTNode = node): void
outer := exp
{type} .= exp
if type is "LabelledStatement"
exp = (exp as LabeledStatement).statement
exp = exp.statement
{type} = exp

switch type
Expand Down Expand Up @@ -447,16 +446,13 @@ function insertSwitchReturns(exp): void
exp.caseBlock.clauses.forEach (clause) =>
insertReturn clause

// Process `break with` and `continue with` within a loop
// Process `break with` and `continue with` within a loop statement
// that already has a resultsRef attribute.
// Returns whether the resultsRef might be modified, so should use let.
function processBreakContinueWith(statement: IterationStatement | ForStatement): boolean
changed .= false
for control of gatherRecursive(statement.block,
(s: ASTNodeObject): s is BreakStatement | ContinueStatement =>
s.type is like "BreakStatement", "ContinueStatement"
(s: ASTNodeObject) =>
isFunction(s) or s.type is "IterationStatement"
for control of gatherRecursiveWithinFunction(statement.block,
.type is "BreakStatement" or .type is "ContinueStatement"
)
function controlName: string
switch control.type
Expand All @@ -468,6 +464,21 @@ function processBreakContinueWith(statement: IterationStatement | ForStatement):
// break with <expr> overwrites the results of the loop
// continue with <expr> appends to the results of the loop
if control.with
if control.label
continue unless statement.parent is like {
type: "LabelledStatement"
label: { name: ^control.label.name }
}
else
// Verify there wasn't another loop or switch in between
{ancestor} := findAncestor control,
(s: ASTNodeObject): s is IterationStatement | ForStatement | SwitchStatement => (or)
s is statement
s.type is "IterationStatement"
s.type is "ForStatement"
s.type is "SwitchStatement" and control.type is "BreakStatement"
continue unless ancestor is statement

control.children.unshift
if control.type is "BreakStatement"
changed = true
Expand All @@ -476,6 +487,10 @@ function processBreakContinueWith(statement: IterationStatement | ForStatement):
[statement.resultsRef, '.push(', trimFirstSpace(control.with), ');']
updateParentPointers control.with, control

// Remove warning associated with break/continue with
i := control.children.findIndex ?.type is "Error"
control.children.splice i, 1 if i >= 0

// Brace containing block now that it has multiple statements
block := control.parent
unless block?.type is "BlockStatement"
Expand Down
18 changes: 14 additions & 4 deletions source/parser/types.civet
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type StatementNode =
| ForStatement
| IfStatement
| IterationStatement
| LabeledStatement
| LabelledStatement
| ReturnStatement
| SwitchStatement
| ThrowStatement
Expand Down Expand Up @@ -79,6 +79,7 @@ export type OtherNode =
| FinallyClause
| Index
| Initializer
| Label
| ObjectBindingPattern
| Parameter
| ParametersNode
Expand Down Expand Up @@ -111,6 +112,7 @@ export type ASTWrapper =

export type ASTError =
type: "Error"
subtype?: "Warning" | "Error"
message: string
token?: undefined
filename?: string
Expand Down Expand Up @@ -288,6 +290,7 @@ export type BreakStatement
children: Children
parent?: Parent
with: ASTNode?
label: Label?

export type ComptimeStatement
type: "ComptimeStatement"
Expand All @@ -307,6 +310,7 @@ export type ContinueStatement
parent?: Parent
special?: "switch"
with: ASTNode?
label: Label?

export type DoStatement
type: "DoStatement"
Expand Down Expand Up @@ -359,13 +363,19 @@ export type DefaultClause
children: Children
block: BlockStatement

export type LabeledStatement
type: "LabeledStatement"
label: ASTNode
export type LabelledStatement
type: "LabelledStatement"
label: Label
statement: ASTNodeBase
children: Children
parent?: Parent

export type Label
type: "Label"
children: Children
parent?: Parent
name: string

export type AccessStart
type: "AccessStart"
children: Children
Expand Down
2 changes: 1 addition & 1 deletion source/parser/util.civet
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ statementTypes := new Set [
"ForStatement"
"IfStatement"
"IterationStatement"
"LabeledStatement"
"LabelledStatement"
"ReturnStatement"
"SwitchStatement"
"ThrowStatement"
Expand Down
69 changes: 65 additions & 4 deletions test/for.civet
Original file line number Diff line number Diff line change
Expand Up @@ -782,15 +782,76 @@ describe "for", ->
};const values =results
"""

testCase """
throws """
break/continue with outside expression context
---
for x of y
continue with null unless x?
break with x
---
for (const x of y) {
if (!(x != null)) { continue }
break
ParseErrors: unknown:2:11 'continue with' outside of loop that returns a value
unknown:3:8 'break with' outside of loop that returns a value
"""

throws """
break with inside switch
---
=>
for x of y
switch x
when 1
break with x
---
ParseErrors: unknown:5:14 'break with' outside of loop that returns a value
"""

testCase """
continue with inside switch
---
=>
for x of y
switch x
when 1
continue with 0
x
---
() => {
const results=[];for (const x of y) {
switch(x) {
case 1: { {
results.push(0);continue}
}
}
results.push(x)
};return results;
}
"""

testCase """
labelled break/continue with
---
function f(arrays)
:outer for array of arrays
:inner for item of array
break :outer with [] if item is "ABORT"
continue outer with [] if item is "SKIP"
break inner with [] if item is "abort"
continue :outer with [] if item is "skip"
break with [] if item is "abort"
continue with [] if item is "skip"
item * item
---
function f(arrays) {
let results;results=[];outer: for (const array of arrays) {
let results1;results1=[];inner: for (const item of array) {
if (item === "ABORT") { results = [];break outer }
if (item === "SKIP") { results.push([]);continue outer }
if (item === "abort") { results1 = [];break inner }
if (item === "skip") { results.push([]);continue outer }
if (item === "abort") { results1 = [];break }
if (item === "skip") { results1.push([]);continue }
results1.push(item * item)
}results.push(results1)
};return results;
}
"""

0 comments on commit a67393f

Please sign in to comment.