ADD: error checking

This commit is contained in:
Chris Wanstrath 2025-10-29 16:03:26 -07:00
parent 0b5d3e634c
commit c69b172c78
4 changed files with 95 additions and 9 deletions

View File

@ -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]

View File

@ -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

View File

@ -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 })
}) })

View File

@ -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", () => {