add TYPE opcode
This commit is contained in:
parent
47e227f50c
commit
f439c25742
51
GUIDE.md
51
GUIDE.md
|
|
@ -186,6 +186,7 @@ CALL
|
||||||
- `POP` - Remove top
|
- `POP` - Remove top
|
||||||
- `DUP` - Duplicate top
|
- `DUP` - Duplicate top
|
||||||
- `SWAP` - Swap top two values
|
- `SWAP` - Swap top two values
|
||||||
|
- `TYPE` - Pop value, push its type as string
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
- `LOAD <name>` - Push variable value (throws if not found)
|
- `LOAD <name>` - Push variable value (throws if not found)
|
||||||
|
|
@ -434,6 +435,56 @@ BIT_USHR ; → 2147483647 (unsigned shift)
|
||||||
- Color manipulation: extract RGB components
|
- Color manipulation: extract RGB components
|
||||||
- Low-level bit manipulation for protocols or file formats
|
- Low-level bit manipulation for protocols or file formats
|
||||||
|
|
||||||
|
### Runtime Type Checking (TYPE)
|
||||||
|
Get the type of a value as a string for runtime introspection:
|
||||||
|
|
||||||
|
```
|
||||||
|
; Basic type check
|
||||||
|
PUSH 42
|
||||||
|
TYPE ; → "number"
|
||||||
|
|
||||||
|
PUSH "hello"
|
||||||
|
TYPE ; → "string"
|
||||||
|
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
TYPE ; → "array"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Type Guard Pattern** (check type before operation):
|
||||||
|
```
|
||||||
|
; Safe addition - only add if both are numbers
|
||||||
|
LOAD x
|
||||||
|
DUP
|
||||||
|
TYPE
|
||||||
|
PUSH "number"
|
||||||
|
EQ
|
||||||
|
JUMP_IF_FALSE .not_number
|
||||||
|
|
||||||
|
LOAD y
|
||||||
|
DUP
|
||||||
|
TYPE
|
||||||
|
PUSH "number"
|
||||||
|
EQ
|
||||||
|
JUMP_IF_FALSE .cleanup_not_number
|
||||||
|
|
||||||
|
ADD ; Safe to add
|
||||||
|
JUMP .end
|
||||||
|
|
||||||
|
.cleanup_not_number:
|
||||||
|
POP ; Remove y
|
||||||
|
.not_number:
|
||||||
|
POP ; Remove x
|
||||||
|
PUSH null
|
||||||
|
.end:
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common Use Cases**:
|
||||||
|
- Type validation before operations
|
||||||
|
- Polymorphic functions that handle multiple types
|
||||||
|
- Debugging and introspection
|
||||||
|
- Dynamic dispatch in DSLs
|
||||||
|
- Safe coercion with fallbacks
|
||||||
|
|
||||||
### Try-Catch
|
### Try-Catch
|
||||||
```
|
```
|
||||||
PUSH_TRY .catch
|
PUSH_TRY .catch
|
||||||
|
|
|
||||||
13
SPEC.md
13
SPEC.md
|
|
@ -143,6 +143,19 @@ type ExceptionHandler = {
|
||||||
**Effect**: Swap the top two values on the stack
|
**Effect**: Swap the top two values on the stack
|
||||||
**Stack**: [value1, value2] → [value2, value1]
|
**Stack**: [value1, value2] → [value2, value1]
|
||||||
|
|
||||||
|
#### TYPE
|
||||||
|
**Operand**: None
|
||||||
|
**Effect**: Pop value from stack, push its type as a string
|
||||||
|
**Stack**: [value] → [typeString]
|
||||||
|
|
||||||
|
Returns the type of a value as a string.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```
|
||||||
|
PUSH 42
|
||||||
|
TYPE ; Pushes "number"
|
||||||
|
```
|
||||||
|
|
||||||
### Variable Operations
|
### Variable Operations
|
||||||
|
|
||||||
#### LOAD
|
#### LOAD
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ type InstructionTuple =
|
||||||
| ["PUSH", Atom]
|
| ["PUSH", Atom]
|
||||||
| ["POP"]
|
| ["POP"]
|
||||||
| ["DUP"]
|
| ["DUP"]
|
||||||
|
| ["SWAP"]
|
||||||
|
| ["TYPE"]
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
| ["LOAD", string]
|
| ["LOAD", string]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ export enum OpCode {
|
||||||
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
|
TRY_LOAD, // operand: variable name (identifier) | stack: [] → [value] | load a variable (if found) or a string
|
||||||
|
|
||||||
|
// information
|
||||||
|
TYPE, // operand: none | stack: [a] → []
|
||||||
|
|
||||||
// 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]
|
||||||
SUB, // operand: none | stack: [a, b] → [a - b]
|
SUB, // operand: none | stack: [a, b] → [a - b]
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ const OPCODES_WITH_OPERANDS = new Set([
|
||||||
const OPCODES_WITHOUT_OPERANDS = new Set([
|
const OPCODES_WITHOUT_OPERANDS = new Set([
|
||||||
OpCode.POP,
|
OpCode.POP,
|
||||||
OpCode.DUP,
|
OpCode.DUP,
|
||||||
|
OpCode.SWAP,
|
||||||
|
OpCode.TYPE,
|
||||||
OpCode.ADD,
|
OpCode.ADD,
|
||||||
OpCode.SUB,
|
OpCode.SUB,
|
||||||
OpCode.MUL,
|
OpCode.MUL,
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,11 @@ export class VM {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OpCode.TYPE:
|
||||||
|
const value = this.stack.pop()!
|
||||||
|
this.stack.push(toValue(value.type))
|
||||||
|
break
|
||||||
|
|
||||||
case OpCode.STORE:
|
case OpCode.STORE:
|
||||||
const name = instruction.operand as string
|
const name = instruction.operand as string
|
||||||
const toStore = this.stack.pop()!
|
const toStore = this.stack.pop()!
|
||||||
|
|
|
||||||
|
|
@ -828,6 +828,171 @@ describe("HALT", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("TYPE", () => {
|
||||||
|
test("null type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", null],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'null' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("boolean type", async () => {
|
||||||
|
const bytecode1 = toBytecode([
|
||||||
|
["PUSH", true],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode1)).toEqual({ type: 'string', value: 'boolean' })
|
||||||
|
|
||||||
|
const bytecode2 = toBytecode([
|
||||||
|
["PUSH", false],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode2)).toEqual({ type: 'string', value: 'boolean' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("number type", async () => {
|
||||||
|
const bytecode1 = toBytecode([
|
||||||
|
["PUSH", 42],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode1)).toEqual({ type: 'string', value: 'number' })
|
||||||
|
|
||||||
|
const bytecode2 = toBytecode([
|
||||||
|
["PUSH", 0],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode2)).toEqual({ type: 'string', value: 'number' })
|
||||||
|
|
||||||
|
const bytecode3 = toBytecode([
|
||||||
|
["PUSH", -3.14],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode3)).toEqual({ type: 'string', value: 'number' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("string type", async () => {
|
||||||
|
const bytecode1 = toBytecode([
|
||||||
|
["PUSH", "hello"],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode1)).toEqual({ type: 'string', value: 'string' })
|
||||||
|
|
||||||
|
const bytecode2 = toBytecode([
|
||||||
|
["PUSH", ""],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode2)).toEqual({ type: 'string', value: 'string' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 3],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'array' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("empty array type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_ARRAY", 0],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'array' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "name"],
|
||||||
|
["PUSH", "Alice"],
|
||||||
|
["PUSH", "age"],
|
||||||
|
["PUSH", 30],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'dict' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("empty dict type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_DICT", 0],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'dict' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("function type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_FUNCTION", ["x"], ".body"],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"],
|
||||||
|
[".body:"],
|
||||||
|
["LOAD", "x"],
|
||||||
|
["RETURN"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'function' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("native function type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["LOAD", "add"],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
const result = await run(bytecode, {
|
||||||
|
add: (a: number, b: number) => a + b
|
||||||
|
})
|
||||||
|
expect(result).toEqual({ type: 'string', value: 'native' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("regex type", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", /test/i],
|
||||||
|
["TYPE"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'regex' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("TYPE with stored result", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 100],
|
||||||
|
["TYPE"],
|
||||||
|
["STORE", "myType"],
|
||||||
|
["LOAD", "myType"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'number' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("TYPE comparison - type guards", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "hello world"],
|
||||||
|
["DUP"],
|
||||||
|
["TYPE"],
|
||||||
|
["PUSH", "string"],
|
||||||
|
["EQ"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'boolean', value: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("LOAD / STORE", () => {
|
describe("LOAD / STORE", () => {
|
||||||
test("variables", async () => {
|
test("variables", async () => {
|
||||||
const str = `
|
const str = `
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user