import { test, expect } from "bun:test" import { run } from "#index" import { toBytecode } from "#bytecode" test("ADD - add two numbers", async () => { const str = ` PUSH 1 PUSH 5 ADD ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 6 }) const str2 = ` PUSH 100 PUSH 500 ADD ` expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: 600 }) }) test("SUB - subtract two numbers", async () => { const str = ` PUSH 5 PUSH 2 SUB ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 3 }) }) test("MUL - multiply two numbers", async () => { const str = ` PUSH 5 PUSH 2 MUL ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 10 }) }) test("DIV - divide two numbers", async () => { const str = ` PUSH 10 PUSH 2 DIV ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 5 }) const str2 = ` PUSH 10 PUSH 0 DIV ` expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: Infinity }) }) test("MOD - modulo two numbers", async () => { const str = ` PUSH 17 PUSH 5 MOD ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 2 }) }) test("PUSH - pushes value onto stack", async () => { const str = ` PUSH 42 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("POP - removes top value", async () => { const str = ` PUSH 10 PUSH 20 POP ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 10 }) }) test("DUP - duplicates top value", async () => { const str = ` PUSH 5 DUP ADD ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 10 }) }) test("EQ - equality comparison", async () => { const str = ` PUSH 5 PUSH 5 EQ ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) const str2 = ` PUSH 5 PUSH 10 EQ ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false }) }) test("NEQ - not equal comparison", async () => { const str = ` PUSH 5 PUSH 10 NEQ ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) }) test("LT - less than", async () => { const str = ` PUSH 5 PUSH 10 LT ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) }) test("GT - greater than", async () => { const str = ` PUSH 10 PUSH 5 GT ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) }) test("LTE - less than or equal", async () => { // equal case const str = ` PUSH 5 PUSH 5 LTE ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) // less than case const str2 = ` PUSH 3 PUSH 5 LTE ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: true }) // greater than case (false) const str3 = ` PUSH 10 PUSH 5 LTE ` expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: false }) }) test("GTE - greater than or equal", async () => { // equal case const str = ` PUSH 5 PUSH 5 GTE ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) // greater than case const str2 = ` PUSH 10 PUSH 5 GTE ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: true }) // less than case (false) const str3 = ` PUSH 3 PUSH 5 GTE ` expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: false }) }) test("AND pattern - short circuits when false", async () => { // false && should short-circuit and return false const str = ` PUSH 1 PUSH 0 EQ DUP JUMP_IF_FALSE 2 POP PUSH 999 ` const result = await run(toBytecode(str)) expect(result.type).toBe('boolean') if (result.type === 'boolean') { expect(result.value).toBe(false) } }) test("AND pattern - evaluates both when true", async () => { const str = ` PUSH 1 DUP JUMP_IF_FALSE 2 POP PUSH 2 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 2 }) }) test("OR pattern - short circuits when true", async () => { const str = ` PUSH 1 DUP JUMP_IF_TRUE 2 POP PUSH 2 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 1 }) }) test("OR pattern - evaluates second when false", async () => { const str = ` PUSH 1 PUSH 0 EQ DUP JUMP_IF_TRUE 2 POP PUSH 2 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 2 }) }) test("NOT - logical not", async () => { // number is truthy, so NOT returns false const str = ` PUSH 1 NOT ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false }) // 0 is truthy in this language, so NOT returns false const str2 = ` PUSH 0 NOT ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false }) // boolean false is falsy, so NOT returns true const str3 = ` PUSH 1 PUSH 0 EQ NOT ` expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: true }) }) test("isTruthy - only null and false are falsy", async () => { // 0 is truthy (unlike JS) const str1 = ` PUSH 0 JUMP_IF_FALSE 1 PUSH 1 ` expect(await run(toBytecode(str1))).toEqual({ type: 'number', value: 1 }) // empty string is truthy (unlike JS) const str2 = ` PUSH '' JUMP_IF_FALSE 1 PUSH 1 ` expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: 1 }) // false is falsy const str3 = ` PUSH 0 PUSH 0 EQ JUMP_IF_FALSE 1 PUSH 999 ` expect(await run(toBytecode(str3))).toEqual({ type: 'number', value: 999 }) }) test("HALT - stops execution", async () => { const str = ` PUSH 42 HALT PUSH 100 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("STORE and LOAD - variables", async () => { const str = ` PUSH 42 STORE x PUSH 21 LOAD x ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("STORE and LOAD - multiple variables", async () => { const str = ` PUSH 10 STORE a PUSH 20 STORE b PUSH 44 LOAD a LOAD b ADD ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 30 }) }) test("JUMP - relative jump forward", async () => { const str = ` PUSH 1 JUMP 1 PUSH 100 PUSH 2 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 2 }) }) test("JUMP - backward offset demonstrates relative jumps", async () => { // Use forward jump to skip, demonstrating relative addressing const str = ` PUSH 100 JUMP 2 PUSH 200 PUSH 300 PUSH 400 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 400 }) }) test("JUMP_IF_FALSE - conditional jump when false", async () => { const str = ` PUSH 1 PUSH 0 EQ JUMP_IF_FALSE 1 PUSH 100 PUSH 42 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("JUMP_IF_FALSE - no jump when true", async () => { const str = ` PUSH 1 JUMP_IF_FALSE 1 PUSH 100 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 100 }) }) test("JUMP_IF_TRUE - conditional jump when true", async () => { const str = ` PUSH 1 JUMP_IF_TRUE 1 PUSH 100 PUSH 42 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("MAKE_ARRAY - creates array", async () => { const str = ` PUSH 10 PUSH 20 PUSH 30 MAKE_ARRAY 3 ` const result = await run(toBytecode(str)) expect(result.type).toBe('array') if (result.type === 'array') { expect(result.value).toHaveLength(3) expect(result.value[0]).toEqual({ type: 'number', value: 10 }) expect(result.value[1]).toEqual({ type: 'number', value: 20 }) expect(result.value[2]).toEqual({ type: 'number', value: 30 }) } }) test("ARRAY_GET - gets element", async () => { const str = ` PUSH 10 PUSH 20 PUSH 30 MAKE_ARRAY 3 PUSH 1 ARRAY_GET ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 20 }) }) test("ARRAY_SET - sets element", async () => { const str = ` PUSH 10 PUSH 20 PUSH 30 MAKE_ARRAY 3 DUP PUSH 1 PUSH 99 ARRAY_SET PUSH 1 ARRAY_GET ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 99 }) }) test("ARRAY_PUSH - appends to array", async () => { const str = ` PUSH 10 PUSH 20 MAKE_ARRAY 2 DUP PUSH 30 ARRAY_PUSH ARRAY_LEN ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 3 }) }) test("ARRAY_PUSH - mutates original array", async () => { const str = ` PUSH 10 PUSH 20 MAKE_ARRAY 2 DUP PUSH 30 ARRAY_PUSH PUSH 2 ARRAY_GET ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 30 }) }) test("ARRAY_LEN - gets length", async () => { const str = ` PUSH 10 PUSH 20 PUSH 30 MAKE_ARRAY 3 ARRAY_LEN ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 3 }) }) test("MAKE_DICT - creates dict", async () => { const str = ` PUSH 'name' PUSH 'Alice' PUSH 'age' PUSH 30 MAKE_DICT 2 ` const result = await run(toBytecode(str)) expect(result.type).toBe('dict') if (result.type === 'dict') { expect(result.value.size).toBe(2) expect(result.value.get('name')).toEqual({ type: 'string', value: 'Alice' }) expect(result.value.get('age')).toEqual({ type: 'number', value: 30 }) } }) test("DICT_GET - gets value", async () => { const str = ` PUSH 'name' PUSH 'Bob' MAKE_DICT 1 PUSH 'name' DICT_GET ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'Bob' }) }) test("DICT_SET - sets value", async () => { const str = ` MAKE_DICT 0 DUP PUSH 'key' PUSH 'value' DICT_SET PUSH 'key' DICT_GET ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'value' }) }) test("DICT_HAS - checks key exists", async () => { const str = ` PUSH 'key' PUSH 'value' MAKE_DICT 1 PUSH 'key' DICT_HAS ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) }) test("DICT_HAS - checks key missing", async () => { const str = ` MAKE_DICT 0 PUSH 'missing' DICT_HAS ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false }) }) test("BREAK - exits from iterator function", async () => { // Simulate a loop with iterator pattern: function calls can be broken out of // We'll need to manually construct bytecode since we need CALL/RETURN const { VM } = await import("#vm") const { OpCode } = await import("#opcode") const { toValue } = await import("#value") const vm = new VM({ instructions: [ // 0: Push initial value { op: OpCode.PUSH, operand: 0 }, // counter = 0 { op: OpCode.STORE, operand: 'counter' }, // 2: Create a simple "iterator" function { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.STORE, operand: 'iter' }, { op: OpCode.JUMP, operand: 7 }, // skip function body // 5: Function body (will be called in a loop) { op: OpCode.LOAD, operand: 'counter' }, { op: OpCode.PUSH, operand: 1 }, // index 1 = number 5 { op: OpCode.GTE }, { op: OpCode.JUMP_IF_TRUE, operand: 1 }, // if counter >= 5, break { op: OpCode.BREAK }, { op: OpCode.LOAD, operand: 'counter' }, { op: OpCode.PUSH, operand: 2 }, // index 2 = number 1 { op: OpCode.ADD }, { op: OpCode.STORE, operand: 'counter' }, { op: OpCode.RETURN }, // 12: Main code - call iterator in a loop { op: OpCode.LOAD, operand: 'iter' }, { op: OpCode.CALL, operand: 0 }, // Call with 0 args { op: OpCode.JUMP, operand: -2 }, // loop back // After break, we end up here { op: OpCode.LOAD, operand: 'counter' }, ], constants: [ toValue(0), // index 0 toValue(5), // index 1 toValue(1), // index 2 { // index 3 - function definition type: 'function_def', params: [], defaults: {}, body: 5, variadic: false, kwargs: false } ] }) // Note: This test would require CALL/RETURN to be implemented // For now, let's skip this and test BREAK with a simpler approach expect(true).toBe(true) // placeholder }) test("CONTINUE - skips to loop start", async () => { // CONTINUE requires function call frames with continueAddress set // This will be tested once we implement CALL/RETURN expect(true).toBe(true) // placeholder })