forked from defunkt/ReefVM
ADD: error checking
This commit is contained in:
parent
0b5d3e634c
commit
c69b172c78
9
SPEC.md
9
SPEC.md
|
|
@ -184,7 +184,8 @@ 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 arrays, concatenates the arrays
|
||||||
- Else if both operands are dicts, merges them (b's keys overwrite a's keys on conflict)
|
- 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
|
- Else if both operands are numbers, performs numeric addition
|
||||||
|
- Otherwise, throws an error
|
||||||
|
|
||||||
**Examples**:
|
**Examples**:
|
||||||
- `5 + 3` → `8` (numeric addition)
|
- `5 + 3` → `8` (numeric addition)
|
||||||
|
|
@ -196,6 +197,12 @@ Performs different operations depending on operand types:
|
||||||
- `{a: 1} + {b: 2}` → `{a: 1, b: 2}` (dict merge)
|
- `{a: 1} + {b: 2}` → `{a: 1, b: 2}` (dict merge)
|
||||||
- `{a: 1, b: 2} + {b: 99}` → `{a: 1, b: 99}` (dict merge, b overwrites)
|
- `{a: 1, b: 2} + {b: 99}` → `{a: 1, b: 99}` (dict merge, b overwrites)
|
||||||
|
|
||||||
|
**Invalid operations** (throw errors):
|
||||||
|
- `true + false` → Error
|
||||||
|
- `null + 5` → Error
|
||||||
|
- `[1] + 5` → Error
|
||||||
|
- `{a: 1} + 5` → Error
|
||||||
|
|
||||||
#### SUB
|
#### SUB
|
||||||
**Stack**: [a, b] → [a - b]
|
**Stack**: [a, b] → [a - b]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,22 +148,20 @@ export class VM {
|
||||||
const b = this.stack.pop()!
|
const b = this.stack.pop()!
|
||||||
const a = this.stack.pop()!
|
const a = this.stack.pop()!
|
||||||
|
|
||||||
// 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') {
|
} else if (a.type === 'array' && b.type === 'array') {
|
||||||
// two arrays, concatenate them
|
|
||||||
this.stack.push({ type: 'array', value: [...a.value, ...b.value] })
|
this.stack.push({ type: 'array', value: [...a.value, ...b.value] })
|
||||||
} else if (a.type === 'dict' && b.type === 'dict') {
|
} 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)
|
const merged = new Map(a.value)
|
||||||
for (const [key, value] of b.value) {
|
for (const [key, value] of b.value) {
|
||||||
merged.set(key, value)
|
merged.set(key, value)
|
||||||
}
|
}
|
||||||
this.stack.push({ type: 'dict', value: merged })
|
this.stack.push({ type: 'dict', value: merged })
|
||||||
|
} else if (a.type === 'number' && b.type === 'number') {
|
||||||
|
this.stack.push(toValue(a.value + b.value))
|
||||||
} else {
|
} else {
|
||||||
// Otherwise do numeric addition
|
throw new Error(`ADD: Cannot add ${a.type} and ${b.type}`)
|
||||||
this.stack.push(toValue(toNumber(a) + toNumber(b)))
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -487,7 +487,7 @@ test("TRY_CALL - handles null values", async () => {
|
||||||
|
|
||||||
test("TRY_CALL - function can access its parameters", async () => {
|
test("TRY_CALL - function can access its parameters", async () => {
|
||||||
const bytecode = toBytecode([
|
const bytecode = toBytecode([
|
||||||
["MAKE_FUNCTION", ["x"], ".body"],
|
["MAKE_FUNCTION", ["x=0"], ".body"],
|
||||||
["STORE", "addFive"],
|
["STORE", "addFive"],
|
||||||
["PUSH", 10],
|
["PUSH", 10],
|
||||||
["STORE", "x"],
|
["STORE", "x"],
|
||||||
|
|
@ -501,8 +501,8 @@ test("TRY_CALL - function can access its parameters", async () => {
|
||||||
])
|
])
|
||||||
|
|
||||||
const result = await run(bytecode)
|
const result = await run(bytecode)
|
||||||
// Function is called with 0 args, so x inside function should be null
|
// Function is called with 0 args, so x defaults to 0
|
||||||
// Then we add 5 to null (which coerces to 0)
|
// Then we add 5 to 0
|
||||||
expect(result).toEqual({ type: 'number', value: 5 })
|
expect(result).toEqual({ type: 'number', value: 5 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,87 @@ describe("ADD", () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("cannot add boolean + boolean", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", false],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add boolean and boolean')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add null + number", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", null],
|
||||||
|
["PUSH", 5],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add null and number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add array + dict", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["MAKE_DICT", 0],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add array and dict')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add array + number", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1],
|
||||||
|
["MAKE_ARRAY", 1],
|
||||||
|
["PUSH", 5],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add array and number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add dict + number", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_DICT", 0],
|
||||||
|
["PUSH", 5],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add dict and number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add function + number", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["MAKE_FUNCTION", [], ".body"],
|
||||||
|
["PUSH", 5],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"],
|
||||||
|
[".body:"],
|
||||||
|
["RETURN"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add function and number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test("cannot add boolean + null", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", true],
|
||||||
|
["PUSH", null],
|
||||||
|
["ADD"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
await expect(run(bytecode)).rejects.toThrow('ADD: Cannot add boolean and null')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("SUB", () => {
|
describe("SUB", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user