forked from defunkt/ReefVM
add bitwise operators
This commit is contained in:
parent
bffb83a528
commit
15884ac239
51
GUIDE.md
51
GUIDE.md
|
|
@ -195,6 +195,10 @@ CALL
|
||||||
### Arithmetic
|
### Arithmetic
|
||||||
- `ADD`, `SUB`, `MUL`, `DIV`, `MOD` - Binary ops (pop 2, push result)
|
- `ADD`, `SUB`, `MUL`, `DIV`, `MOD` - Binary ops (pop 2, push result)
|
||||||
|
|
||||||
|
### Bitwise
|
||||||
|
- `BIT_AND`, `BIT_OR`, `BIT_XOR` - Bitwise logical ops (pop 2, push result)
|
||||||
|
- `BIT_SHL`, `BIT_SHR`, `BIT_USHR` - Bitwise shift ops (pop 2, push result)
|
||||||
|
|
||||||
### Comparison
|
### Comparison
|
||||||
- `EQ`, `NEQ`, `LT`, `GT`, `LTE`, `GTE` - Pop 2, push boolean
|
- `EQ`, `NEQ`, `LT`, `GT`, `LTE`, `GTE` - Pop 2, push boolean
|
||||||
|
|
||||||
|
|
@ -385,6 +389,51 @@ SUB ; Result based on operand order
|
||||||
- String concatenation with specific order
|
- String concatenation with specific order
|
||||||
- Preparing arguments for functions that care about position
|
- Preparing arguments for functions that care about position
|
||||||
|
|
||||||
|
### Bitwise Operations
|
||||||
|
All bitwise operations work with 32-bit signed integers:
|
||||||
|
|
||||||
|
```
|
||||||
|
; Bitwise AND (masking)
|
||||||
|
PUSH 5
|
||||||
|
PUSH 3
|
||||||
|
BIT_AND ; → 1 (0101 & 0011 = 0001)
|
||||||
|
|
||||||
|
; Bitwise OR (combining flags)
|
||||||
|
PUSH 5
|
||||||
|
PUSH 3
|
||||||
|
BIT_OR ; → 7 (0101 | 0011 = 0111)
|
||||||
|
|
||||||
|
; Bitwise XOR (toggling bits)
|
||||||
|
PUSH 5
|
||||||
|
PUSH 3
|
||||||
|
BIT_XOR ; → 6 (0101 ^ 0011 = 0110)
|
||||||
|
|
||||||
|
; Left shift (multiply by power of 2)
|
||||||
|
PUSH 5
|
||||||
|
PUSH 2
|
||||||
|
BIT_SHL ; → 20 (5 << 2 = 5 * 4)
|
||||||
|
|
||||||
|
; Arithmetic right shift (divide by power of 2, preserves sign)
|
||||||
|
PUSH 20
|
||||||
|
PUSH 2
|
||||||
|
BIT_SHR ; → 5 (20 >> 2 = 20 / 4)
|
||||||
|
|
||||||
|
PUSH -20
|
||||||
|
PUSH 2
|
||||||
|
BIT_SHR ; → -5 (sign preserved)
|
||||||
|
|
||||||
|
; Logical right shift (zero-fill)
|
||||||
|
PUSH -1
|
||||||
|
PUSH 1
|
||||||
|
BIT_USHR ; → 2147483647 (unsigned shift)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common Use Cases**:
|
||||||
|
- Flags and bit masks: `flags band MASK` to test, `flags bor FLAG` to set
|
||||||
|
- Fast multiplication/division by powers of 2
|
||||||
|
- Color manipulation: extract RGB components
|
||||||
|
- Low-level bit manipulation for protocols or file formats
|
||||||
|
|
||||||
### Try-Catch
|
### Try-Catch
|
||||||
```
|
```
|
||||||
PUSH_TRY .catch
|
PUSH_TRY .catch
|
||||||
|
|
@ -622,6 +671,8 @@ Only `null` and `false` are falsy. Everything else (including `0`, `""`, empty a
|
||||||
|
|
||||||
**Arithmetic ops** (ADD, SUB, MUL, DIV, MOD) coerce both operands to numbers.
|
**Arithmetic ops** (ADD, SUB, MUL, DIV, MOD) coerce both operands to numbers.
|
||||||
|
|
||||||
|
**Bitwise ops** (BIT_AND, BIT_OR, BIT_XOR, BIT_SHL, BIT_SHR, BIT_USHR) coerce both operands to 32-bit signed integers.
|
||||||
|
|
||||||
**Comparison ops** (LT, GT, LTE, GTE) coerce both operands to numbers.
|
**Comparison ops** (LT, GT, LTE, GTE) coerce both operands to numbers.
|
||||||
|
|
||||||
**Equality ops** (EQ, NEQ) use type-aware comparison with deep equality for arrays/dicts.
|
**Equality ops** (EQ, NEQ) use type-aware comparison with deep equality for arrays/dicts.
|
||||||
|
|
|
||||||
56
SPEC.md
56
SPEC.md
|
|
@ -220,6 +220,62 @@ Performs different operations depending on operand types:
|
||||||
#### MOD
|
#### MOD
|
||||||
**Stack**: [a, b] → [a % b]
|
**Stack**: [a, b] → [a % b]
|
||||||
|
|
||||||
|
### Bitwise Operations
|
||||||
|
|
||||||
|
All bitwise operations coerce operands to 32-bit signed integers, perform the operation, and push the result as a number.
|
||||||
|
|
||||||
|
#### BIT_AND
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a & b]
|
||||||
|
|
||||||
|
Performs bitwise AND operation. Both operands are coerced to 32-bit signed integers.
|
||||||
|
|
||||||
|
**Example**: `5 & 3` → `1` (binary: `0101 & 0011` → `0001`)
|
||||||
|
|
||||||
|
#### BIT_OR
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a | b]
|
||||||
|
|
||||||
|
Performs bitwise OR operation. Both operands are coerced to 32-bit signed integers.
|
||||||
|
|
||||||
|
**Example**: `5 | 3` → `7` (binary: `0101 | 0011` → `0111`)
|
||||||
|
|
||||||
|
#### BIT_XOR
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a ^ b]
|
||||||
|
|
||||||
|
Performs bitwise XOR (exclusive OR) operation. Both operands are coerced to 32-bit signed integers.
|
||||||
|
|
||||||
|
**Example**: `5 ^ 3` → `6` (binary: `0101 ^ 0011` → `0110`)
|
||||||
|
|
||||||
|
#### BIT_SHL
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a << b]
|
||||||
|
|
||||||
|
Performs left shift operation. Left operand is coerced to 32-bit signed integer, right operand determines shift amount (masked to 0-31).
|
||||||
|
|
||||||
|
**Example**: `5 << 2` → `20` (binary: `0101` shifted left 2 positions → `10100`)
|
||||||
|
|
||||||
|
#### BIT_SHR
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a >> b]
|
||||||
|
|
||||||
|
Performs sign-preserving right shift operation. Left operand is coerced to 32-bit signed integer, right operand determines shift amount (masked to 0-31). The sign bit is preserved (arithmetic shift).
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
- `20 >> 2` → `5` (binary: `10100` shifted right 2 positions → `0101`)
|
||||||
|
- `-20 >> 2` → `-5` (sign bit preserved)
|
||||||
|
|
||||||
|
#### BIT_USHR
|
||||||
|
**Operand**: None
|
||||||
|
**Stack**: [a, b] → [a >>> b]
|
||||||
|
|
||||||
|
Performs zero-fill right shift operation. Left operand is coerced to 32-bit signed integer, right operand determines shift amount (masked to 0-31). Zeros are shifted in from the left (logical shift).
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
- `-1 >>> 1` → `2147483647` (all bits shift right, zero fills from left)
|
||||||
|
- `-8 >>> 1` → `2147483644`
|
||||||
|
|
||||||
### Comparison Operations
|
### Comparison Operations
|
||||||
|
|
||||||
All comparison operations pop two values, compare, push boolean result.
|
All comparison operations pop two values, compare, push boolean result.
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ type InstructionTuple =
|
||||||
// Arithmetic
|
// Arithmetic
|
||||||
| ["ADD"] | ["SUB"] | ["MUL"] | ["DIV"] | ["MOD"]
|
| ["ADD"] | ["SUB"] | ["MUL"] | ["DIV"] | ["MOD"]
|
||||||
|
|
||||||
|
// Bitwise
|
||||||
|
| ["BIT_AND"] | ["BIT_OR"] | ["BIT_XOR"] | ["BIT_SHL"] | ["BIT_SHR"] | ["BIT_USHR"]
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
| ["EQ"] | ["NEQ"] | ["LT"] | ["GT"] | ["LTE"] | ["GTE"]
|
| ["EQ"] | ["NEQ"] | ["LT"] | ["GT"] | ["LTE"] | ["GTE"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,14 @@ export enum OpCode {
|
||||||
DIV, // operand: none | stack: [a, b] → [a / b]
|
DIV, // operand: none | stack: [a, b] → [a / b]
|
||||||
MOD, // operand: none | stack: [a, b] → [a % b]
|
MOD, // operand: none | stack: [a, b] → [a % b]
|
||||||
|
|
||||||
|
// bitwise operations (coerce to 32-bit integers, pop 2, push result)
|
||||||
|
BIT_AND, // operand: none | stack: [a, b] → [a & b]
|
||||||
|
BIT_OR, // operand: none | stack: [a, b] → [a | b]
|
||||||
|
BIT_XOR, // operand: none | stack: [a, b] → [a ^ b]
|
||||||
|
BIT_SHL, // operand: none | stack: [a, b] → [a << b]
|
||||||
|
BIT_SHR, // operand: none | stack: [a, b] → [a >> b] (sign-preserving)
|
||||||
|
BIT_USHR, // operand: none | stack: [a, b] → [a >>> b] (zero-fill)
|
||||||
|
|
||||||
// comparison (pop 2, push boolean)
|
// comparison (pop 2, push boolean)
|
||||||
EQ, // operand: none | stack: [a, b] → [a == b] (deep equality)
|
EQ, // operand: none | stack: [a, b] → [a == b] (deep equality)
|
||||||
NEQ, // operand: none | stack: [a, b] → [a != b]
|
NEQ, // operand: none | stack: [a, b] → [a != b]
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@ const OPCODES_WITHOUT_OPERANDS = new Set([
|
||||||
OpCode.MUL,
|
OpCode.MUL,
|
||||||
OpCode.DIV,
|
OpCode.DIV,
|
||||||
OpCode.MOD,
|
OpCode.MOD,
|
||||||
|
OpCode.BIT_AND,
|
||||||
|
OpCode.BIT_OR,
|
||||||
|
OpCode.BIT_XOR,
|
||||||
|
OpCode.BIT_SHL,
|
||||||
|
OpCode.BIT_SHR,
|
||||||
|
OpCode.BIT_USHR,
|
||||||
OpCode.EQ,
|
OpCode.EQ,
|
||||||
OpCode.NEQ,
|
OpCode.NEQ,
|
||||||
OpCode.LT,
|
OpCode.LT,
|
||||||
|
|
|
||||||
25
src/vm.ts
25
src/vm.ts
|
|
@ -233,6 +233,31 @@ export class VM {
|
||||||
this.stack.push({ type: 'boolean', value: !isTrue(val) })
|
this.stack.push({ type: 'boolean', value: !isTrue(val) })
|
||||||
break
|
break
|
||||||
|
|
||||||
|
// Bitwise operations
|
||||||
|
case OpCode.BIT_AND:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) & (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case OpCode.BIT_OR:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) | (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case OpCode.BIT_XOR:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) ^ (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case OpCode.BIT_SHL:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) << (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case OpCode.BIT_SHR:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) >> (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case OpCode.BIT_USHR:
|
||||||
|
this.binaryOp((a, b) => (toNumber(a) | 0) >>> (toNumber(b) | 0))
|
||||||
|
break
|
||||||
|
|
||||||
case OpCode.HALT:
|
case OpCode.HALT:
|
||||||
this.stopped = true
|
this.stopped = true
|
||||||
break
|
break
|
||||||
|
|
|
||||||
107
tests/bitwise.test.ts
Normal file
107
tests/bitwise.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { expect, describe, test } from 'bun:test'
|
||||||
|
import { toBytecode, run } from '#reef'
|
||||||
|
|
||||||
|
describe('bitwise operations', () => {
|
||||||
|
test('BIT_AND', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 3], ["BIT_AND"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_AND with zero', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 0], ["BIT_AND"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_OR', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 3], ["BIT_OR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 7 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_OR with zero', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 0], ["BIT_OR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_XOR', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 3], ["BIT_XOR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 6 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_XOR with itself returns zero', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 5], ["BIT_XOR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_SHL', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 2], ["BIT_SHL"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_SHL by zero', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 5], ["PUSH", 0], ["BIT_SHL"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_SHR', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_SHR preserves sign for negative numbers', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", -20], ["PUSH", 2], ["BIT_SHR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: -5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_USHR', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", -1], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 2147483647 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('BIT_USHR does not preserve sign', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", -8], ["PUSH", 1], ["BIT_USHR"], ["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 2147483644 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compound bitwise operations', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
// (5 & 3) | (8 ^ 12)
|
||||||
|
["PUSH", 5], ["PUSH", 3], ["BIT_AND"], // stack: [1]
|
||||||
|
["PUSH", 8], ["PUSH", 12], ["BIT_XOR"], // stack: [1, 4]
|
||||||
|
["BIT_OR"], // stack: [5]
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 5 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shift with large shift amounts', async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", 1], ["PUSH", 31], ["BIT_SHL"], ["HALT"]
|
||||||
|
])
|
||||||
|
// 1 << 31 = -2147483648 (most significant bit set)
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: -2147483648 })
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user