ADD now concats strings, too

This commit is contained in:
Chris Wanstrath 2025-10-29 15:20:28 -07:00
parent b58f848a65
commit 9618dd6414
4 changed files with 174 additions and 2 deletions

11
SPEC.md
View File

@ -179,7 +179,16 @@ All arithmetic operations pop two values, perform operation, push result as numb
#### ADD
**Stack**: [a, b] → [a + b]
**Note**: Only for numbers (use separate string concat if needed)
Performs addition or string concatenation depending on operand types:
- If either operand is a string, converts both to strings and concatenates
- Otherwise, converts both to numbers and performs numeric addition
**Examples**:
- `5 + 3``8` (numeric addition)
- `"hello" + " world"``"hello world"` (string concatenation)
- `"count: " + 42``"count: 42"` (string concatenation)
- `100 + " items"``"100 items"` (string concatenation)
#### SUB
**Stack**: [a, b] → [a - b]

View File

@ -0,0 +1,73 @@
/**
* Demonstrates the ADD opcode working with both numbers and strings
*
* ADD now behaves like JavaScript's + operator:
* - If either operand is a string, it does string concatenation
* - Otherwise, it does numeric addition
*/
import { toBytecode, run } from "#reef"
// Numeric addition
const numericAdd = toBytecode(`
PUSH 10
PUSH 5
ADD
HALT
`)
console.log('Numeric addition (10 + 5):')
console.log(await run(numericAdd))
// Output: { type: 'number', value: 15 }
// String concatenation
const stringConcat = toBytecode(`
PUSH "hello"
PUSH " world"
ADD
HALT
`)
console.log('\nString concatenation ("hello" + " world"):')
console.log(await run(stringConcat))
// Output: { type: 'string', value: 'hello world' }
// Mixed: string + number
const mixedConcat = toBytecode(`
PUSH "count: "
PUSH 42
ADD
HALT
`)
console.log('\nMixed concatenation ("count: " + 42):')
console.log(await run(mixedConcat))
// Output: { type: 'string', value: 'count: 42' }
// Building a message
const buildMessage = toBytecode(`
PUSH "You have "
PUSH 3
ADD
PUSH " new messages"
ADD
HALT
`)
console.log('\nBuilding a message:')
console.log(await run(buildMessage))
// Output: { type: 'string', value: 'You have 3 new messages' }
// Computing then concatenating
const computeAndConcat = toBytecode(`
PUSH "Result: "
PUSH 10
PUSH 5
ADD
ADD
HALT
`)
console.log('\nComputing then concatenating ("Result: " + (10 + 5)):')
console.log(await run(computeAndConcat))
// Output: { type: 'string', value: 'Result: 15' }

View File

@ -145,7 +145,16 @@ export class VM {
break
case OpCode.ADD:
this.binaryOp((a, b) => toNumber(a) + toNumber(b))
const b = this.stack.pop()!
const a = this.stack.pop()!
// If either operand is a string, do string concatenation
if (a.type === 'string' || b.type === 'string') {
this.stack.push(toValue(toString(a) + toString(b)))
} else {
// Otherwise do numeric addition
this.stack.push(toValue(toNumber(a) + toNumber(b)))
}
break
case OpCode.SUB:

View File

@ -18,6 +18,87 @@ describe("ADD", () => {
`
expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: 600 })
})
test("concatenate two strings", async () => {
const str = `
PUSH "hello"
PUSH " world"
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'hello world' })
const str2 = `
PUSH "foo"
PUSH "bar"
ADD
`
expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'foobar' })
})
test("concatenate string with number", async () => {
const str = `
PUSH "count: "
PUSH 42
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'count: 42' })
const str2 = `
PUSH 100
PUSH " items"
ADD
`
expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: '100 items' })
})
test("concatenate string with boolean", async () => {
const str = `
PUSH "result: "
PUSH true
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'result: true' })
const str2 = `
PUSH false
PUSH " value"
ADD
`
expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'false value' })
})
test("concatenate string with null", async () => {
const str = `
PUSH "value: "
PUSH null
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'value: null' })
})
test("concatenate multiple strings in sequence", async () => {
const str = `
PUSH "hello"
PUSH " "
ADD
PUSH "world"
ADD
PUSH "!"
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'hello world!' })
})
test("mixed arithmetic and string concatenation", async () => {
const str = `
PUSH "Result: "
PUSH 10
PUSH 5
ADD
ADD
`
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'Result: 15' })
})
})
describe("SUB", () => {