diff --git a/civet.dev/reference.md b/civet.dev/reference.md
index 43d9ce77..bb589b48 100644
--- a/civet.dev/reference.md
+++ b/civet.dev/reference.md
@@ -1744,6 +1744,16 @@ found :=
process item
+
+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
+
+
## Other Blocks
### Try Blocks
diff --git a/source/parser.hera b/source/parser.hera
index 7f502bb3..d69a76f8 100644
--- a/source/parser.hera
+++ b/source/parser.hera
@@ -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
@@ -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
@@ -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
diff --git a/source/parser/function.civet b/source/parser/function.civet
index a0e3f736..51385d9e 100644
--- a/source/parser/function.civet
+++ b/source/parser/function.civet
@@ -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
@@ -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
@@ -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
@@ -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
@@ -468,6 +464,21 @@ function processBreakContinueWith(statement: IterationStatement | ForStatement):
// break with overwrites the results of the loop
// continue with 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
@@ -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"
diff --git a/source/parser/types.civet b/source/parser/types.civet
index f5f1250e..56bfdae4 100644
--- a/source/parser/types.civet
+++ b/source/parser/types.civet
@@ -24,7 +24,7 @@ export type StatementNode =
| ForStatement
| IfStatement
| IterationStatement
- | LabeledStatement
+ | LabelledStatement
| ReturnStatement
| SwitchStatement
| ThrowStatement
@@ -79,6 +79,7 @@ export type OtherNode =
| FinallyClause
| Index
| Initializer
+ | Label
| ObjectBindingPattern
| Parameter
| ParametersNode
@@ -111,6 +112,7 @@ export type ASTWrapper =
export type ASTError =
type: "Error"
+ subtype?: "Warning" | "Error"
message: string
token?: undefined
filename?: string
@@ -288,6 +290,7 @@ export type BreakStatement
children: Children
parent?: Parent
with: ASTNode?
+ label: Label?
export type ComptimeStatement
type: "ComptimeStatement"
@@ -307,6 +310,7 @@ export type ContinueStatement
parent?: Parent
special?: "switch"
with: ASTNode?
+ label: Label?
export type DoStatement
type: "DoStatement"
@@ -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
diff --git a/source/parser/util.civet b/source/parser/util.civet
index 7d454e85..911110b3 100644
--- a/source/parser/util.civet
+++ b/source/parser/util.civet
@@ -128,7 +128,7 @@ statementTypes := new Set [
"ForStatement"
"IfStatement"
"IterationStatement"
- "LabeledStatement"
+ "LabelledStatement"
"ReturnStatement"
"SwitchStatement"
"ThrowStatement"
diff --git a/test/for.civet b/test/for.civet
index 8fe53dfc..2b86b013 100644
--- a/test/for.civet
+++ b/test/for.civet
@@ -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;
}
"""