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