Fix underscore handling in nested pipes and non-function receivers
Add pipeVarStack to track pipe variables for nested pipes, allowing _ to correctly reference the innermost piped value. Also enable piping to array literals and parenthesized expressions (e.g., `5 | [_ _ _]`).
This commit is contained in:
parent
f5043fe701
commit
248a53c887
|
|
@ -57,6 +57,7 @@ export class Compiler {
|
|||
loopLabelCount = 0
|
||||
bytecode: Bytecode
|
||||
pipeCounter = 0
|
||||
pipeVarStack: string[] = [] // Stack of pipe variable names for nested pipes
|
||||
|
||||
constructor(
|
||||
public input: string,
|
||||
|
|
@ -733,11 +734,23 @@ export class Compiler {
|
|||
const instructions: ProgramItem[] = []
|
||||
instructions.push(...this.#compileNode(pipedFunctionCall, input))
|
||||
|
||||
// Use a unique variable name for this pipe level to handle nested pipes correctly
|
||||
this.pipeCounter++
|
||||
const pipeValName = `_pipe_value_${this.pipeCounter}`
|
||||
pipeReceivers.forEach((pipeReceiver) => {
|
||||
instructions.push(['STORE', pipeValName])
|
||||
const pipeVarName = `_pipe_${this.pipeCounter}`
|
||||
this.pipeVarStack.push(pipeVarName)
|
||||
|
||||
pipeReceivers.forEach((pipeReceiver) => {
|
||||
// Store the piped value in the current pipe's variable
|
||||
// Also store as `_` for direct access in simple cases
|
||||
instructions.push(['DUP'])
|
||||
instructions.push(['STORE', pipeVarName])
|
||||
instructions.push(['STORE', '_'])
|
||||
|
||||
const isFunctionCall =
|
||||
pipeReceiver.type.is('FunctionCall') || pipeReceiver.type.is('FunctionCallOrIdentifier')
|
||||
|
||||
if (isFunctionCall) {
|
||||
// Function call receiver: check for explicit _ usage to determine arg handling
|
||||
const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts(
|
||||
pipeReceiver,
|
||||
input,
|
||||
|
|
@ -757,32 +770,30 @@ export class Compiler {
|
|||
|
||||
// If no underscore is explicitly used, add the piped value as the first positional arg
|
||||
if (shouldPushPositionalArg) {
|
||||
instructions.push(['LOAD', pipeValName])
|
||||
instructions.push(['LOAD', pipeVarName])
|
||||
}
|
||||
|
||||
positionalArgs.forEach((arg) => {
|
||||
if (arg.type.is('Underscore')) {
|
||||
instructions.push(['LOAD', pipeValName])
|
||||
} else {
|
||||
instructions.push(...this.#compileNode(arg, input))
|
||||
}
|
||||
})
|
||||
|
||||
namedArgs.forEach((arg) => {
|
||||
const { name, valueNode } = getNamedArgParts(arg, input)
|
||||
instructions.push(['PUSH', name])
|
||||
if (valueNode.type.is('Underscore')) {
|
||||
instructions.push(['LOAD', pipeValName])
|
||||
} else {
|
||||
instructions.push(...this.#compileNode(valueNode, input))
|
||||
}
|
||||
})
|
||||
|
||||
instructions.push(['PUSH', positionalArgs.length + (shouldPushPositionalArg ? 1 : 0)])
|
||||
instructions.push(['PUSH', namedArgs.length])
|
||||
instructions.push(['CALL'])
|
||||
} else {
|
||||
// Non-function-call receiver (Array, ParenExpr, etc.): compile directly
|
||||
// The `_` variable is available for use in nested expressions
|
||||
instructions.push(...this.#compileNode(pipeReceiver, input))
|
||||
}
|
||||
})
|
||||
|
||||
this.pipeVarStack.pop()
|
||||
return instructions
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +880,13 @@ export class Compiler {
|
|||
return [] // ignore comments
|
||||
}
|
||||
|
||||
case 'Underscore': {
|
||||
// _ refers to the piped value for the current (innermost) pipe
|
||||
// Use the stack to handle nested pipes correctly
|
||||
const pipeVar = this.pipeVarStack.at(-1) ?? '_'
|
||||
return [['LOAD', pipeVar]]
|
||||
}
|
||||
|
||||
default:
|
||||
throw new CompilerError(
|
||||
`Compiler doesn't know how to handle a "${node.type.name}" node.`,
|
||||
|
|
|
|||
|
|
@ -117,4 +117,42 @@ describe('pipe expressions', () => {
|
|||
test('dict literals can be piped', () => {
|
||||
expect(`[a=1 b=2 c=3] | dict.values | list.sort | str.join '-'`).toEvaluateTo('1-2-3')
|
||||
})
|
||||
|
||||
test('pipe to array literal using _ in nested expressions', () => {
|
||||
// _ should be accessible inside nested function calls within array literals
|
||||
const code = `
|
||||
double = do x: x * 2 end
|
||||
triple = do x: x * 3 end
|
||||
5 | [(double _) (triple _)]`
|
||||
expect(code).toEvaluateTo([10, 15])
|
||||
})
|
||||
|
||||
test('pipe to array literal using _ multiple times', () => {
|
||||
expect(`10 | [_ _ _]`).toEvaluateTo([10, 10, 10])
|
||||
})
|
||||
|
||||
test('pipe to parenthesized expression using _', () => {
|
||||
const code = `
|
||||
double = do x: x * 2 end
|
||||
5 | (double _)`
|
||||
expect(code).toEvaluateTo(10)
|
||||
})
|
||||
|
||||
test('pipe chain with array literal receiver', () => {
|
||||
// Pipe to array, then pipe that array to a function
|
||||
const code = `
|
||||
double = do x: x * 2 end
|
||||
5 | [(double _) _] | list.sum`
|
||||
expect(code).toEvaluateTo(15) // [10, 5] -> 15
|
||||
})
|
||||
|
||||
test('_ in deeply nested expressions within pipe', () => {
|
||||
// _ should work in nested function calls within function arguments
|
||||
const code = `
|
||||
add = do a b: a + b end
|
||||
mul = do a b: a * b end
|
||||
10 | add (mul _ 2) _`
|
||||
// add(mul(10, 2), 10) = add(20, 10) = 30
|
||||
expect(code).toEvaluateTo(30)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user