Compare commits
2 Commits
9618dd6414
...
ba8376e2c3
| Author | SHA1 | Date | |
|---|---|---|---|
| ba8376e2c3 | |||
| 956fd576f8 |
8
SPEC.md
8
SPEC.md
|
|
@ -180,8 +180,10 @@ All arithmetic operations pop two values, perform operation, push result as numb
|
||||||
#### ADD
|
#### ADD
|
||||||
**Stack**: [a, b] → [a + b]
|
**Stack**: [a, b] → [a + b]
|
||||||
|
|
||||||
Performs addition or string concatenation depending on operand types:
|
Performs different operations depending on operand types:
|
||||||
- If either operand is a string, converts both to strings and concatenates
|
- If either operand is a string, converts both to strings and concatenates
|
||||||
|
- Else if both operands are arrays, concatenates the arrays
|
||||||
|
- Else if both operands are dicts, merges them (b's keys overwrite a's keys on conflict)
|
||||||
- Otherwise, converts both to numbers and performs numeric addition
|
- Otherwise, converts both to numbers and performs numeric addition
|
||||||
|
|
||||||
**Examples**:
|
**Examples**:
|
||||||
|
|
@ -189,6 +191,10 @@ Performs addition or string concatenation depending on operand types:
|
||||||
- `"hello" + " world"` → `"hello world"` (string concatenation)
|
- `"hello" + " world"` → `"hello world"` (string concatenation)
|
||||||
- `"count: " + 42` → `"count: 42"` (string concatenation)
|
- `"count: " + 42` → `"count: 42"` (string concatenation)
|
||||||
- `100 + " items"` → `"100 items"` (string concatenation)
|
- `100 + " items"` → `"100 items"` (string concatenation)
|
||||||
|
- `[1, 2, 3] + [4]` → `[1, 2, 3, 4]` (array concatenation)
|
||||||
|
- `[1, 2] + [3, 4]` → `[1, 2, 3, 4]` (array concatenation)
|
||||||
|
- `{a: 1} + {b: 2}` → `{a: 1, b: 2}` (dict merge)
|
||||||
|
- `{a: 1, b: 2} + {b: 99}` → `{a: 1, b: 99}` (dict merge, b overwrites)
|
||||||
|
|
||||||
#### SUB
|
#### SUB
|
||||||
**Stack**: [a, b] → [a - b]
|
**Stack**: [a, b] → [a - b]
|
||||||
|
|
|
||||||
116
examples/add-with-arrays.ts
Normal file
116
examples/add-with-arrays.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Demonstrates the ADD opcode working with arrays
|
||||||
|
*
|
||||||
|
* ADD now handles array concatenation:
|
||||||
|
* - [1, 2, 3] + [4] === [1, 2, 3, 4]
|
||||||
|
* - If both operands are arrays, they are concatenated
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { toBytecode, run } from "#reef"
|
||||||
|
|
||||||
|
// Basic array concatenation
|
||||||
|
const basicConcat = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 3],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('Basic array concatenation ([1, 2, 3] + [4]):')
|
||||||
|
const result1 = await run(basicConcat)
|
||||||
|
console.log(result1)
|
||||||
|
// Output: { type: 'array', value: [1, 2, 3, 4] }
|
||||||
|
|
||||||
|
// Concatenate two multi-element arrays
|
||||||
|
const multiConcat = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["PUSH", 3],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nConcatenate two arrays ([1, 2] + [3, 4]):')
|
||||||
|
const result2 = await run(multiConcat)
|
||||||
|
console.log(result2)
|
||||||
|
// Output: { type: 'array', value: [1, 2, 3, 4] }
|
||||||
|
|
||||||
|
// Concatenate multiple arrays in sequence
|
||||||
|
const multipleConcat = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMultiple concatenations ([1] + [2] + [3] + [4]):')
|
||||||
|
const result3 = await run(multipleConcat)
|
||||||
|
console.log(result3)
|
||||||
|
// Output: { type: 'array', value: [1, 2, 3, 4] }
|
||||||
|
|
||||||
|
// Concatenate arrays with mixed types
|
||||||
|
const mixedTypes = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", "hello"],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", null],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nConcatenate arrays with mixed types ([1, "hello"] + [true, null]):')
|
||||||
|
const result4 = await run(mixedTypes)
|
||||||
|
console.log(result4)
|
||||||
|
// Output: { type: 'array', value: [1, "hello", true, null] }
|
||||||
|
|
||||||
|
// Concatenate empty array with non-empty
|
||||||
|
const emptyConcat = toBytecode([
|
||||||
|
["MAKE_ARRAY", 0],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 3],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nConcatenate empty array with [1, 2, 3] ([] + [1, 2, 3]):')
|
||||||
|
const result5 = await run(emptyConcat)
|
||||||
|
console.log(result5)
|
||||||
|
// Output: { type: 'array', value: [1, 2, 3] }
|
||||||
|
|
||||||
|
// Nested arrays
|
||||||
|
const nestedConcat = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["PUSH", 3],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nConcatenate nested arrays ([[1, 2]] + [[3, 4]]):')
|
||||||
|
const result6 = await run(nestedConcat)
|
||||||
|
console.log(result6)
|
||||||
|
// Output: { type: 'array', value: [[1, 2], [3, 4]] }
|
||||||
158
examples/add-with-dicts.ts
Normal file
158
examples/add-with-dicts.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
/**
|
||||||
|
* Demonstrates the ADD opcode working with dicts
|
||||||
|
*
|
||||||
|
* ADD now handles dict merging:
|
||||||
|
* - {a: 1} + {b: 2} === {a: 1, b: 2}
|
||||||
|
* - If both operands are dicts, they are merged
|
||||||
|
* - Keys from the second dict overwrite keys from the first on conflict
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { toBytecode, run } from "#reef"
|
||||||
|
|
||||||
|
// Basic dict merge
|
||||||
|
const basicMerge = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('Basic dict merge ({a: 1} + {b: 2}):')
|
||||||
|
const result1 = await run(basicMerge)
|
||||||
|
console.log(result1)
|
||||||
|
// Output: { type: 'dict', value: Map { a: 1, b: 2 } }
|
||||||
|
|
||||||
|
// Merge with overlapping keys
|
||||||
|
const overlapMerge = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 99],
|
||||||
|
["PUSH", "c"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMerge with overlapping keys ({a: 1, b: 2} + {b: 99, c: 3}):')
|
||||||
|
const result2 = await run(overlapMerge)
|
||||||
|
console.log(result2)
|
||||||
|
console.log('Note: b is overwritten from 2 to 99')
|
||||||
|
// Output: { type: 'dict', value: Map { a: 1, b: 99, c: 3 } }
|
||||||
|
|
||||||
|
// Merge multiple dicts in sequence
|
||||||
|
const multipleMerge = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", "c"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", "d"],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMultiple merges ({a: 1} + {b: 2} + {c: 3} + {d: 4}):')
|
||||||
|
const result3 = await run(multipleMerge)
|
||||||
|
console.log(result3)
|
||||||
|
// Output: { type: 'dict', value: Map { a: 1, b: 2, c: 3, d: 4 } }
|
||||||
|
|
||||||
|
// Merge dicts with different value types
|
||||||
|
const mixedTypes = toBytecode([
|
||||||
|
["PUSH", "num"],
|
||||||
|
["PUSH", 42],
|
||||||
|
["PUSH", "str"],
|
||||||
|
["PUSH", "hello"],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["PUSH", "bool"],
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", "null"],
|
||||||
|
["PUSH", null],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMerge dicts with different types ({num: 42, str: "hello"} + {bool: true, null: null}):')
|
||||||
|
const result4 = await run(mixedTypes)
|
||||||
|
console.log(result4)
|
||||||
|
// Output: { type: 'dict', value: Map { num: 42, str: 'hello', bool: true, null: null } }
|
||||||
|
|
||||||
|
// Merge empty dict with non-empty
|
||||||
|
const emptyMerge = toBytecode([
|
||||||
|
["MAKE_DICT", 0],
|
||||||
|
["PUSH", "x"],
|
||||||
|
["PUSH", 100],
|
||||||
|
["PUSH", "y"],
|
||||||
|
["PUSH", 200],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMerge empty dict with {x: 100, y: 200} ({} + {x: 100, y: 200}):')
|
||||||
|
const result5 = await run(emptyMerge)
|
||||||
|
console.log(result5)
|
||||||
|
// Output: { type: 'dict', value: Map { x: 100, y: 200 } }
|
||||||
|
|
||||||
|
// Merge dicts with nested structures
|
||||||
|
const nestedMerge = toBytecode([
|
||||||
|
["PUSH", "data"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "config"],
|
||||||
|
["PUSH", "debug"],
|
||||||
|
["PUSH", true],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nMerge dicts with nested structures:')
|
||||||
|
const result6 = await run(nestedMerge)
|
||||||
|
console.log(result6)
|
||||||
|
// Output: { type: 'dict', value: Map { data: [1, 2], config: { debug: true } } }
|
||||||
|
|
||||||
|
// Building configuration objects
|
||||||
|
const configBuild = toBytecode([
|
||||||
|
// Default config
|
||||||
|
["PUSH", "debug"],
|
||||||
|
["PUSH", false],
|
||||||
|
["PUSH", "port"],
|
||||||
|
["PUSH", 3000],
|
||||||
|
["PUSH", "host"],
|
||||||
|
["PUSH", "localhost"],
|
||||||
|
["MAKE_DICT", 3],
|
||||||
|
// Override with user config
|
||||||
|
["PUSH", "debug"],
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", "port"],
|
||||||
|
["PUSH", 8080],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log('\nBuilding config (defaults + overrides):')
|
||||||
|
const result7 = await run(configBuild)
|
||||||
|
console.log(result7)
|
||||||
|
// Output: { type: 'dict', value: Map { debug: true, port: 8080, host: 'localhost' } }
|
||||||
10
src/vm.ts
10
src/vm.ts
|
|
@ -151,6 +151,16 @@ export class VM {
|
||||||
// If either operand is a string, do string concatenation
|
// If either operand is a string, do string concatenation
|
||||||
if (a.type === 'string' || b.type === 'string') {
|
if (a.type === 'string' || b.type === 'string') {
|
||||||
this.stack.push(toValue(toString(a) + toString(b)))
|
this.stack.push(toValue(toString(a) + toString(b)))
|
||||||
|
} else if (a.type === 'array' && b.type === 'array') {
|
||||||
|
// two arrays, concatenate them
|
||||||
|
this.stack.push({ type: 'array', value: [...a.value, ...b.value] })
|
||||||
|
} else if (a.type === 'dict' && b.type === 'dict') {
|
||||||
|
// two dicts, merge them (b's keys overwrite a's keys)
|
||||||
|
const merged = new Map(a.value)
|
||||||
|
for (const [key, value] of b.value) {
|
||||||
|
merged.set(key, value)
|
||||||
|
}
|
||||||
|
this.stack.push({ type: 'dict', value: merged })
|
||||||
} else {
|
} else {
|
||||||
// Otherwise do numeric addition
|
// Otherwise do numeric addition
|
||||||
this.stack.push(toValue(toNumber(a) + toNumber(b)))
|
this.stack.push(toValue(toNumber(a) + toNumber(b)))
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,280 @@ describe("ADD", () => {
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'Result: 15' })
|
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'Result: 15' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("concatenate two arrays", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 3],
|
||||||
|
["PUSH", 4],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value.length).toBe(4)
|
||||||
|
expect(result.value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value[1]).toEqual({ type: 'number', value: 2 })
|
||||||
|
expect(result.value[2]).toEqual({ type: 'number', value: 3 })
|
||||||
|
expect(result.value[3]).toEqual({ type: 'number', value: 4 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("concatenate empty arrays", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_ARRAY", 0],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value.length).toBe(2)
|
||||||
|
expect(result.value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value[1]).toEqual({ type: 'number', value: 2 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("concatenate multiple arrays in sequence", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value.length).toBe(3)
|
||||||
|
expect(result.value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value[1]).toEqual({ type: 'number', value: 2 })
|
||||||
|
expect(result.value[2]).toEqual({ type: 'number', value: 3 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("concatenate arrays with different types", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", "hello"],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", null],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value.length).toBe(4)
|
||||||
|
expect(result.value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value[1]).toEqual({ type: 'string', value: 'hello' })
|
||||||
|
expect(result.value[2]).toEqual({ type: 'boolean', value: true })
|
||||||
|
expect(result.value[3]).toEqual({ type: 'null', value: null })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("concatenate arrays containing nested arrays", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value.length).toBe(2)
|
||||||
|
// First element is nested array [1, 2]
|
||||||
|
expect(result.value[0]?.type).toBe('array')
|
||||||
|
if (result.value[0]?.type === 'array') {
|
||||||
|
expect(result.value[0].value.length).toBe(2)
|
||||||
|
expect(result.value[0].value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value[0].value[1]).toEqual({ type: 'number', value: 2 })
|
||||||
|
}
|
||||||
|
// Second element is 3
|
||||||
|
expect(result.value[1]).toEqual({ type: 'number', value: 3 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge two dicts", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(2)
|
||||||
|
expect(result.value.get('a')).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value.get('b')).toEqual({ type: 'number', value: 2 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge dicts with overlapping keys (second overwrites)", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 99],
|
||||||
|
["PUSH", "c"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(3)
|
||||||
|
expect(result.value.get('a')).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value.get('b')).toEqual({ type: 'number', value: 99 }) // overwritten
|
||||||
|
expect(result.value.get('c')).toEqual({ type: 'number', value: 3 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge empty dicts", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_DICT", 0],
|
||||||
|
["PUSH", "x"],
|
||||||
|
["PUSH", 42],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(1)
|
||||||
|
expect(result.value.get('x')).toEqual({ type: 'number', value: 42 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge multiple dicts in sequence", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["PUSH", "c"],
|
||||||
|
["PUSH", 3],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(3)
|
||||||
|
expect(result.value.get('a')).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(result.value.get('b')).toEqual({ type: 'number', value: 2 })
|
||||||
|
expect(result.value.get('c')).toEqual({ type: 'number', value: 3 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge dicts with different value types", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "num"],
|
||||||
|
["PUSH", 42],
|
||||||
|
["PUSH", "str"],
|
||||||
|
["PUSH", "hello"],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["PUSH", "bool"],
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", "null"],
|
||||||
|
["PUSH", null],
|
||||||
|
["MAKE_DICT", 2],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(4)
|
||||||
|
expect(result.value.get('num')).toEqual({ type: 'number', value: 42 })
|
||||||
|
expect(result.value.get('str')).toEqual({ type: 'string', value: 'hello' })
|
||||||
|
expect(result.value.get('bool')).toEqual({ type: 'boolean', value: true })
|
||||||
|
expect(result.value.get('null')).toEqual({ type: 'null', value: null })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("merge dicts with nested structures", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "a"],
|
||||||
|
["PUSH", 1],
|
||||||
|
["PUSH", 2],
|
||||||
|
["MAKE_ARRAY", 2],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["PUSH", "b"],
|
||||||
|
["PUSH", "x"],
|
||||||
|
["PUSH", 99],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["MAKE_DICT", 1],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.size).toBe(2)
|
||||||
|
|
||||||
|
// a contains an array [1, 2]
|
||||||
|
const aValue = result.value.get('a')
|
||||||
|
expect(aValue?.type).toBe('array')
|
||||||
|
if (aValue?.type === 'array') {
|
||||||
|
expect(aValue.value.length).toBe(2)
|
||||||
|
expect(aValue.value[0]).toEqual({ type: 'number', value: 1 })
|
||||||
|
expect(aValue.value[1]).toEqual({ type: 'number', value: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// b contains a nested dict {x: 99}
|
||||||
|
const bValue = result.value.get('b')
|
||||||
|
expect(bValue?.type).toBe('dict')
|
||||||
|
if (bValue?.type === 'dict') {
|
||||||
|
expect(bValue.value.size).toBe(1)
|
||||||
|
expect(bValue.value.get('x')).toEqual({ type: 'number', value: 99 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("SUB", () => {
|
describe("SUB", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user