update spec

This commit is contained in:
Chris Wanstrath 2025-10-05 21:36:48 -07:00
parent 8c187a89aa
commit 0270424f9b

77
SPEC.md
View File

@ -38,8 +38,8 @@ type Value =
| { type: 'string', value: string } | { type: 'string', value: string }
| { type: 'array', value: Value[] } | { type: 'array', value: Value[] }
| { type: 'dict', value: Map<string, Value> } | { type: 'dict', value: Map<string, Value> }
| { type: 'function', params: string[], defaults: Record<string, Value>, | { type: 'function', params: string[], defaults: Record<string, number>,
body: number, parentScope: Scope, variadic: boolean, kwargs: boolean } body: number, parentScope: Scope, variadic: boolean, named: boolean }
``` ```
### Type Coercion ### Type Coercion
@ -67,7 +67,7 @@ type Instruction = {
type Constant = type Constant =
| Value | Value
| { type: 'function_def', params: string[], defaults: Record<string, number>, | { type: 'function_def', params: string[], defaults: Record<string, number>,
body: number, variadic: boolean, kwargs: boolean } body: number, variadic: boolean, named: boolean }
``` ```
## Scope Chain ## Scope Chain
@ -276,12 +276,12 @@ Adds a finally block to the current try/catch. The finally block will execute wh
**Behavior**: **Behavior**:
1. Pop exception handler 1. Pop exception handler
2. If handler has `finallyAddress`, jump there 2. Continue to next instruction
3. Otherwise continue to next instruction
**Notes**: **Notes**:
- The VM ensures finally runs when try completes normally - The VM does NOT automatically jump to finally blocks on POP_TRY
- The compiler must ensure catch blocks jump to finally when present - The compiler must explicitly generate JUMP instructions to finally blocks when the try block completes normally
- The compiler must ensure catch blocks also jump to finally when present
- Finally blocks should end with normal control flow (no special terminator needed) - Finally blocks should end with normal control flow (no special terminator needed)
#### THROW #### THROW
@ -296,8 +296,12 @@ Adds a finally block to the current try/catch. The finally block will execute wh
4. Unwind call stack to handler's depth 4. Unwind call stack to handler's depth
5. Restore handler's scope 5. Restore handler's scope
6. Push error value back onto stack 6. Push error value back onto stack
7. Jump to handler's catch address 7. If handler has `finallyAddress`, jump there; otherwise jump to `catchAddress`
8. **Note**: After catch block executes, compiler must jump to finally if present
**Notes**:
- When THROW jumps to finally (if present), the error value remains on stack for the finally block
- The compiler must structure catch/finally blocks appropriately to handle the error value
- If finally is present, the catch block is typically entered via a jump from the finally block or through explicit compiler-generated control flow
### Function Operations ### Function Operations
@ -310,8 +314,8 @@ The constant must be a `function_def` with:
- `params`: Parameter names - `params`: Parameter names
- `defaults`: Map of param names to constant indices for default values - `defaults`: Map of param names to constant indices for default values
- `body`: Instruction address of function body - `body`: Instruction address of function body
- `variadic`: If true, last param collects remaining positional args as array - `variadic`: If true, second-to-last param (if `named` is also true) or last param collects remaining positional args as array
- `kwargs`: If true, last param collects all named args as dict - `named`: If true, last param collects unmatched named args as dict
The created function captures `currentScope` as its `parentScope`. The created function captures `currentScope` as its `parentScope`.
@ -332,7 +336,7 @@ The created function captures `currentScope` as its `parentScope`.
9. Bind parameters: 9. Bind parameters:
- For regular functions: bind params by position, then by name, then defaults, then null - For regular functions: bind params by position, then by name, then defaults, then null
- For variadic functions: bind fixed params, collect rest into array - For variadic functions: bind fixed params, collect rest into array
- For kwargs functions: bind fixed params by position/name, collect unmatched named args into dict - For functions with `named: true`: bind fixed params by position/name, collect unmatched named args into dict
10. Set currentScope to new scope 10. Set currentScope to new scope
11. Jump to function body 11. Jump to function body
@ -344,8 +348,8 @@ The created function captures `currentScope` as its `parentScope`.
**Named Args Handling**: **Named Args Handling**:
- Named args that match fixed parameter names are bound to those params - Named args that match fixed parameter names are bound to those params
- Remaining named args (that don't match any fixed param) are collected into `@kwargs` dict - If the function has `named: true`, remaining named args (that don't match any fixed param) are collected into the last parameter as a dict
- This allows flexible calling: `fn(x=10, y=20, extra=30)` where `extra` goes to kwargs - This allows flexible calling: `fn(x=10, y=20, extra=30)` where `extra` goes to the named args dict
**Errors**: Throws if top of stack is not a function **Errors**: Throws if top of stack is not a function
@ -510,14 +514,30 @@ skip_body:
### Try-Catch ### Try-Catch
``` ```
PUSH_TRY catch_label PUSH_TRY #3 ; Jump to catch block (3 instructions ahead)
# try block ; try block
POP_TRY POP_TRY
JUMP end_label JUMP #2 ; Jump past catch block
catch_label: ; catch:
STORE 'errorVar' # Error is on stack STORE 'errorVar' ; Error is on stack
# catch block ; catch block
end_label: ; end:
```
### Try-Catch-Finally
```
PUSH_TRY #4 ; Jump to catch block (4 instructions ahead)
PUSH_FINALLY #7 ; Jump to finally block (7 instructions ahead)
; try block
POP_TRY
JUMP #5 ; Jump to finally
; catch:
STORE 'errorVar' ; Error is on stack
; catch block
JUMP #2 ; Jump to finally
; finally:
; finally block (executes in both cases)
; end:
``` ```
### Named Function Call ### Named Function Call
@ -601,7 +621,7 @@ All of these should throw errors:
### Function Parameter Binding ### Function Parameter Binding
- Missing positional args → use named args → use defaults → use null - Missing positional args → use named args → use defaults → use null
- Extra positional args → collected by variadic parameter or ignored - Extra positional args → collected by variadic parameter or ignored
- Extra named args → collected by kwargs parameter or ignored - Extra named args → collected by named args parameter (if `named: true`) or ignored
- Named arg matching is case-sensitive - Named arg matching is case-sensitive
### Tail Call Optimization ### Tail Call Optimization
@ -615,11 +635,12 @@ All of these should throw errors:
- CONTINUE is implemented by the compiler using JUMPs - CONTINUE is implemented by the compiler using JUMPs
### Exception Unwinding ### Exception Unwinding
- THROW unwinds call stack to handler's depth, not just to handler - THROW unwinds call stack to handler's depth
- Exception handlers form a stack (nested try blocks) - Exception handlers form a stack (nested try blocks)
- Error value on stack is available in catch block via STORE - Error value on stack is available in catch/finally blocks
- Finally blocks always execute, even if there's a return/break in try or catch - When THROW occurs and handler has finallyAddress, VM jumps to finally first
- Finally executes after try (if no exception) or after catch (if exception) - Compiler is responsible for structuring control flow so finally executes in all cases
- Finally typically executes after try (if no exception) or after catch (if exception), but control flow is compiler-managed
## VM Initialization ## VM Initialization
@ -643,7 +664,7 @@ const result = await vm.execute()
6. **Break/continue** (nested functions, iterator pattern) 6. **Break/continue** (nested functions, iterator pattern)
7. **Closures** (capturing variables, multiple nesting levels) 7. **Closures** (capturing variables, multiple nesting levels)
8. **Tail calls** (self-recursive, mutual recursion) 8. **Tail calls** (self-recursive, mutual recursion)
9. **Parameter binding** (positional, named, defaults, variadic, kwargs, combinations) 9. **Parameter binding** (positional, named, defaults, variadic, named args collection, combinations)
10. **Array/dict operations** (creation, access, mutation) 10. **Array/dict operations** (creation, access, mutation)
11. **Error conditions** (all error cases listed above) 11. **Error conditions** (all error cases listed above)
12. **Edge cases** (empty stack, null values, shadowing, etc.) 12. **Edge cases** (empty stack, null values, shadowing, etc.)
@ -655,7 +676,7 @@ const result = await vm.execute()
3. **Closure examples** (counters, adder factories) 3. **Closure examples** (counters, adder factories)
4. **Exception examples** (try/catch/throw chains) 4. **Exception examples** (try/catch/throw chains)
5. **Complex scope** (deeply nested functions) 5. **Complex scope** (deeply nested functions)
6. **Mixed features** (variadic + defaults + kwargs) 6. **Mixed features** (variadic + defaults + named args)
### Property-Based Tests Should Cover ### Property-Based Tests Should Cover