diff --git a/civet.dev/reference.md b/civet.dev/reference.md index 9c85ba35..57f5a6ae 100644 --- a/civet.dev/reference.md +++ b/civet.dev/reference.md @@ -1818,7 +1818,7 @@ for key: keyof typeof object, value in object ### Loop Expressions -If needed, loops automatically assemble an Array of the last value +If needed, loops automatically assemble an array of the last value within the body of the loop for each completed iteration. @@ -1850,6 +1850,17 @@ so you cannot use `return` inside such a loop, nor can you `break` or `continue` any outer loop. ::: +You can also accumulate multiple items and/or spreads: + + +function flatJoin(list: T[][], sep: T): T[] + for sublist, i of list + if i + sep, ...sublist + else + ...sublist + + If you don't specify a body, `for` loops list the item being iterated over: diff --git a/source/parser.hera b/source/parser.hera index 264952ac..1bd769ee 100644 --- a/source/parser.hera +++ b/source/parser.hera @@ -326,6 +326,30 @@ CommaExpression if($2.length == 0) return $1 return $0 +# Variation on CommaExpression that allows ... spreads, +# which we allow in statements for the sake of expressionized iterations +CommaExpressionSpread + AssignmentExpressionSpread ( CommaDelimiter AssignmentExpressionSpread )* -> + if($2.length == 0) return $1 + return $0 + +AssignmentExpressionSpread + _? DotDotDot AssignmentExpression:expression -> + return { + type: "SpreadElement", + children: $0, + expression, + names: expression.names, + } + AssignmentExpression:expression ( _? DotDotDot )? -> + if (!$2) return $1 + return { + type: "SpreadElement", + children: [ ...$2, $1 ], + expression, + names: expression.names, + } + # https://262.ecma-international.org/#prod-Arguments Arguments ExplicitArguments @@ -5397,7 +5421,9 @@ CommaExpressionStatement # NOTE: semi-colons are being handled elsewhere # NOTE: Shouldn't need negative lookahead if shadowed in the proper order # NOTE: CommaExpression allows , operator - CommaExpression -> + # NOTE: CommaExpressionSpread allows ... spreads, + # which are useful within expressionized iterations + CommaExpressionSpread -> // Wrap object literal with parens to disambiguate from block statements. // Also wrap nameless functions from `->` expressions with parens // as needed in JS. diff --git a/source/parser/lib.civet b/source/parser/lib.civet index 1b576cbe..77557908 100644 --- a/source/parser/lib.civet +++ b/source/parser/lib.civet @@ -754,7 +754,7 @@ function makeExpressionStatement(expression: ASTNode): ASTNode if Array.isArray(expression) and expression[1]?[0]?[0]?[1]?.token is "," // CommaExpression [ makeExpressionStatement expression[0] - expression[1].map ([comma, exp]) => // CommaDelimiter AssignmentExpression + expression[1].map [comma, exp] => // CommaDelimiter AssignmentExpression [comma, makeExpressionStatement exp] ] else if expression?.type is "ObjectExpression" or diff --git a/test/for.civet b/test/for.civet index 56ddc3b8..bf63d6ba 100644 --- a/test/for.civet +++ b/test/for.civet @@ -1527,3 +1527,58 @@ describe "for", -> --- if (!(()=>{let results=true;for (const f of facesHit) {results = false; break}return results})()) { return false } """ + + describe "spreads", -> + testCase """ + comma in body + --- + function concatPairs(pairs) + for [x, y] of pairs + x, y + --- + function concatPairs(pairs) { + const results=[];for (const [x, y] of pairs) { + results.push(x, y) + };return results; + } + """ + + testCase """ + spread in body + --- + function concat(arrays) + for array of arrays + ...array + --- + function concat(arrays) { + const results=[];for (const array of arrays) { + results.push(...array) + };return results; + } + """ + + testCase """ + reverse spread in body + --- + function concat(arrays) + for array of arrays + array ... + --- + function concat(arrays) { + const results=[];for (const array of arrays) { + results.push( ...array) + };return results; + } + """ + + testCase """ + multiple spreads in body + --- + result := + for item of items + item.pre, ...item.mid, item.post, ... item.tail + --- + const results=[];for (const item of items) { + results.push(item.pre, ...item.mid, item.post, ... item.tail) + };const result =results + """