Compare commits

..

No commits in common. "main" and "editor-fixes" have entirely different histories.

3 changed files with 42 additions and 99 deletions

View File

@ -57,7 +57,6 @@ export class Compiler {
loopLabelCount = 0
bytecode: Bytecode
pipeCounter = 0
pipeVarStack: string[] = [] // Stack of pipe variable names for nested pipes
constructor(
public input: string,
@ -734,66 +733,56 @@ 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 pipeVarName = `_pipe_${this.pipeCounter}`
this.pipeVarStack.push(pipeVarName)
const pipeValName = `_pipe_value_${this.pipeCounter}`
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', '_'])
instructions.push(['STORE', pipeValName])
const isFunctionCall =
pipeReceiver.type.is('FunctionCall') || pipeReceiver.type.is('FunctionCallOrIdentifier')
const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts(
pipeReceiver,
input,
)
if (isFunctionCall) {
// Function call receiver: check for explicit _ usage to determine arg handling
const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts(
pipeReceiver,
input,
)
instructions.push(...this.#compileNode(identifierNode, input))
instructions.push(...this.#compileNode(identifierNode, input))
const isUnderscoreInPositionalArgs = positionalArgs.some((arg) =>
arg.type.is('Underscore'),
)
const isUnderscoreInNamedArgs = namedArgs.some((arg) => {
const { valueNode } = getNamedArgParts(arg, input)
return valueNode.type.is('Underscore')
})
const isUnderscoreInPositionalArgs = positionalArgs.some((arg) =>
arg.type.is('Underscore'),
)
const isUnderscoreInNamedArgs = namedArgs.some((arg) => {
const { valueNode } = getNamedArgParts(arg, input)
return valueNode.type.is('Underscore')
})
const shouldPushPositionalArg = !isUnderscoreInPositionalArgs && !isUnderscoreInNamedArgs
const shouldPushPositionalArg = !isUnderscoreInPositionalArgs && !isUnderscoreInNamedArgs
// If no underscore is explicitly used, add the piped value as the first positional arg
if (shouldPushPositionalArg) {
instructions.push(['LOAD', pipeVarName])
}
positionalArgs.forEach((arg) => {
instructions.push(...this.#compileNode(arg, input))
})
namedArgs.forEach((arg) => {
const { name, valueNode } = getNamedArgParts(arg, input)
instructions.push(['PUSH', name])
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))
// If no underscore is explicitly used, add the piped value as the first positional arg
if (shouldPushPositionalArg) {
instructions.push(['LOAD', pipeValName])
}
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'])
})
this.pipeVarStack.pop()
return instructions
}
@ -880,13 +869,6 @@ 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.`,

View File

@ -117,42 +117,4 @@ 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)
})
})

View File

@ -43,8 +43,7 @@ export const globals: Record<string, any> = {
name: '',
path: '.',
},
}
: {
} : {
args: Bun.argv.slice(3),
argv: Bun.argv.slice(1),
env: process.env,
@ -54,7 +53,7 @@ export const globals: Record<string, any> = {
name: Bun.argv[2] || '(shrimp)',
path: resolve(join('.', Bun.argv[2] ?? '')),
},
},
}
// hello
echo: (...args: any[]) => {