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

break/continue [label] with and better error handling #1397

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprisingly Labeled is preferred in American English https://www.grammarly.com/blog/labeled-labelled/

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;
}
"""
Loading