1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
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);
});
});
|