forked from defunkt/ReefVM
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
|
#### ADD
|
||||||
**Stack**: [a, b] → [a + b]
|
**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
|
#### SUB
|
||||||
**Stack**: [a, b] → [a - b]
|
**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
|
break
|
||||||
|
|
||||||
case OpCode.ADD:
|
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
|
break
|
||||||
|
|
||||||
case OpCode.SUB:
|
case OpCode.SUB:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,87 @@ describe("ADD", () => {
|
||||||
`
|
`
|
||||||
expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: 600 })
|
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", () => {
|
describe("SUB", () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user