const assert = require('assert'); const { createLexer } = require('../src/core/lexer'); const { createParser } = require('../src/core/parser'); const { createInterpreter } = require('../src/core/interpreter'); function interpret(code) { const lexer = createLexer(code); const tokens = lexer.allTokens(); const parser = createParser(tokens); const ast = parser.parse(); const interpreter = createInterpreter(ast); interpreter.interpret(); return interpreter; } describe('Math Namespace', () => { it('should support basic numeric ops: abs/floor/ceil/round/trunc', () => { const code = ` a : math.abs -3; b : math.floor 2.9; c : math.ceil 2.1; d : math.round 2.5; e : math.trunc -2.9; `; const itp = interpret(code); assert.strictEqual(itp.scope.get('a').value, 3); assert.strictEqual(itp.scope.get('b').value, 2); assert.strictEqual(itp.scope.get('c').value, 3); assert.strictEqual(itp.scope.get('d').value, 3); assert.strictEqual(itp.scope.get('e').value, -2); }); it('should support min/max/clamp', () => { const code = ` a : math.min 10 3; b : math.max 10 3; c : math.clamp 15 0 10; `; const itp = interpret(code); assert.strictEqual(itp.scope.get('a').value, 3); assert.strictEqual(itp.scope.get('b').value, 10); assert.strictEqual(itp.scope.get('c').value, 10); }); it('should support pow/sqrt/exp/log (with domain checks)', () => { const code = ` p : math.pow 2 8; s : math.sqrt 9; e : math.exp 1; l : math.log 2.718281828; `; const itp = interpret(code); assert.strictEqual(itp.scope.get('p').value, 256); assert.strictEqual(itp.scope.get('s').value, 3); assert.ok(Math.abs(itp.scope.get('e').value - Math.E) < 1e-9); assert.ok(Math.abs(itp.scope.get('l').value - 1) < 1e-6); assert.throws(() => interpret('x : math.sqrt -1;')); assert.throws(() => interpret('x : math.log 0;')); }); it('should support trig and conversions', () => { const code = ` c : math.cos 0; s : math.sin 0; a : math.atan2 1 1; d : math.deg PI; r : math.rad 180; `; const itp = interpret(code); assert.ok(Math.abs(itp.scope.get('c').value - 1) < 1e-12); assert.ok(Math.abs(itp.scope.get('s').value - 0) < 1e-12); assert.ok(Math.abs(itp.scope.get('a').value - Math.PI/4) < 1e-6); assert.ok(Math.abs(itp.scope.get('d').value - 180) < 1e-12); assert.ok(Math.abs(itp.scope.get('r').value - Math.PI) < 1e-6); }); it('should support random and randomInt', () => { const code = ` one : math.randomInt 1 1; `; const itp = interpret(code); assert.strictEqual(itp.scope.get('one').value, 1); }); it('should accept Int where Float is expected, and Number as supertype', () => { const code = ` // Float-typed parameter accepts Int (widening) f : (x: Float) -> Float -> x; r1 : f 2; // Number supertype accepts Int or Float idN : (x: Number) -> Number -> x; n1 : idN 2; n2 : idN 2.5; // Return type Number accepts either Int or Float (use dummy arg since zero-arg call syntax is not supported) retN1 : (z: Int) -> Number -> 3; retN2 : (z: Int) -> Number -> 3.5; v1 : retN1 0; v2 : retN2 0; `; const itp = interpret(code); assert.strictEqual(itp.scope.get('r1').value, 2); assert.strictEqual(itp.scope.get('n1').value, 2); assert.ok(Math.abs(itp.scope.get('n2').value - 2.5) < 1e-12); assert.strictEqual(itp.scope.get('v1').value, 3); assert.ok(Math.abs(itp.scope.get('v2').value - 3.5) < 1e-12); }); });