ADD now concats strings, too
This commit is contained in:
parent
b58f848a65
commit
9618dd6414
11
SPEC.md
11
SPEC.md
|
|
@ -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]
|
||||
|
|
|
|||
73
examples/add-with-strings.ts
Normal file
73
examples/add-with-strings.ts
Normal 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' }
|
||||
11
src/vm.ts
11
src/vm.ts
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user