no kwargs

This commit is contained in:
Chris Wanstrath 2025-10-05 22:34:07 -07:00
parent d8e97c0f20
commit 078fc37a02
3 changed files with 27 additions and 27 deletions

View File

@ -19,7 +19,7 @@ It's where Shrimp live.
- Dictionary operations (MAKE_DICT, DICT_GET, DICT_SET, DICT_HAS)
- Function operations (MAKE_FUNCTION, CALL, TAIL_CALL, RETURN) with parameter binding
- Variadic functions with positional rest parameters (`...rest`)
- Named arguments (kwargs) that collect unmatched named args into a dict (`@named`)
- Named arguments (named) that collect unmatched named args into a dict (`@named`)
- Mixed positional and named arguments with proper priority binding
- Tail call optimization with unbounded recursion (10,000+ iterations without stack overflow)
- Exception handling (PUSH_TRY, PUSH_FINALLY, POP_TRY, THROW) with nested try/finally blocks and call stack unwinding

View File

@ -386,7 +386,7 @@ export class VM {
// Check if named argument was provided for this param
if (namedArgs.has(paramName)) {
this.scope.set(paramName, namedArgs.get(paramName)!)
namedArgs.delete(paramName) // Remove from named args so it won't go to kwargs
namedArgs.delete(paramName) // Remove from named args so it won't go to named
} else if (positionalArgs[i] !== undefined) {
this.scope.set(paramName, positionalArgs[i]!)
} else if (fn.defaults[paramName] !== undefined) {
@ -410,11 +410,11 @@ export class VM {
// Handle named parameter (collect remaining named args that didn't match params)
if (fn.named) {
const namedParamName = fn.params[fn.params.length - 1]!
const kwargsDict = new Map<string, Value>()
const namedDict = new Map<string, Value>()
for (const [key, value] of namedArgs) {
kwargsDict.set(key, value)
namedDict.set(key, value)
}
this.scope.set(namedParamName, { type: 'dict', value: kwargsDict })
this.scope.set(namedParamName, { type: 'dict', value: namedDict })
}
// subtract 1 because pc was incremented
@ -488,11 +488,11 @@ export class VM {
// Handle named parameter
if (tailFn.named) {
const namedParamName = tailFn.params[tailFn.params.length - 1]!
const kwargsDict = new Map<string, Value>()
const namedDict = new Map<string, Value>()
for (const [key, value] of tailNamedArgs) {
kwargsDict.set(key, value)
namedDict.set(key, value)
}
this.scope.set(namedParamName, { type: 'dict', value: kwargsDict })
this.scope.set(namedParamName, { type: 'dict', value: namedDict })
}
// subtract 1 because PC was incremented

View File

@ -201,7 +201,7 @@ test("TAIL_CALL - variadic function", async () => {
test("CALL - named args function with no fixed params", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (@kwargs) #9
MAKE_FUNCTION (@named) #9
PUSH "name"
PUSH "Bob"
PUSH "age"
@ -210,7 +210,7 @@ test("CALL - named args function with no fixed params", async () => {
PUSH 2
CALL
HALT
LOAD kwargs
LOAD named
RETURN
`)
@ -224,7 +224,7 @@ test("CALL - named args function with no fixed params", async () => {
test("CALL - named args function with one fixed param", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x @kwargs) #8
MAKE_FUNCTION (x @named) #8
PUSH 10
PUSH "name"
PUSH "Alice"
@ -232,7 +232,7 @@ test("CALL - named args function with one fixed param", async () => {
PUSH 1
CALL
HALT
LOAD kwargs
LOAD named
RETURN
`)
@ -244,9 +244,9 @@ test("CALL - named args function with one fixed param", async () => {
}
})
test("CALL - named args with matching param name should bind to param not kwargs", async () => {
test("CALL - named args with matching param name should bind to param not named", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (name @kwargs) #8
MAKE_FUNCTION (name @named) #8
PUSH "Bob"
PUSH "age"
PUSH 50
@ -259,13 +259,13 @@ test("CALL - named args with matching param name should bind to param not kwargs
`)
const result = await new VM(bytecode).run()
// name should be bound as regular param, not collected in kwargs
// name should be bound as regular param, not collected in named
expect(result).toEqual({ type: 'string', value: 'Bob' })
})
test("CALL - named args that match param names should not be in kwargs", async () => {
test("CALL - named args that match param names should not be in named", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (name age @kwargs) #9
MAKE_FUNCTION (name age @named) #9
PUSH "name"
PUSH "Bob"
PUSH "city"
@ -274,14 +274,14 @@ test("CALL - named args that match param names should not be in kwargs", async (
PUSH 2
CALL
HALT
LOAD kwargs
LOAD named
RETURN
`)
const result = await new VM(bytecode).run()
expect(result.type).toBe('dict')
if (result.type === 'dict') {
// Only city should be in kwargs, name should be bound to param
// Only city should be in named, name should be bound to param
expect(result.value.get('city')).toEqual({ type: 'string', value: 'NYC' })
expect(result.value.has('name')).toBe(false)
expect(result.value.size).toBe(1)
@ -290,7 +290,7 @@ test("CALL - named args that match param names should not be in kwargs", async (
test("CALL - mixed variadic and named args", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x ...rest @kwargs) #10
MAKE_FUNCTION (x ...rest @named) #10
PUSH 1
PUSH 2
PUSH 3
@ -315,9 +315,9 @@ test("CALL - mixed variadic and named args", async () => {
})
})
test("CALL - mixed variadic and named args, check kwargs", async () => {
test("CALL - mixed variadic and named args, check named", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x ...rest @kwargs) #10
MAKE_FUNCTION (x ...rest @named) #10
PUSH 1
PUSH 2
PUSH 3
@ -327,7 +327,7 @@ test("CALL - mixed variadic and named args, check kwargs", async () => {
PUSH 1
CALL
HALT
LOAD kwargs
LOAD named
RETURN
`)
@ -340,18 +340,18 @@ test("CALL - mixed variadic and named args, check kwargs", async () => {
test("CALL - named args with no extra named args", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x @kwargs) #6
MAKE_FUNCTION (x @named) #6
PUSH 10
PUSH 1
PUSH 0
CALL
HALT
LOAD kwargs
LOAD named
RETURN
`)
const result = await new VM(bytecode).run()
// kwargs should be empty dict
// named should be empty dict
expect(result.type).toBe('dict')
if (result.type === 'dict') {
expect(result.value.size).toBe(0)
@ -360,7 +360,7 @@ test("CALL - named args with no extra named args", async () => {
test("CALL - named args with defaults on fixed params", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x=5 @kwargs) #7
MAKE_FUNCTION (x=5 @named) #7
PUSH "name"
PUSH "Alice"
PUSH 0