import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { parse } from '../src/parser.js'; import { transpile } from '../src/transpiler.js'; /** * Helper: transpile a Push3 source string, returning the result. * The transpiler primes 8 input slots on the DYADIC stack, so the program * operates on top of those. */ function run(src: string) { const ast = parse(src); return transpile(ast); } // --------------------------------------------------------------------------- // Stack underflow detection // --------------------------------------------------------------------------- describe('stack underflow', () => { it('DYADIC.+ underflows when fewer than 2 extra values beyond inputs', () => { // After 8 input slots are primed, DYADIC.+ pops two. Two pops from 8 is // fine (no underflow). But after popping all 8 inputs + 1 more, underflow. // Program: pop all 8 inputs, then try DYADIC.+ on an empty stack. const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.+ )`; assert.throws(() => run(src), /DYADIC stack underflow/); }); it('DYADIC.SWAP underflows on empty stack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.SWAP )`; assert.throws(() => run(src), /DYADIC stack underflow/); }); it('DYADIC.DUP underflows on empty stack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.DUP )`; assert.throws(() => run(src), /DYADIC stack underflow/); }); it('DYADIC.POP underflows on empty stack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP )`; assert.throws(() => run(src), /DYADIC stack underflow/); }); it('BOOLEAN.NOT underflows on empty boolean stack', () => { const src = '(BOOLEAN.NOT)'; assert.throws(() => run(src), /BOOLEAN stack underflow/); }); it('BOOLEAN.AND underflows on empty boolean stack', () => { const src = '(BOOLEAN.AND)'; assert.throws(() => run(src), /BOOLEAN stack underflow/); }); it('BOOLEAN.OR underflows with only one boolean', () => { const src = '(TRUE BOOLEAN.OR)'; assert.throws(() => run(src), /BOOLEAN stack underflow/); }); it('DYADIC.DEFINE underflows NAME stack', () => { // Push a value on DYADIC but no name on NAME stack const src = '(42 DYADIC.DEFINE)'; assert.throws(() => run(src), /NAME stack underflow/); }); it('DYADIC.> underflows when only one value on stack after clearing', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.> )`; assert.throws(() => run(src), /DYADIC stack underflow/); }); it('EXEC.IF underflows when boolean stack is empty', () => { // EXEC.IF needs a boolean condition — pops from bStack const src = '(EXEC.IF (1) (2))'; assert.throws(() => run(src), /BOOLEAN stack underflow/); }); }); // --------------------------------------------------------------------------- // Stack overflow / excess values — transpiler handles gracefully // --------------------------------------------------------------------------- describe('stack overflow / excess values', () => { it('extra values on DYADIC stack are silently discarded (top 4 taken)', () => { // Push 6 values on top of the 8 inputs = 14 total. Top 4 become outputs. const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 200 300 400 500 600 )`; const result = run(src); // Top of stack = 600 → ci, 500 → anchorShare, 400 → anchorWidth, 300 → discoveryDepth assert.equal(result.ciVar, '600'); assert.equal(result.anchorShareVar, '500'); assert.equal(result.anchorWidthVar, '400'); assert.equal(result.discoveryDepthVar, '300'); }); it('fewer than 4 values uses bear defaults for missing positions', () => { // Clear all 8, push only 1 value const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 42 )`; const result = run(src); assert.equal(result.ciVar, '42'); // Missing positions get bear defaults assert.equal(result.anchorShareVar, '300000000000000000'); assert.equal(result.anchorWidthVar, '100'); assert.equal(result.discoveryDepthVar, '300000000000000000'); }); it('empty program (no instructions) uses inputs as outputs', () => { // No instructions at all — the 8 primed input slots remain. // Top 4 = inputs[0] through inputs[3]. const src = '()'; const result = run(src); assert.equal(result.ciVar, 'uint256(inputs[0].mantissa)'); assert.equal(result.anchorShareVar, 'uint256(inputs[1].mantissa)'); assert.equal(result.anchorWidthVar, 'uint256(inputs[2].mantissa)'); assert.equal(result.discoveryDepthVar, 'uint256(inputs[3].mantissa)'); }); }); // --------------------------------------------------------------------------- // EXEC.IF branching — balanced branches // --------------------------------------------------------------------------- describe('EXEC.IF balanced branches', () => { it('emits if/else for simple balanced branches', () => { // Clear stack, push condition value, compare, then branch const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF (10 20 30 40) (1 2 3 4) )`; const result = run(src); // Both branches push 4 values; the result depends on the condition // The output should contain if/else structure const body = result.functionBody.join('\n'); assert.ok(body.includes('if ('), 'should contain if statement'); // Result vars should be used for differing positions assert.ok(result.ciVar !== undefined); assert.ok(result.anchorShareVar !== undefined); }); it('both branches produce same value → no result variable needed', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF (10 20 30 99) (1 2 3 99) )`; const result = run(src); // Both branches have 99 at bottom (same position from before) — doesn't need result var for that // But top 3 differ → need result vars for those const body = result.functionBody.join('\n'); assert.ok(body.includes('if ('), 'should emit if/else'); }); }); // --------------------------------------------------------------------------- // EXEC.IF branching — unbalanced branches // --------------------------------------------------------------------------- describe('EXEC.IF unbalanced branches', () => { it('true branch pushes more DYADIC values than false branch → error', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF (10 20) (10) )`; assert.throws(() => run(src), /DYADIC stack depth mismatch/); }); it('false branch pushes more DYADIC values than true branch → error', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF (10) (10 20) )`; assert.throws(() => run(src), /DYADIC stack depth mismatch/); }); it('error message includes both branch depths', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF (10 20 30) (10) )`; assert.throws(() => run(src), /true branch produces 3.*false branch produces 1/); }); it('one branch pushes extra, the other is no-op → depth mismatch error', () => { // True branch pushes 2 extra values, false branch leaves stack unchanged const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 50 100 50 DYADIC.> EXEC.IF (99 88) () )`; assert.throws(() => run(src), /DYADIC stack depth mismatch/); }); it('EXEC.IF with missing branches throws', () => { // EXEC.IF needs exactly 2 subsequent items const src = `( TRUE EXEC.IF (1) )`; assert.throws(() => run(src), /EXEC\.IF: missing branches/); }); it('BOOLEAN stack depth mismatch between branches → error', () => { // true branch pushes a boolean comparison, false branch does not const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 20 30 40 TRUE EXEC.IF (10 5 DYADIC.>) () )`; assert.throws(() => run(src), /BOOLEAN stack depth mismatch/); }); }); // --------------------------------------------------------------------------- // EXEC.IF — nested branching // --------------------------------------------------------------------------- describe('EXEC.IF nested branches', () => { it('nested EXEC.IF produces valid output', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 100 50 DYADIC.> EXEC.IF ( 100 80 DYADIC.> EXEC.IF (1 2 3 4) (5 6 7 8) ) (10 20 30 40) )`; const result = run(src); const body = result.functionBody.join('\n'); // Should contain nested if blocks const ifCount = (body.match(/if \(/g) || []).length; assert.ok(ifCount >= 2, `expected at least 2 if statements, got ${ifCount}`); }); }); // --------------------------------------------------------------------------- // Arithmetic and comparisons // --------------------------------------------------------------------------- describe('arithmetic operations', () => { it('DYADIC.+ produces addition expression', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 20 DYADIC.+ )`; const result = run(src); assert.equal(result.ciVar, '(10 + 20)'); }); it('DYADIC.- produces subtraction expression', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 3 DYADIC.- )`; const result = run(src); assert.equal(result.ciVar, '(10 - 3)'); }); it('DYADIC.* produces multiplication expression', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 5 6 DYADIC.* )`; const result = run(src); assert.equal(result.ciVar, '(5 * 6)'); }); it('DYADIC./ produces safe division expression', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 3 DYADIC./ )`; const result = run(src); assert.equal(result.ciVar, '(3 == 0 ? 0 : 10 / 3)'); }); it('DYADIC.> produces comparison on boolean stack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 5 DYADIC.> EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(10 > 5)'), 'condition should contain (10 > 5)'); }); it('DYADIC.< produces less-than comparison', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 3 9 DYADIC.< EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(3 < 9)')); }); it('DYADIC.>= produces >= comparison', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 3 3 DYADIC.>= EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(3 >= 3)')); }); it('DYADIC.<= produces <= comparison', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 5 10 DYADIC.<= EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(5 <= 10)')); }); }); // --------------------------------------------------------------------------- // Boolean operations // --------------------------------------------------------------------------- describe('boolean operations', () => { it('BOOLEAN.NOT negates a boolean', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP TRUE BOOLEAN.NOT EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('!(true)')); }); it('BOOLEAN.AND combines two booleans', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP TRUE FALSE BOOLEAN.AND EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(true && false)')); }); it('BOOLEAN.OR combines two booleans', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP TRUE FALSE BOOLEAN.OR EXEC.IF (1 2 3 4) (5 6 7 8) )`; const result = run(src); const body = result.functionBody.join('\n'); assert.ok(body.includes('(true || false)')); }); }); // --------------------------------------------------------------------------- // Name binding and DEFINE // --------------------------------------------------------------------------- describe('DYADIC.DEFINE and name binding', () => { it('binds a name and retrieves it', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 42 FOO DYADIC.DEFINE FOO )`; const result = run(src); // FOO should be bound to 'foo' variable, and ci should reference it assert.equal(result.ciVar, 'foo'); const body = result.functionBody.join('\n'); assert.ok(body.includes('uint256 foo = uint256(42);')); }); it('bound names push to dStack, not nameStack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 99 X DYADIC.DEFINE X X DYADIC.+ )`; const result = run(src); assert.equal(result.ciVar, '(x + x)'); }); }); // --------------------------------------------------------------------------- // DYADIC.DUP and DYADIC.SWAP // --------------------------------------------------------------------------- describe('DYADIC.DUP and DYADIC.SWAP', () => { it('DYADIC.DUP duplicates top of stack', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 42 DYADIC.DUP DYADIC.+ )`; const result = run(src); // DUP materialises into dup0, then + is (dup0 + dup0) assert.ok(result.ciVar.includes('dup0')); const body = result.functionBody.join('\n'); assert.ok(body.includes('uint256 dup0 = uint256(42);')); }); it('DYADIC.SWAP exchanges top two values', () => { const src = `( DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP 10 20 DYADIC.SWAP )`; const result = run(src); // After SWAP: bottom=20, top=10 assert.equal(result.ciVar, '10'); assert.equal(result.anchorShareVar, '20'); }); }); // --------------------------------------------------------------------------- // Unsupported instruction // --------------------------------------------------------------------------- describe('unsupported instructions', () => { it('throws on unknown instruction', () => { const src = '(CODE.NOOP)'; assert.throws(() => run(src), /Unsupported instruction: CODE.NOOP/); }); }); // --------------------------------------------------------------------------- // Integration: optimizer_seed.push3-style programs // --------------------------------------------------------------------------- describe('integration: bear/bull program', () => { it('simple bear/bull program transpiles without error', () => { // Simplified version of the optimizer_v3.push3 structure const src = `( PERCENTAGESTAKED DYADIC.DEFINE TAXRATE DYADIC.DEFINE DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP DYADIC.POP PERCENTAGESTAKED 100 DYADIC.* 1000000000000000000 DYADIC./ STAKED DYADIC.DEFINE STAKED 91 DYADIC.> EXEC.IF ( 1000000000000000000 20 1000000000000000000 0 ) ( 300000000000000000 100 300000000000000000 0 ) )`; const result = run(src); // Should produce valid output with if/else const body = result.functionBody.join('\n'); assert.ok(body.includes('if (')); // Output vars should be result variables (rN) since branches differ assert.ok(result.ciVar !== undefined); assert.ok(result.functionBody.length > 0); }); });