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('EQ - equality with regexes', async () => { const str = ` PUSH /cool/i PUSH /cool/i EQ ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) const str2 = ` PUSH /cool/ PUSH /cool/i EQ ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false }) const str3 = ` PUSH /not-cool/ PUSH /cool/ EQ ` expect(await run(toBytecode(str3))).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 .end POP PUSH 999 .end: ` 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 .end POP PUSH 2 .end: ` 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 .end POP PUSH 2 .end: ` 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 .end POP PUSH 2 .end: ` 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 .end PUSH 1 .end: ` expect(await run(toBytecode(str1))).toEqual({ type: 'number', value: 1 }) // empty string is truthy (unlike JS) const str2 = ` PUSH '' JUMP_IF_FALSE .end PUSH 1 .end: ` expect(await run(toBytecode(str2))).toEqual({ type: 'number', value: 1 }) // false is falsy const str3 = ` PUSH 0 PUSH 0 EQ JUMP_IF_FALSE .end PUSH 999 .end: ` 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("TRY_LOAD - variable found", async () => { const str = ` PUSH 100 STORE count TRY_LOAD count ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 100 }) const str2 = ` PUSH 'Bobby' STORE name TRY_LOAD name ` expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'Bobby' }) }) test("TRY_LOAD - variable missing", async () => { const str = ` PUSH 100 STORE count TRY_LOAD count1 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'count1' }) const str2 = ` PUSH 'Bobby' STORE name TRY_LOAD full-name ` expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: 'full-name' }) }) test("TRY_LOAD - with different value types", async () => { // Array const str1 = ` PUSH 1 PUSH 2 PUSH 3 MAKE_ARRAY #3 STORE arr TRY_LOAD arr ` const result1 = await run(toBytecode(str1)) expect(result1.type).toBe('array') // Dict const str2 = ` PUSH 'key' PUSH 'value' MAKE_DICT #1 STORE dict TRY_LOAD dict ` const result2 = await run(toBytecode(str2)) expect(result2.type).toBe('dict') // Boolean const str3 = ` PUSH true STORE flag TRY_LOAD flag ` expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: true }) // Null const str4 = ` PUSH null STORE empty TRY_LOAD empty ` expect(await run(toBytecode(str4))).toEqual({ type: 'null', value: null }) }) test("TRY_LOAD - in nested scope", async () => { // Function should be able to TRY_LOAD variable from parent scope const str = ` PUSH 42 STORE outer MAKE_FUNCTION () .fn PUSH 0 PUSH 0 CALL HALT .fn: TRY_LOAD outer RETURN ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("TRY_LOAD - missing variable in nested scope returns name", async () => { // If variable doesn't exist in any scope, should return name as string const str = ` PUSH 42 STORE outer MAKE_FUNCTION () .fn PUSH 0 PUSH 0 CALL HALT .fn: TRY_LOAD inner RETURN ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'inner' }) }) test("TRY_LOAD - used for conditional variable existence check", async () => { // Pattern: use TRY_LOAD to check if variable exists and get its value or name const str = ` PUSH 100 STORE count TRY_LOAD count PUSH 'count' EQ ` // Variable exists, so TRY_LOAD returns 100, which != 'count' expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false }) const str2 = ` PUSH 100 STORE count TRY_LOAD missing PUSH 'missing' EQ ` // Variable missing, so TRY_LOAD returns 'missing', which == 'missing' expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: true }) }) test("TRY_LOAD - with function value", async () => { const str = ` MAKE_FUNCTION () .fn STORE myFunc JUMP .skip .fn: PUSH 99 RETURN .skip: TRY_LOAD myFunc ` const result = await run(toBytecode(str)) expect(result.type).toBe('function') }) test("JUMP - relative jump forward", async () => { const str = ` PUSH 1 JUMP .skip PUSH 100 .skip: PUSH 2 ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 2 }) }) test("JUMP - forward jump skips instructions", async () => { // Use forward jump to skip, demonstrating relative addressing const str = ` PUSH 100 JUMP .end PUSH 200 PUSH 300 .end: 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 .skip PUSH 100 .skip: 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 .skip PUSH 100 .skip: ` 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 .skip PUSH 100 .skip: 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("STR_CONCAT - concats together strings", async () => { const str = ` PUSH "Hi " PUSH "friend" PUSH "!" STR_CONCAT #3 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Hi friend!" }) const str2 = ` PUSH "Holy smokes!" PUSH "It's " PUSH "alive!" STR_CONCAT #2 ` expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: "It's alive!" }) const str3 = ` PUSH 1 PUSH " + " PUSH 1 PUSH " = " PUSH 1 PUSH 1 ADD STR_CONCAT #5 ` expect(await run(toBytecode(str3))).toEqual({ type: 'string', value: "1 + 1 = 2" }) }) test("STR_CONCAT - empty concat (count=0)", async () => { const str = ` PUSH "leftover" STR_CONCAT #0 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "" }) }) test("STR_CONCAT - single string", async () => { const str = ` PUSH "hello" STR_CONCAT #1 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "hello" }) }) test("STR_CONCAT - converts numbers to strings", async () => { const str = ` PUSH 42 PUSH 100 PUSH 7 STR_CONCAT #3 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "421007" }) }) test("STR_CONCAT - converts booleans to strings", async () => { const str = ` PUSH "Result: " PUSH true STR_CONCAT #2 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Result: true" }) const str2 = ` PUSH false PUSH " is false" STR_CONCAT #2 ` expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: "false is false" }) }) test("STR_CONCAT - converts null to strings", async () => { const str = ` PUSH "Value: " PUSH null STR_CONCAT #2 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Value: null" }) }) test("STR_CONCAT - mixed types", async () => { const str = ` PUSH "Count: " PUSH 42 PUSH ", Active: " PUSH true PUSH ", Total: " PUSH null STR_CONCAT #6 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Count: 42, Active: true, Total: null" }) }) test("STR_CONCAT - array format", async () => { const bytecode = toBytecode([ ["PUSH", "Hello"], ["PUSH", " "], ["PUSH", "World"], ["STR_CONCAT", 3], ["HALT"] ]) const result = await run(bytecode) expect(result).toEqual({ type: 'string', value: "Hello World" }) }) test("STR_CONCAT - with variables", async () => { const str = ` PUSH "Alice" STORE name PUSH "Hello, " LOAD name PUSH "!" STR_CONCAT #3 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Hello, Alice!" }) }) test("STR_CONCAT - composable (multiple concatenations)", async () => { const str = ` PUSH "Hello" PUSH " " PUSH "World" STR_CONCAT #3 PUSH "!" STR_CONCAT #2 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Hello World!" }) }) test("STR_CONCAT - with emoji and unicode", async () => { const str = ` PUSH "Hello " PUSH "🌍" PUSH "!" STR_CONCAT #3 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Hello 🌍!" }) const str2 = ` PUSH "こんにけは" PUSH "δΈ–η•Œ" STR_CONCAT #2 ` expect(await run(toBytecode(str2))).toEqual({ type: 'string', value: "γ“γ‚“γ«γ‘γ―δΈ–η•Œ" }) }) test("STR_CONCAT - with expressions", async () => { const str = ` PUSH "Result: " PUSH 10 PUSH 5 ADD STR_CONCAT #2 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "Result: 15" }) }) test("STR_CONCAT - large concat", async () => { const str = ` PUSH "a" PUSH "b" PUSH "c" PUSH "d" PUSH "e" PUSH "f" PUSH "g" PUSH "h" PUSH "i" PUSH "j" STR_CONCAT #10 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: "abcdefghij" }) }) test("BREAK - throws error when no break target", async () => { // BREAK requires a break target frame on the call stack // A single function call has no previous frame to mark as break target const bytecode = toBytecode(` MAKE_FUNCTION () .fn PUSH 0 PUSH 0 CALL HALT .fn: BREAK `) try { await run(bytecode) expect(true).toBe(false) // Should not reach here } catch (e: any) { expect(e.message).toContain('no break target found') } }) test("BREAK - exits from nested function call", async () => { // BREAK unwinds to the break target (the outer function's frame) // Main calls outer, outer calls inner, inner BREAKs back to outer's caller (main) const bytecode = toBytecode(` MAKE_FUNCTION () .outer PUSH 0 PUSH 0 CALL PUSH 42 HALT .outer: MAKE_FUNCTION () .inner PUSH 0 PUSH 0 CALL PUSH 99 RETURN .inner: BREAK `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 42 }) }) test("JUMP backward - simple loop", async () => { // Very simple: counter starts at 0, loops 3 times incrementing // On 3rd iteration (counter==3), exits and returns counter const bytecode = toBytecode(` PUSH 0 STORE counter .loop: LOAD counter PUSH 3 EQ JUMP_IF_FALSE .body LOAD counter HALT .body: LOAD counter PUSH 1 ADD STORE counter JUMP .loop `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 3 }) }) test("emoji variable names - string format", async () => { const bytecode = toBytecode(` PUSH 5 STORE πŸ’Ž LOAD πŸ’Ž HALT `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 5 }) }) test("emoji variable names - array format", async () => { const bytecode = toBytecode([ ["PUSH", 100], ["STORE", "πŸ’°"], ["LOAD", "πŸ’°"], ["PUSH", 50], ["ADD"], ["HALT"] ]) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 150 }) }) test("unicode variable names - Japanese", async () => { const bytecode = toBytecode(` PUSH 42 STORE 倉数 LOAD 倉数 HALT `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 42 }) }) test("unicode variable names - Chinese", async () => { const bytecode = toBytecode([ ["PUSH", 888], ["STORE", "ζ•°ε­—"], ["LOAD", "ζ•°ε­—"], ["HALT"] ]) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 888 }) }) test("emoji in function parameters", async () => { const bytecode = toBytecode(` MAKE_FUNCTION (πŸ’Ž 🌟) .add STORE add JUMP .after .add: LOAD πŸ’Ž LOAD 🌟 ADD RETURN .after: LOAD add PUSH 10 PUSH 20 PUSH 2 PUSH 0 CALL HALT `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 30 }) }) test("emoji with defaults and variadic", async () => { const bytecode = toBytecode([ ["MAKE_FUNCTION", ["🎯=100", "...🎨"], ".fn"], ["STORE", "fn"], ["JUMP", ".after"], [".fn:"], ["LOAD", "🎯"], ["RETURN"], [".after:"], ["LOAD", "fn"], ["PUSH", 0], ["PUSH", 0], ["CALL"], ["HALT"] ]) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 100 }) }) test("mixed emoji and regular names", async () => { const bytecode = toBytecode([ ["PUSH", 10], ["STORE", "πŸ’Ž"], ["PUSH", 20], ["STORE", "value"], ["PUSH", 30], ["STORE", "🌟"], ["LOAD", "πŸ’Ž"], ["LOAD", "value"], ["ADD"], ["LOAD", "🌟"], ["ADD"], ["HALT"] ]) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 60 }) }) // ======================================== // RegExp Tests // ======================================== test("RegExp - basic pattern parsing", async () => { const str = ` PUSH /hello/ ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('hello') expect(result.value.flags).toBe('') } }) test("RegExp - pattern with flags", async () => { const str = ` PUSH /test/gi ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('test') expect(result.value.global).toBe(true) expect(result.value.ignoreCase).toBe(true) } }) test("RegExp - multiple flag combinations", async () => { // Test i flag const str1 = ` PUSH /pattern/i ` const result1 = await run(toBytecode(str1)) expect(result1.type).toBe('regex') if (result1.type === 'regex') { expect(result1.value.ignoreCase).toBe(true) } // Test g flag const str2 = ` PUSH /pattern/g ` const result2 = await run(toBytecode(str2)) expect(result2.type).toBe('regex') if (result2.type === 'regex') { expect(result2.value.global).toBe(true) } // Test m flag const str3 = ` PUSH /pattern/m ` const result3 = await run(toBytecode(str3)) expect(result3.type).toBe('regex') if (result3.type === 'regex') { expect(result3.value.multiline).toBe(true) } // Test combined flags const str4 = ` PUSH /pattern/gim ` const result4 = await run(toBytecode(str4)) expect(result4.type).toBe('regex') if (result4.type === 'regex') { expect(result4.value.global).toBe(true) expect(result4.value.ignoreCase).toBe(true) expect(result4.value.multiline).toBe(true) } }) test("RegExp - complex patterns", async () => { // Character class const str1 = ` PUSH /[a-z0-9]+/ ` const result1 = await run(toBytecode(str1)) expect(result1.type).toBe('regex') if (result1.type === 'regex') { expect(result1.value.source).toBe('[a-z0-9]+') } // Quantifiers const str2 = ` PUSH /a{2,4}/ ` const result2 = await run(toBytecode(str2)) expect(result2.type).toBe('regex') if (result2.type === 'regex') { expect(result2.value.source).toBe('a{2,4}') } // Groups and alternation const str3 = ` PUSH /(foo|bar)/ ` const result3 = await run(toBytecode(str3)) expect(result3.type).toBe('regex') if (result3.type === 'regex') { expect(result3.value.source).toBe('(foo|bar)') } // Anchors and special chars const str4 = ` PUSH /^[a-z]+$/ ` const result4 = await run(toBytecode(str4)) expect(result4.type).toBe('regex') if (result4.type === 'regex') { expect(result4.value.source).toBe('^[a-z]+$') } }) test("RegExp - escaping special characters", async () => { const str = ` PUSH /\\d+\\.\\d+/ ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('\\d+\\.\\d+') } }) test("RegExp - store and load", async () => { const str = ` PUSH /test/i STORE pattern LOAD pattern ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('test') expect(result.value.ignoreCase).toBe(true) } }) test("RegExp - TRY_LOAD with regex", async () => { const str = ` PUSH /hello/g STORE regex TRY_LOAD regex ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('hello') expect(result.value.global).toBe(true) } }) test("RegExp - NEQ comparison", async () => { const str = ` PUSH /foo/ PUSH /bar/ NEQ ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) const str2 = ` PUSH /test/i PUSH /test/i NEQ ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false }) }) test("RegExp - is truthy", async () => { // Regex values should be truthy (not null or false) const str = ` PUSH /test/ JUMP_IF_FALSE .end PUSH 42 .end: ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 42 }) }) test("RegExp - NOT returns false (regex is truthy)", async () => { const str = ` PUSH /pattern/ NOT ` expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: false }) }) test("RegExp - in arrays", async () => { const str = ` PUSH /first/ PUSH /second/i PUSH /third/g 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]!.type).toBe('regex') if (result.value[0]!.type === 'regex') { expect(result.value[0]!.value.source).toBe('first') } expect(result.value[1]!.type).toBe('regex') if (result.value[1]!.type === 'regex') { expect(result.value[1]!.value.source).toBe('second') expect(result.value[1]!.value.ignoreCase).toBe(true) } expect(result.value[2]!.type).toBe('regex') if (result.value[2]!.type === 'regex') { expect(result.value[2]!.value.source).toBe('third') expect(result.value[2]!.value.global).toBe(true) } } }) test("RegExp - retrieve from array", async () => { const str = ` PUSH /pattern/i PUSH /test/g MAKE_ARRAY #2 PUSH 1 ARRAY_GET ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('test') expect(result.value.global).toBe(true) } }) test("RegExp - in dicts", async () => { const str = ` PUSH 'email' PUSH /^[a-z@.]+$/i PUSH 'phone' PUSH /\\d{3}-\\d{4}/ MAKE_DICT #2 ` const result = await run(toBytecode(str)) expect(result.type).toBe('dict') if (result.type === 'dict') { expect(result.value.size).toBe(2) const email = result.value.get('email') expect(email?.type).toBe('regex') if (email?.type === 'regex') { expect(email.value.source).toBe('^[a-z@.]+$') expect(email.value.ignoreCase).toBe(true) } const phone = result.value.get('phone') expect(phone?.type).toBe('regex') if (phone?.type === 'regex') { expect(phone.value.source).toBe('\\d{3}-\\d{4}') } } }) test("RegExp - retrieve from dict", async () => { const str = ` PUSH 'pattern' PUSH /test/gim MAKE_DICT #1 PUSH 'pattern' DICT_GET ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('test') expect(result.value.global).toBe(true) expect(result.value.ignoreCase).toBe(true) expect(result.value.multiline).toBe(true) } }) test("RegExp - with STR_CONCAT converts to string", async () => { const str = ` PUSH "Pattern: " PUSH /test/gi STR_CONCAT #2 ` const result = await run(toBytecode(str)) expect(result.type).toBe('string') if (result.type === 'string') { expect(result.value).toBe('Pattern: /test/gi') } }) test("RegExp - multiple regex in STR_CONCAT", async () => { const str = ` PUSH /foo/ PUSH " and " PUSH /bar/i STR_CONCAT #3 ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: '/foo/ and /bar/i' }) }) test("RegExp - DUP with regex", async () => { const str = ` PUSH /pattern/i DUP EQ ` // Same regex duplicated should be equal expect(await run(toBytecode(str))).toEqual({ type: 'boolean', value: true }) }) test("RegExp - empty pattern", async () => { const str = ` PUSH // ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('(?:)') } }) test("RegExp - pattern with forward slashes escaped", async () => { const str = ` PUSH /https:\\/\\// ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('https:\\/\\/') } }) test("RegExp - unicode patterns", async () => { const str = ` PUSH /こんにけは/ ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('こんにけは') } }) test("RegExp - emoji in pattern", async () => { const str = ` PUSH /πŸŽ‰+/ ` const result = await run(toBytecode(str)) expect(result.type).toBe('regex') if (result.type === 'regex') { expect(result.value.source).toBe('πŸŽ‰+') } }) test("RegExp - comparing different regex types", async () => { // Different patterns const str1 = ` PUSH /abc/ PUSH /xyz/ EQ ` expect(await run(toBytecode(str1))).toEqual({ type: 'boolean', value: false }) // Same pattern, different flags const str2 = ` PUSH /test/ PUSH /test/i EQ ` expect(await run(toBytecode(str2))).toEqual({ type: 'boolean', value: false }) // Different order of flags (should be equal) const str3 = ` PUSH /test/ig PUSH /test/gi EQ ` expect(await run(toBytecode(str3))).toEqual({ type: 'boolean', value: true }) }) test("RegExp - with native functions", async () => { const { VM } = await import("#vm") const bytecode = toBytecode(` PUSH "hello world" PUSH /world/ CALL_NATIVE match HALT `) const vm = new VM(bytecode) // Register a native function that takes a string and regex vm.registerFunction('match', (str: string, pattern: RegExp) => { return pattern.test(str) }) const result = await vm.run() expect(result).toEqual({ type: 'boolean', value: true }) }) test("RegExp - native function with regex replacement", async () => { const { VM } = await import("#vm") const bytecode = toBytecode(` PUSH "hello world" PUSH /o/g PUSH "0" CALL_NATIVE replace HALT `) const vm = new VM(bytecode) vm.registerFunction('replace', (str: string, pattern: RegExp, replacement: string) => { return str.replace(pattern, replacement) }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'hell0 w0rld' }) }) test("RegExp - native function extracting matches", async () => { const { VM } = await import("#vm") const bytecode = toBytecode(` PUSH "test123abc456" PUSH /\\d+/g CALL_NATIVE extractNumbers HALT `) const vm = new VM(bytecode) vm.registerFunction('extractNumbers', (str: string, pattern: RegExp) => { return str.match(pattern) || [] }) const result = await vm.run() expect(result.type).toBe('array') if (result.type === 'array') { expect(result.value).toHaveLength(2) expect(result.value[0]).toEqual({ type: 'string', value: '123' }) expect(result.value[1]).toEqual({ type: 'string', value: '456' }) } })