Compare commits
No commits in common. "146b0a28831161e03966746acce7d5fc7fe2229d" and "27857bfae8f028a04c1d8234c48a1ec7076e288c" have entirely different histories.
146b0a2883
...
27857bfae8
|
|
@ -23,14 +23,6 @@ bun test <file> # Run specific test file
|
||||||
bun test --watch # Watch mode
|
bun test --watch # Watch mode
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tools
|
|
||||||
```bash
|
|
||||||
./bin/reef <file.reef> # Execute bytecode file
|
|
||||||
./bin/validate <file.reef> # Validate bytecode
|
|
||||||
./bin/debug <file.reef> # Step-by-step debugger
|
|
||||||
./bin/repl # Interactive REPL
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
No build step required - Bun runs TypeScript directly.
|
No build step required - Bun runs TypeScript directly.
|
||||||
|
|
||||||
|
|
|
||||||
3
GUIDE.md
3
GUIDE.md
|
|
@ -195,8 +195,7 @@ CALL
|
||||||
- `DUP` - Duplicate top
|
- `DUP` - Duplicate top
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
- `LOAD <name>` - Push variable value (throws if not found)
|
- `LOAD <name>` - Push variable value
|
||||||
- `TRY_LOAD <name>` - Push variable value if found, otherwise push name as string (never throws)
|
|
||||||
- `STORE <name>` - Pop and store in variable
|
- `STORE <name>` - Pop and store in variable
|
||||||
|
|
||||||
### Arithmetic
|
### Arithmetic
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,6 @@ Run the simple debugger to see what the instructions are doing:
|
||||||
./bin/debug examples/loop.reef
|
./bin/debug examples/loop.reef
|
||||||
./bin/debug -h examples/loop.reef
|
./bin/debug -h examples/loop.reef
|
||||||
|
|
||||||
Interactive REPL for exploring instructions:
|
|
||||||
|
|
||||||
./bin/repl
|
|
||||||
|
|
||||||
Type opcodes interactively and see the stack and variables update in real-time.
|
|
||||||
|
|
||||||
Commands: `clear`, `reset`, `exit`.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Stack operations (PUSH, POP, DUP)
|
- Stack operations (PUSH, POP, DUP)
|
||||||
|
|
|
||||||
31
SPEC.md
31
SPEC.md
|
|
@ -86,11 +86,6 @@ class Scope {
|
||||||
2. If not found, recursively check parent
|
2. If not found, recursively check parent
|
||||||
3. If not found anywhere, throw error
|
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)**:
|
**Variable Assignment (STORE)**:
|
||||||
1. If variable exists in current scope, update it
|
1. If variable exists in current scope, update it
|
||||||
2. Else if variable exists in any parent scope, update it there
|
2. Else if variable exists in any parent scope, update it there
|
||||||
|
|
@ -147,32 +142,10 @@ type ExceptionHandler = {
|
||||||
**Errors**: Throws if variable not found in scope chain
|
**Errors**: Throws if variable not found in scope chain
|
||||||
|
|
||||||
#### STORE
|
#### STORE
|
||||||
**Operand**: Variable name (string)
|
**Operand**: Variable name (string)
|
||||||
**Effect**: Store top of stack into variable (following scope chain rules)
|
**Effect**: Store top of stack into variable (following scope chain rules)
|
||||||
**Stack**: [value] → []
|
**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
|
### Arithmetic Operations
|
||||||
|
|
||||||
All arithmetic operations pop two values, perform operation, push result as number.
|
All arithmetic operations pop two values, perform operation, push result as number.
|
||||||
|
|
|
||||||
298
bin/repl
298
bin/repl
|
|
@ -1,298 +0,0 @@
|
||||||
#!/usr/bin/env bun
|
|
||||||
|
|
||||||
import { VM, toBytecode } from "../src/index"
|
|
||||||
import { OpCode } from "../src/opcode"
|
|
||||||
import type { Value } from "../src/value"
|
|
||||||
import * as readline from 'node:readline'
|
|
||||||
|
|
||||||
// ANSI color codes
|
|
||||||
const colors = {
|
|
||||||
reset: '\x1b[0m',
|
|
||||||
bright: '\x1b[1m',
|
|
||||||
dim: '\x1b[2m',
|
|
||||||
cyan: '\x1b[36m',
|
|
||||||
yellow: '\x1b[33m',
|
|
||||||
green: '\x1b[32m',
|
|
||||||
red: '\x1b[31m',
|
|
||||||
blue: '\x1b[34m',
|
|
||||||
magenta: '\x1b[35m',
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatValue(value: Value): string {
|
|
||||||
if (value.type === 'string') {
|
|
||||||
return `${colors.green}"${value.value}"${colors.reset}`
|
|
||||||
} else if (value.type === 'number') {
|
|
||||||
return `${colors.cyan}${value.value}${colors.reset}`
|
|
||||||
} else if (value.type === 'boolean') {
|
|
||||||
return `${colors.yellow}${value.value}${colors.reset}`
|
|
||||||
} else if (value.type === 'null') {
|
|
||||||
return `${colors.dim}null${colors.reset}`
|
|
||||||
} else if (value.type === 'array') {
|
|
||||||
const items = value.value.map(v => formatValue(v)).join(', ')
|
|
||||||
return `${colors.blue}[${items}]${colors.reset}`
|
|
||||||
} else if (value.type === 'dict') {
|
|
||||||
const entries = Array.from(value.value.entries())
|
|
||||||
.map(([k, v]) => `${k}: ${formatValue(v)}`)
|
|
||||||
.join(', ')
|
|
||||||
return `${colors.magenta}{${entries}}${colors.reset}`
|
|
||||||
} else if (value.type === 'function') {
|
|
||||||
const params = value.params.join(', ')
|
|
||||||
return `${colors.dim}<fn(${params})>${colors.reset}`
|
|
||||||
}
|
|
||||||
return String(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatStack(stack: Value[]): string {
|
|
||||||
if (stack.length === 0) {
|
|
||||||
return ` ${colors.dim}[empty]${colors.reset}`
|
|
||||||
}
|
|
||||||
return stack.map((v, i) => ` ${colors.dim}[${i}]${colors.reset} ${formatValue(v)}`).join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatVariables(scope: any): string {
|
|
||||||
const vars: string[] = []
|
|
||||||
|
|
||||||
function collectVars(s: any, depth = 0) {
|
|
||||||
if (!s) return
|
|
||||||
|
|
||||||
const prefix = depth > 0 ? `${colors.dim}(parent)${colors.reset} ` : ''
|
|
||||||
|
|
||||||
for (const [name, value] of s.locals.entries()) {
|
|
||||||
vars.push(` ${prefix}${colors.bright}${name}${colors.reset} = ${formatValue(value)}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.parent) {
|
|
||||||
collectVars(s.parent, depth + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collectVars(scope)
|
|
||||||
|
|
||||||
if (vars.length === 0) {
|
|
||||||
return ` ${colors.dim}[no variables]${colors.reset}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return vars.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOpcodeName(op: OpCode): string {
|
|
||||||
return OpCode[op] || `UNKNOWN(${op})`
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatInstructionAt(vm: VM, pc: number): string {
|
|
||||||
const instruction = vm.instructions[pc]
|
|
||||||
if (!instruction) {
|
|
||||||
return `${colors.dim}[END]${colors.reset}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const opName = getOpcodeName(instruction.op)
|
|
||||||
let operandStr = ''
|
|
||||||
|
|
||||||
if (instruction.operand !== undefined) {
|
|
||||||
const operand = instruction.operand
|
|
||||||
|
|
||||||
// Format operand based on type
|
|
||||||
if (typeof operand === 'number') {
|
|
||||||
// Check if it's a constant reference for PUSH
|
|
||||||
if (instruction.op === OpCode.PUSH) {
|
|
||||||
const constant = vm.constants[operand]
|
|
||||||
if (constant && constant.type !== 'function_def') {
|
|
||||||
operandStr = ` ${formatValue(constant)}`
|
|
||||||
}
|
|
||||||
} else if (instruction.op === OpCode.MAKE_FUNCTION) {
|
|
||||||
const fnDef = vm.constants[operand]
|
|
||||||
if (fnDef && fnDef.type === 'function_def') {
|
|
||||||
const params = fnDef.params.join(' ')
|
|
||||||
const bodyLabel = vm.labels.get(fnDef.body)
|
|
||||||
const bodyStr = bodyLabel ? `.${bodyLabel}` : `#${fnDef.body}`
|
|
||||||
operandStr = ` ${colors.dim}(${params}) ${bodyStr}${colors.reset}`
|
|
||||||
}
|
|
||||||
} else if (instruction.op === OpCode.JUMP || instruction.op === OpCode.JUMP_IF_FALSE || instruction.op === OpCode.JUMP_IF_TRUE) {
|
|
||||||
const targetPC = pc + 1 + operand
|
|
||||||
const targetLabel = vm.labels.get(targetPC)
|
|
||||||
if (targetLabel) {
|
|
||||||
operandStr = ` ${colors.cyan}.${targetLabel}${colors.reset}`
|
|
||||||
} else {
|
|
||||||
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
|
|
||||||
}
|
|
||||||
} else if (instruction.op === OpCode.PUSH_TRY || instruction.op === OpCode.PUSH_FINALLY) {
|
|
||||||
const targetLabel = vm.labels.get(operand)
|
|
||||||
if (targetLabel) {
|
|
||||||
operandStr = ` ${colors.cyan}.${targetLabel}${colors.reset}`
|
|
||||||
} else {
|
|
||||||
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
operandStr = ` ${colors.cyan}${operand}${colors.reset}`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
operandStr = ` ${colors.yellow}${operand}${colors.reset}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${colors.bright}${opName}${colors.reset}${operandStr}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayHistory(instructions: string[], vm: VM) {
|
|
||||||
console.log(`\n${colors.bright}Instructions:${colors.reset}`)
|
|
||||||
if (instructions.length === 0) {
|
|
||||||
console.log(` ${colors.dim}[none yet]${colors.reset}`)
|
|
||||||
} else {
|
|
||||||
instructions.forEach((_, i) => {
|
|
||||||
console.log(` ${colors.dim}[${i}]${colors.reset} ${formatInstructionAt(vm, i)}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayState(vm: VM) {
|
|
||||||
console.log(`\n${colors.bright}Stack:${colors.reset}`)
|
|
||||||
console.log(formatStack(vm.stack))
|
|
||||||
|
|
||||||
console.log(`\n${colors.bright}Variables:${colors.reset}`)
|
|
||||||
console.log(formatVariables(vm.scope))
|
|
||||||
|
|
||||||
if (vm.callStack.length > 0) {
|
|
||||||
console.log(`\n${colors.bright}Call Stack Depth:${colors.reset} ${colors.cyan}${vm.callStack.length}${colors.reset}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.exceptionHandlers.length > 0) {
|
|
||||||
console.log(`${colors.bright}Exception Handlers:${colors.reset} ${colors.cyan}${vm.exceptionHandlers.length}${colors.reset}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearScreen() {
|
|
||||||
console.write('\x1b[2J\x1b[H')
|
|
||||||
}
|
|
||||||
|
|
||||||
function showWelcome() {
|
|
||||||
clearScreen()
|
|
||||||
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
|
|
||||||
console.log(`${colors.bright}🪸 ReefVM REPL${colors.reset}`)
|
|
||||||
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
|
|
||||||
console.log(`\nType instructions one at a time. Use ${colors.bright}Tab${colors.reset} for autocomplete.`)
|
|
||||||
console.log(`\nCommands:`)
|
|
||||||
console.log(` ${colors.bright}clear${colors.reset} - Clear screen and reset state`)
|
|
||||||
console.log(` ${colors.bright}reset${colors.reset} - Reset VM state (keep history visible)`)
|
|
||||||
console.log(` ${colors.bright}exit${colors.reset} - Quit REPL`)
|
|
||||||
console.log(`\nExamples:`)
|
|
||||||
console.log(` ${colors.cyan}PUSH 42${colors.reset}`)
|
|
||||||
console.log(` ${colors.cyan}PUSH 10${colors.reset}`)
|
|
||||||
console.log(` ${colors.cyan}ADD${colors.reset}`)
|
|
||||||
console.log(` ${colors.cyan}STORE result${colors.reset}`)
|
|
||||||
console.log()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function repl() {
|
|
||||||
// Get all opcode names for autocomplete
|
|
||||||
const opcodes = Object.keys(OpCode)
|
|
||||||
.filter(key => isNaN(Number(key))) // Filter out numeric keys (enum values)
|
|
||||||
|
|
||||||
const commands = ['clear', 'reset', 'exit', 'quit']
|
|
||||||
const completions = [...opcodes, ...commands]
|
|
||||||
|
|
||||||
// Autocomplete function
|
|
||||||
function completer(line: string) {
|
|
||||||
const hits = completions.filter(c => c.toLowerCase().startsWith(line.toLowerCase()))
|
|
||||||
return [hits.length ? hits : completions, line]
|
|
||||||
}
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout,
|
|
||||||
prompt: `${colors.green}reef>${colors.reset} `,
|
|
||||||
completer,
|
|
||||||
})
|
|
||||||
|
|
||||||
let instructions: string[] = []
|
|
||||||
let vm: VM | null = null
|
|
||||||
|
|
||||||
showWelcome()
|
|
||||||
|
|
||||||
rl.prompt()
|
|
||||||
|
|
||||||
rl.on('line', async (line: string) => {
|
|
||||||
let trimmed = line.trim()
|
|
||||||
|
|
||||||
// Handle empty lines
|
|
||||||
if (!trimmed) {
|
|
||||||
rl.prompt()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle commands
|
|
||||||
if (trimmed === 'exit' || trimmed === 'quit') {
|
|
||||||
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmed === 'clear') {
|
|
||||||
instructions = []
|
|
||||||
vm = null
|
|
||||||
showWelcome()
|
|
||||||
rl.prompt()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmed === 'reset') {
|
|
||||||
instructions = []
|
|
||||||
vm = null
|
|
||||||
console.log(`\n${colors.yellow}VM reset${colors.reset}`)
|
|
||||||
rl.prompt()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowercase -> uppercase (cuz we're lazy)
|
|
||||||
if (/^[a-z]\s*/.test(trimmed)) {
|
|
||||||
const parts = trimmed.split(' ')
|
|
||||||
trimmed = [parts[0].toUpperCase(), ...parts.slice(1)].join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add instruction and execute
|
|
||||||
instructions.push(trimmed)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Rebuild bytecode with all instructions
|
|
||||||
const bytecodeStr = instructions.join('\n')
|
|
||||||
const bytecode = toBytecode(bytecodeStr)
|
|
||||||
vm = new VM(bytecode)
|
|
||||||
|
|
||||||
// Execute all instructions
|
|
||||||
while (!vm.stopped && vm.pc < vm.instructions.length) {
|
|
||||||
const instruction = vm.instructions[vm.pc]!
|
|
||||||
await vm.execute(instruction)
|
|
||||||
vm.pc++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display current state
|
|
||||||
clearScreen()
|
|
||||||
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
|
|
||||||
console.log(`${colors.bright}🪸 ReefVM REPL${colors.reset}`)
|
|
||||||
console.log(`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`)
|
|
||||||
|
|
||||||
displayHistory(instructions, vm)
|
|
||||||
displayState(vm)
|
|
||||||
|
|
||||||
console.log(`\n${colors.dim}${'─'.repeat(63)}${colors.reset}`)
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(`\n${colors.red}Error: ${error.message}${colors.reset}`)
|
|
||||||
// Remove the failed instruction
|
|
||||||
instructions.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
rl.prompt()
|
|
||||||
})
|
|
||||||
|
|
||||||
rl.on('close', () => {
|
|
||||||
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle Ctrl+C gracefully
|
|
||||||
rl.on('SIGINT', () => {
|
|
||||||
console.log(`\n${colors.yellow}Use 'exit' to quit${colors.reset}`)
|
|
||||||
rl.prompt()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main
|
|
||||||
await repl()
|
|
||||||
|
|
@ -5,9 +5,8 @@ export enum OpCode {
|
||||||
DUP, // operand: none | stack: [value] → [value, value]
|
DUP, // operand: none | stack: [value] → [value, value]
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
LOAD, // operand: variable name (identifier) | stack: [] → [value]
|
LOAD, // operand: variable name (identifier) | stack: [] → [value]
|
||||||
STORE, // 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)
|
// math (coerce to number, pop 2, push result)
|
||||||
ADD, // operand: none | stack: [a, b] → [a + b]
|
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
|
this.stopped = true
|
||||||
break
|
break
|
||||||
|
|
||||||
case OpCode.LOAD: {
|
case OpCode.LOAD:
|
||||||
const varName = instruction.operand as string
|
const varName = instruction.operand as string
|
||||||
const value = this.scope.get(varName)
|
const value = this.scope.get(varName)
|
||||||
|
|
||||||
|
|
@ -125,19 +125,6 @@ export class VM {
|
||||||
|
|
||||||
this.stack.push(value)
|
this.stack.push(value)
|
||||||
break
|
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:
|
case OpCode.STORE:
|
||||||
const name = instruction.operand as string
|
const name = instruction.operand as string
|
||||||
|
|
|
||||||
|
|
@ -327,151 +327,6 @@ test("STORE and LOAD - multiple variables", async () => {
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 30 })
|
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 () => {
|
test("JUMP - relative jump forward", async () => {
|
||||||
const str = `
|
const str = `
|
||||||
PUSH 1
|
PUSH 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user