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
|
||||
- `DUP` - Duplicate top
|
||||
- `SWAP` - Swap top two values
|
||||
- `TYPE` - Pop value, push its type as string
|
||||
|
||||
### Variables
|
||||
- `LOAD <name>` - Push variable value (throws if not found)
|
||||
|
|
@ -434,6 +435,56 @@ BIT_USHR ; → 2147483647 (unsigned shift)
|
|||
- Color manipulation: extract RGB components
|
||||
- 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
|
||||
```
|
||||
PUSH_TRY .catch
|
||||
|
|
|
|||
13
SPEC.md
13
SPEC.md
|
|
@ -143,6 +143,19 @@ type ExceptionHandler = {
|
|||
**Effect**: Swap the top two values on the stack
|
||||
**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
|
||||
|
||||
#### LOAD
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ type InstructionTuple =
|
|||
| ["PUSH", Atom]
|
||||
| ["POP"]
|
||||
| ["DUP"]
|
||||
| ["SWAP"]
|
||||
| ["TYPE"]
|
||||
|
||||
// Variables
|
||||
| ["LOAD", string]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ export enum OpCode {
|
|||
STORE, // operand: variable name (identifier) | stack: [value] → []
|
||||
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)
|
||||
ADD, // 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([
|
||||
OpCode.POP,
|
||||
OpCode.DUP,
|
||||
OpCode.SWAP,
|
||||
OpCode.TYPE,
|
||||
OpCode.ADD,
|
||||
OpCode.SUB,
|
||||
OpCode.MUL,
|
||||
|
|
|
|||
|
|
@ -285,6 +285,11 @@ export class VM {
|
|||
break
|
||||
}
|
||||
|
||||
case OpCode.TYPE:
|
||||
const value = this.stack.pop()!
|
||||
this.stack.push(toValue(value.type))
|
||||
break
|
||||
|
||||
case OpCode.STORE:
|
||||
const name = instruction.operand as string
|
||||
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", () => {
|
||||
test("variables", async () => {
|
||||
const str = `
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user