From 4b2fd615546cc4dd1cacd40ce3cf4c014d3eec9f Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 29 Oct 2025 20:37:09 -0700 Subject: [PATCH] SWAP opcode --- GUIDE.md | 25 +++++++++++++++++++++++++ SPEC.md | 5 +++++ src/opcode.ts | 1 + src/vm.ts | 7 +++++++ tests/opcodes.test.ts | 30 ++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+) diff --git a/GUIDE.md b/GUIDE.md index a8e5683..f151dee 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -185,6 +185,7 @@ CALL - `PUSH ` - Push constant - `POP` - Remove top - `DUP` - Duplicate top +- `SWAP` - Swap top two values ### Variables - `LOAD ` - Push variable value (throws if not found) @@ -360,6 +361,30 @@ POP .end: ; Result on stack ``` +### Reversing Operand Order +Use SWAP to reverse operand order for non-commutative operations: + +``` +; Compute 10 / 2 when values are in reverse order +PUSH 2 +PUSH 10 +SWAP ; Now: [10, 2] +DIV ; 10 / 2 = 5 +``` + +``` +; Compute "hello" - "world" (subtraction with strings coerced to numbers) +PUSH "world" +PUSH "hello" +SWAP ; Now: ["hello", "world"] +SUB ; Result based on operand order +``` + +**Common Use Cases**: +- Division and subtraction when operands are in wrong order +- String concatenation with specific order +- Preparing arguments for functions that care about position + ### Try-Catch ``` PUSH_TRY .catch diff --git a/SPEC.md b/SPEC.md index 4262d6d..dd6e71e 100644 --- a/SPEC.md +++ b/SPEC.md @@ -138,6 +138,11 @@ type ExceptionHandler = { **Effect**: Duplicate top of stack **Stack**: [value] → [value, value] +#### SWAP +**Operand**: None +**Effect**: Swap the top two values on the stack +**Stack**: [value1, value2] → [value2, value1] + ### Variable Operations #### LOAD diff --git a/src/opcode.ts b/src/opcode.ts index 6fae6f1..d66ebd5 100644 --- a/src/opcode.ts +++ b/src/opcode.ts @@ -3,6 +3,7 @@ export enum OpCode { PUSH, // operand: constant index (number) | stack: [] → [value] POP, // operand: none | stack: [value] → [] DUP, // operand: none | stack: [value] → [value, value] + SWAP, // operand: none | stack: [value1, value2] → [value2, value1] // variables LOAD, // operand: variable name (identifier) | stack: [] → [value] diff --git a/src/vm.ts b/src/vm.ts index d55eb8c..19e54a8 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -144,6 +144,13 @@ export class VM { this.stack.push(this.stack[this.stack.length - 1]!) break + case OpCode.SWAP: + const first = this.stack.pop()! + const second = this.stack.pop()! + this.stack.push(first) + this.stack.push(second) + break + case OpCode.ADD: const b = this.stack.pop()! const a = this.stack.pop()! diff --git a/tests/opcodes.test.ts b/tests/opcodes.test.ts index 394771f..a921c86 100644 --- a/tests/opcodes.test.ts +++ b/tests/opcodes.test.ts @@ -538,6 +538,36 @@ describe("DUP", () => { }) }) +describe("SWAP", () => { + test("swaps two numbers", async () => { + const str = ` + PUSH 10 + PUSH 20 + SWAP + ` + expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 10 }) + }) + + test("swap and use in subtraction", async () => { + const str = ` + PUSH 5 + PUSH 10 + SWAP + SUB + ` + expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 5 }) + }) + + test("swap different types", async () => { + const str = ` + PUSH "hello" + PUSH 42 + SWAP + ` + expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'hello' }) + }) +}) + describe("EQ", () => { test("equality comparison", async () => { const str = `