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) - Dictionary operations (MAKE_DICT, DICT_GET, DICT_SET, DICT_HAS)
- Function operations (MAKE_FUNCTION, CALL, TAIL_CALL, RETURN) with parameter binding - Function operations (MAKE_FUNCTION, CALL, TAIL_CALL, RETURN) with parameter binding
- Variadic functions with positional rest parameters (`...rest`) - 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 - Mixed positional and named arguments with proper priority binding
- Tail call optimization with unbounded recursion (10,000+ iterations without stack overflow) - 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 - 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 // Check if named argument was provided for this param
if (namedArgs.has(paramName)) { if (namedArgs.has(paramName)) {
this.scope.set(paramName, namedArgs.get(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) { } else if (positionalArgs[i] !== undefined) {
this.scope.set(paramName, positionalArgs[i]!) this.scope.set(paramName, positionalArgs[i]!)
} else if (fn.defaults[paramName] !== undefined) { } 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) // Handle named parameter (collect remaining named args that didn't match params)
if (fn.named) { if (fn.named) {
const namedParamName = fn.params[fn.params.length - 1]! 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) { 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 // subtract 1 because pc was incremented
@ -488,11 +488,11 @@ export class VM {
// Handle named parameter // Handle named parameter
if (tailFn.named) { if (tailFn.named) {
const namedParamName = tailFn.params[tailFn.params.length - 1]! 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) { 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 // 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 () => { test("CALL - named args function with no fixed params", async () => {
const bytecode = toBytecode(` const bytecode = toBytecode(`
MAKE_FUNCTION (@kwargs) #9 MAKE_FUNCTION (@named) #9
PUSH "name" PUSH "name"
PUSH "Bob" PUSH "Bob"
PUSH "age" PUSH "age"
@ -210,7 +210,7 @@ test("CALL - named args function with no fixed params", async () => {
PUSH 2 PUSH 2
CALL CALL
HALT HALT
LOAD kwargs LOAD named
RETURN 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 () => { test("CALL - named args function with one fixed param", async () => {
const bytecode = toBytecode(` const bytecode = toBytecode(`
MAKE_FUNCTION (x @kwargs) #8 MAKE_FUNCTION (x @named) #8
PUSH 10 PUSH 10
PUSH "name" PUSH "name"
PUSH "Alice" PUSH "Alice"
@ -232,7 +232,7 @@ test("CALL - named args function with one fixed param", async () => {
PUSH 1 PUSH 1
CALL CALL
HALT HALT
LOAD kwargs LOAD named
RETURN 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(` const bytecode = toBytecode(`
MAKE_FUNCTION (name @kwargs) #8 MAKE_FUNCTION (name @named) #8
PUSH "Bob" PUSH "Bob"
PUSH "age" PUSH "age"
PUSH 50 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() 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' }) 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(` const bytecode = toBytecode(`
MAKE_FUNCTION (name age @kwargs) #9 MAKE_FUNCTION (name age @named) #9
PUSH "name" PUSH "name"
PUSH "Bob" PUSH "Bob"
PUSH "city" PUSH "city"
@ -274,14 +274,14 @@ test("CALL - named args that match param names should not be in kwargs", async (
PUSH 2 PUSH 2
CALL CALL
HALT HALT
LOAD kwargs LOAD named
RETURN RETURN
`) `)
const result = await new VM(bytecode).run() const result = await new VM(bytecode).run()
expect(result.type).toBe('dict') expect(result.type).toBe('dict')
if (result.type === '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.get('city')).toEqual({ type: 'string', value: 'NYC' })
expect(result.value.has('name')).toBe(false) expect(result.value.has('name')).toBe(false)
expect(result.value.size).toBe(1) 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 () => { test("CALL - mixed variadic and named args", async () => {
const bytecode = toBytecode(` const bytecode = toBytecode(`
MAKE_FUNCTION (x ...rest @kwargs) #10 MAKE_FUNCTION (x ...rest @named) #10
PUSH 1 PUSH 1
PUSH 2 PUSH 2
PUSH 3 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(` const bytecode = toBytecode(`
MAKE_FUNCTION (x ...rest @kwargs) #10 MAKE_FUNCTION (x ...rest @named) #10
PUSH 1 PUSH 1
PUSH 2 PUSH 2
PUSH 3 PUSH 3
@ -327,7 +327,7 @@ test("CALL - mixed variadic and named args, check kwargs", async () => {
PUSH 1 PUSH 1
CALL CALL
HALT HALT
LOAD kwargs LOAD named
RETURN 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 () => { test("CALL - named args with no extra named args", async () => {
const bytecode = toBytecode(` const bytecode = toBytecode(`
MAKE_FUNCTION (x @kwargs) #6 MAKE_FUNCTION (x @named) #6
PUSH 10 PUSH 10
PUSH 1 PUSH 1
PUSH 0 PUSH 0
CALL CALL
HALT HALT
LOAD kwargs LOAD named
RETURN RETURN
`) `)
const result = await new VM(bytecode).run() const result = await new VM(bytecode).run()
// kwargs should be empty dict // named should be empty dict
expect(result.type).toBe('dict') expect(result.type).toBe('dict')
if (result.type === 'dict') { if (result.type === 'dict') {
expect(result.value.size).toBe(0) 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 () => { test("CALL - named args with defaults on fixed params", async () => {
const bytecode = toBytecode(` const bytecode = toBytecode(`
MAKE_FUNCTION (x=5 @kwargs) #7 MAKE_FUNCTION (x=5 @named) #7
PUSH "name" PUSH "name"
PUSH "Alice" PUSH "Alice"
PUSH 0 PUSH 0