diff --git a/source/parser/op.civet b/source/parser/op.civet index 3529e8e5..44fa27ce 100644 --- a/source/parser/op.civet +++ b/source/parser/op.civet @@ -6,6 +6,7 @@ import type { } from ./types.civet import { + assert makeLeftHandSideExpression replaceNode trimFirstSpace @@ -15,6 +16,10 @@ import { processPatternTest } from ./pattern-matching.civet +import { + maybeRefAssignment +} from ./ref.civet + // Binary operator precedence, from low to high // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table // NOTE: Added ^^ between || and &&, just like ^ is between | and & @@ -285,7 +290,7 @@ function expandChainedComparisons([first, binops]: [ASTNode, [ASTNode, BinaryOp, return results function processChains: void - if chains.length > 0 + if chains# > 0 // At least one relational op, so expand any existence operators `x?` first = expandExistence first for each index, k of chains @@ -297,28 +302,41 @@ function expandChainedComparisons([first, binops]: [ASTNode, [ASTNode, BinaryOp, exp := binop[3] = expandExistence binop[3] results.push first - endIndex := chains[k + 1] ?? i + 1 - results.push ...binops[start...endIndex].flat() - // TODO: add refs to ensure middle expressions are evaluated only once - // NOTE: This first gets discarded if we're in the last iteration of the chain - first = [exp] ++ binops[index + 1...endIndex] - start = endIndex + // If there's more to the chain, ref the right-hand side of this + // relation (which is the left-hand side of the next op) + if k+1 < chains# + endIndex := chains[k + 1] + rhs := + index + 1 < endIndex ? [exp] ++ binops[index + 1...endIndex] : exp + { ref, refAssignment } := maybeRefAssignment rhs + // TODO: do we need to recurse on the binary ops here? + binops[index][3] = makeLeftHandSideExpression refAssignment ?? rhs + results.push ...binops[start...index + 1].flat() + first = ref + start = endIndex + else + results.push ...binops[start...i + 1].flat() else // Advance start if there was no chain results.push first results.push ...binops[start...i + 1].flat() - start = i + 1 + start = i + 1 chains.length = 0 function expandExistence(exp: ASTNode): ASTNode // Expand existence operator like x? if existence := isExistence(exp) + { ref, refAssignment } := maybeRefAssignment existence.expression + if refAssignment? + replaceNode + existence.expression + makeLeftHandSideExpression refAssignment + existence results.push existence, " ", chainOp, " " - existence.expression + ref else exp - ; // avoid implicit return of hoisted function export { getPrecedence diff --git a/source/parser/ref.civet b/source/parser/ref.civet index 44ca1432..a06389c6 100644 --- a/source/parser/ref.civet +++ b/source/parser/ref.civet @@ -35,6 +35,7 @@ function needsRef(expression: ASTNode, base = "ref"): ASTRef | undefined case "Ref": case "Identifier": case "Literal": + case "Placeholder": return } return makeRef(base) diff --git a/source/parser/types.civet b/source/parser/types.civet index 827639f3..312aaba9 100644 --- a/source/parser/types.civet +++ b/source/parser/types.civet @@ -194,7 +194,6 @@ export type BinaryOp = (string & ) | (ChainOp & token?: never relational?: never - assoc?: never ) export type NonNullAssertion @@ -214,6 +213,7 @@ export type ChainOp type: "ChainOp" special: true prec: number + assoc: string children?: never parent?: never diff --git a/source/parser/util.civet b/source/parser/util.civet index 43a6acba..5dfcb518 100644 --- a/source/parser/util.civet +++ b/source/parser/util.civet @@ -456,8 +456,8 @@ function deepCopy(root: T): T /** * Replace this node with another, by modifying its parent's children. */ -function replaceNode(node: ASTNodeObject, newNode: ASTNode, parent?: ASTNodeParent): void - parent ??= node.parent +function replaceNode(node: ASTNode, newNode: ASTNode, parent?: ASTNodeParent): void + parent ??= (node as ASTNodeObject?)?.parent unless parent? throw new Error "replaceNode failed: node has no parent" diff --git a/test/binary-op.civet b/test/binary-op.civet index 8c5b56bc..d7952baf 100644 --- a/test/binary-op.civet +++ b/test/binary-op.civet @@ -366,7 +366,7 @@ describe "binary operations", -> --- a+b is in c*d is in e%f+g --- - (c*d).includes(a+b) && (e%f+g).includes(c*d) + let ref;(ref = c*d).includes(a+b) && (e%f+g).includes(ref) """ testCase """ diff --git a/test/chained-comparisons.civet b/test/chained-comparisons.civet index 8e6ebe53..b3bc5710 100644 --- a/test/chained-comparisons.civet +++ b/test/chained-comparisons.civet @@ -24,15 +24,15 @@ describe "chained comparisons", -> --- a + b < c + d < e + f --- - a + b < c + d && c + d < e + f + let ref;a + b < (ref = c + d) && ref < e + f """ testCase """ - higher precedence + more higher precedence --- a + b + x + y < c + d < e + f --- - a + b + x + y < c + d && c + d < e + f + let ref;a + b + x + y < (ref = c + d) && ref < e + f """ testCase """ @@ -68,9 +68,11 @@ describe "chained comparisons", -> --- (a < b) < c (a + b) < (c + d) < (e + f) + (a < b) < c --- - (a < b) < c; - (a + b) < (c + d) && (c + d) < (e + f) + (a < b) < c + let ref;(a + b) < (ref = (c + d)) && ref < (e + f); + (a < b) < c """ testCase """