TRY_LOAD opcode
This commit is contained in:
parent
27857bfae8
commit
fd447abea8
3
GUIDE.md
3
GUIDE.md
|
|
@ -195,7 +195,8 @@ CALL
|
|||
- `DUP` - Duplicate top
|
||||
|
||||
### Variables
|
||||
- `LOAD <name>` - Push variable value
|
||||
- `LOAD <name>` - Push variable value (throws if not found)
|
||||
- `TRY_LOAD <name>` - Push variable value if found, otherwise push name as string (never throws)
|
||||
- `STORE <name>` - Pop and store in variable
|
||||
|
||||
### Arithmetic
|
||||
|
|
|
|||
27
SPEC.md
27
SPEC.md
|
|
@ -86,6 +86,11 @@ class Scope {
|
|||
2. If not found, recursively check parent
|
||||
3. If not found anywhere, throw error
|
||||
|
||||
**Variable Resolution (TRY_LOAD)**:
|
||||
1. Check current scope's locals
|
||||
2. If not found, recursively check parent
|
||||
3. If not found anywhere, return variable name as string (no error)
|
||||
|
||||
**Variable Assignment (STORE)**:
|
||||
1. If variable exists in current scope, update it
|
||||
2. Else if variable exists in any parent scope, update it there
|
||||
|
|
@ -146,6 +151,28 @@ type ExceptionHandler = {
|
|||
**Effect**: Store top of stack into variable (following scope chain rules)
|
||||
**Stack**: [value] → []
|
||||
|
||||
#### TRY_LOAD
|
||||
**Operand**: Variable name (string)
|
||||
**Effect**: Push variable value onto stack if found, otherwise push variable name as string
|
||||
**Stack**: [] → [value | name]
|
||||
**Errors**: Never throws (unlike LOAD)
|
||||
|
||||
**Behavior**:
|
||||
1. Search for variable in scope chain (current scope and all parents)
|
||||
2. If found, push the variable's value onto stack
|
||||
3. If not found, push the variable name as a string value onto stack
|
||||
|
||||
**Use Cases**:
|
||||
- Shell-like behavior where strings don't need quotes
|
||||
|
||||
**Example**:
|
||||
```
|
||||
PUSH 42
|
||||
STORE x
|
||||
TRY_LOAD x ; Pushes 42 (variable exists)
|
||||
TRY_LOAD y ; Pushes "y" (variable doesn't exist)
|
||||
```
|
||||
|
||||
### Arithmetic Operations
|
||||
|
||||
All arithmetic operations pop two values, perform operation, push result as number.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export enum OpCode {
|
|||
// variables
|
||||
LOAD, // operand: variable name (identifier) | stack: [] → [value]
|
||||
STORE, // operand: variable name (identifier) | stack: [value] → []
|
||||
TRY_LOAD, // operand: variable name (identifier) | stack: [] → [value] | load a variable (if found) or a string
|
||||
|
||||
// math (coerce to number, pop 2, push result)
|
||||
ADD, // operand: none | stack: [a, b] → [a + b]
|
||||
|
|
|
|||
15
src/vm.ts
15
src/vm.ts
|
|
@ -116,7 +116,7 @@ export class VM {
|
|||
this.stopped = true
|
||||
break
|
||||
|
||||
case OpCode.LOAD:
|
||||
case OpCode.LOAD: {
|
||||
const varName = instruction.operand as string
|
||||
const value = this.scope.get(varName)
|
||||
|
||||
|
|
@ -125,6 +125,19 @@ export class VM {
|
|||
|
||||
this.stack.push(value)
|
||||
break
|
||||
}
|
||||
|
||||
case OpCode.TRY_LOAD: {
|
||||
const varName = instruction.operand as string
|
||||
const value = this.scope.get(varName)
|
||||
|
||||
if (value === undefined)
|
||||
this.stack.push(toValue(varName))
|
||||
else
|
||||
this.stack.push(value)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case OpCode.STORE:
|
||||
const name = instruction.operand as string
|
||||
|
|
|
|||
|
|
@ -327,6 +327,151 @@ test("STORE and LOAD - multiple variables", async () => {
|
|||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 30 })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - variable found", async () => {
|
||||
const str = `
|
||||
PUSH 100
|
||||
STORE count
|
||||
TRY_LOAD count
|
||||
`
|
||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 100 })
|
||||
|
||||
const str2 = `
|
||||
PUSH 'Bobby'
|
||||
STORE name
|
||||
TRY_LOAD name
|
||||
`
|
||||
expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'Bobby' })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - variable missing", async () => {
|
||||
const str = `
|
||||
PUSH 100
|
||||
STORE count
|
||||
TRY_LOAD count1
|
||||
`
|
||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'count1' })
|
||||
|
||||
const str2 = `
|
||||
PUSH 'Bobby'
|
||||
STORE name
|
||||
TRY_LOAD full-name
|
||||
`
|
||||
expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'full-name' })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - with different value types", async () => {
|
||||
// Array
|
||||
const str1 = `
|
||||
PUSH 1
|
||||
PUSH 2
|
||||
PUSH 3
|
||||
MAKE_ARRAY #3
|
||||
STORE arr
|
||||
TRY_LOAD arr
|
||||
`
|
||||
const result1 = await run(toBytecode(str1))
|
||||
expect(result1.type).toBe('array')
|
||||
|
||||
// Dict
|
||||
const str2 = `
|
||||
PUSH 'key'
|
||||
PUSH 'value'
|
||||
MAKE_DICT #1
|
||||
STORE dict
|
||||
TRY_LOAD dict
|
||||
`
|
||||
const result2 = await run(toBytecode(str2))
|
||||
expect(result2.type).toBe('dict')
|
||||
|
||||
// Boolean
|
||||
const str3 = `
|
||||
PUSH true
|
||||
STORE flag
|
||||
TRY_LOAD flag
|
||||
`
|
||||
expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: true })
|
||||
|
||||
// Null
|
||||
const str4 = `
|
||||
PUSH null
|
||||
STORE empty
|
||||
TRY_LOAD empty
|
||||
`
|
||||
expect(await run(toBytecode(str4))).toEqual({ type: 'null', value: null })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - in nested scope", async () => {
|
||||
// Function should be able to TRY_LOAD variable from parent scope
|
||||
const str = `
|
||||
PUSH 42
|
||||
STORE outer
|
||||
MAKE_FUNCTION () .fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
.fn:
|
||||
TRY_LOAD outer
|
||||
RETURN
|
||||
`
|
||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - missing variable in nested scope returns name", async () => {
|
||||
// If variable doesn't exist in any scope, should return name as string
|
||||
const str = `
|
||||
PUSH 42
|
||||
STORE outer
|
||||
MAKE_FUNCTION () .fn
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
HALT
|
||||
.fn:
|
||||
TRY_LOAD inner
|
||||
RETURN
|
||||
`
|
||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'inner' })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - used for conditional variable existence check", async () => {
|
||||
// Pattern: use TRY_LOAD to check if variable exists and get its value or name
|
||||
const str = `
|
||||
PUSH 100
|
||||
STORE count
|
||||
TRY_LOAD count
|
||||
PUSH 'count'
|
||||
EQ
|
||||
`
|
||||
// Variable exists, so TRY_LOAD returns 100, which != 'count'
|
||||
expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false })
|
||||
|
||||
const str2 = `
|
||||
PUSH 100
|
||||
STORE count
|
||||
TRY_LOAD missing
|
||||
PUSH 'missing'
|
||||
EQ
|
||||
`
|
||||
// Variable missing, so TRY_LOAD returns 'missing', which == 'missing'
|
||||
expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: true })
|
||||
})
|
||||
|
||||
test("TRY_LOAD - with function value", async () => {
|
||||
const str = `
|
||||
MAKE_FUNCTION () .fn
|
||||
STORE myFunc
|
||||
JUMP .skip
|
||||
.fn:
|
||||
PUSH 99
|
||||
RETURN
|
||||
.skip:
|
||||
TRY_LOAD myFunc
|
||||
`
|
||||
const result = await run(toBytecode(str))
|
||||
expect(result.type).toBe('function')
|
||||
})
|
||||
|
||||
test("JUMP - relative jump forward", async () => {
|
||||
const str = `
|
||||
PUSH 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user