const assert = require('assert'); const { createLexer } = require('../src/core/lexer'); const { createParser } = require('../src/core/parser'); const { createInterpreter } = require('../src/core/interpreter'); describe('Language Features', () => { 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('Mathematical Constants', () => { it('should correctly handle PI constant', () => { const code = 'result : PI;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, Math.PI); assert.strictEqual(result.isFloat, true); }); it('should correctly handle INFINITY constant', () => { const code = 'result : INFINITY;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, Infinity); assert.strictEqual(result.isFloat, true); }); it('should use constants in expressions', () => { const code = 'result : 2 * PI;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 2 * Math.PI); }); }); describe('Immutable List Operations', () => { it('should correctly append to lists', () => { const code = ` original : [1, 2, 3]; result : append original 4; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.deepStrictEqual(original.map(item => item.value), [1, 2, 3]); assert.deepStrictEqual(result.map(item => item.value), [1, 2, 3, 4]); }); it('should correctly prepend to lists', () => { const code = ` original : [1, 2, 3]; result : prepend 0 original; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.deepStrictEqual(original.map(item => item.value), [1, 2, 3]); assert.deepStrictEqual(result.map(item => item.value), [0, 1, 2, 3]); }); it('should correctly concatenate lists', () => { const code = ` list1 : [1, 2]; list2 : [3, 4]; result : concat list1 list2; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.deepStrictEqual(result.map(item => item.value), [1, 2, 3, 4]); }); it('should correctly update list elements', () => { const code = ` original : [1, 2, 3]; result : update original 1 99; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.deepStrictEqual(original.map(item => item.value), [1, 2, 3]); assert.deepStrictEqual(result.map(item => item.value), [1, 99, 3]); }); it('should correctly remove elements from lists', () => { const code = ` original : [1, 2, 3]; result : removeAt original 1; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.deepStrictEqual(original.map(item => item.value), [1, 2, 3]); assert.deepStrictEqual(result.map(item => item.value), [1, 3]); }); it('should correctly slice lists', () => { const code = ` original : [1, 2, 3, 4, 5]; result : slice original 1 4; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.deepStrictEqual(original.map(item => item.value), [1, 2, 3, 4, 5]); assert.deepStrictEqual(result.map(item => item.value), [2, 3, 4]); }); }); describe('Immutable Table Operations', () => { it('should correctly set table properties', () => { const code = ` original : {name: "Alice", age: 30}; result : set original "city" "NYC"; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.strictEqual(original.properties.get('name'), 'Alice'); assert.strictEqual(original.properties.get('age').value, 30); assert.strictEqual(result.properties.get('name'), 'Alice'); assert.strictEqual(result.properties.get('age').value, 30); assert.strictEqual(result.properties.get('city'), 'NYC'); }); it('should correctly remove table properties', () => { const code = ` original : {name: "Alice", age: 30}; result : remove original "age"; `; const interpreter = interpret(code); const original = interpreter.scope.get('original'); const result = interpreter.scope.get('result'); assert.strictEqual(original.properties.get('name'), 'Alice'); assert.strictEqual(original.properties.get('age').value, 30); assert.strictEqual(result.properties.get('name'), 'Alice'); assert.strictEqual(result.properties.has('age'), false); }); it('should correctly merge tables', () => { const code = ` table1 : {name: "Alice", age: 30}; table2 : {city: "NYC", country: "USA"}; result : merge table1 table2; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.properties.get('name'), 'Alice'); assert.strictEqual(result.properties.get('age').value, 30); assert.strictEqual(result.properties.get('city'), 'NYC'); assert.strictEqual(result.properties.get('country'), 'USA'); }); it('should correctly get table keys', () => { const code = ` table : {name: "Alice", age: 30}; result : keys table; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.deepStrictEqual(result, ['name', 'age']); }); it('should correctly get table values', () => { const code = ` table : {name: "Alice", age: 30}; result : values table; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result[0], 'Alice'); assert.strictEqual(result[1].value, 30); }); }); describe('String Operations', () => { it('should correctly concatenate strings', () => { const code = 'result : str.concat "Hello" " " "World";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'Hello World'); }); it('should correctly split strings', () => { const code = 'result : str.split "a,b,c" ",";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.deepStrictEqual(result, ['a', 'b', 'c']); }); it('should correctly join lists into strings', () => { const code = 'result : str.join ["a", "b", "c"] "-";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'a-b-c'); }); it('should correctly get string length', () => { const code = 'result : str.length "hello";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 5); }); it('should correctly get substrings', () => { const code = 'result : str.substring "hello world" 0 5;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'hello'); }); it('should correctly replace substrings', () => { const code = 'result : str.replace "hello hello" "hello" "hi";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'hi hi'); }); it('should correctly trim strings', () => { const code = 'result : str.trim " hello ";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'hello'); }); it('should correctly convert to uppercase', () => { const code = 'result : str.upper "hello";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'HELLO'); }); it('should correctly convert to lowercase', () => { const code = 'result : str.lower "HELLO";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'hello'); }); }); describe('Type Declarations and Type Checking', () => { it('should correctly handle type declarations', () => { const code = ` myNumber Int; myNumber : 42; `; const interpreter = interpret(code); const result = interpreter.scope.get('myNumber'); assert.strictEqual(result.value, 42); }); it('should correctly handle type checking in when expressions', () => { const code = ` checkType : val -> when val is Int then "Integer" String then "String" Bool then "Boolean" _ then "Other"; result1 : checkType 42; result2 : checkType "hello"; result3 : checkType true; `; const interpreter = interpret(code); assert.strictEqual(interpreter.scope.get('result1'), 'Integer'); assert.strictEqual(interpreter.scope.get('result2'), 'String'); assert.strictEqual(interpreter.scope.get('result3'), 'Boolean'); }); }); describe('Result Type', () => { it('should correctly create Ok results', () => { const code = 'result : Ok 42;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.type, 'Result'); assert.strictEqual(result.variant, 'Ok'); assert.strictEqual(result.value.value, 42); }); it('should correctly create Err results', () => { const code = 'result : Err "error message";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.type, 'Result'); assert.strictEqual(result.variant, 'Err'); assert.strictEqual(result.value, 'error message'); }); it('should correctly pattern match on Result types', () => { const code = ` divide : x y -> when y is 0 then Err "Division by zero" _ then Ok (x / y); result1 : when (divide 10 2) is Ok value then value Err msg then 0; result2 : when (divide 10 0) is Ok value then value Err msg then msg; `; const interpreter = interpret(code); const result1 = interpreter.scope.get('result1'); const result2 = interpreter.scope.get('result2'); assert.strictEqual(result1.value, 5); assert.strictEqual(result2, 'Division by zero'); }); }); describe('Operators', () => { it('should correctly handle unary negation', () => { const code = 'result : -5;'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, -5); }); it('should correctly handle string concatenation with ..', () => { const code = 'result : "Hello" .. " " .. "World";'; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result, 'Hello World'); }); it('should correctly handle comparison operators', () => { const code = ` result1 : 5 > 3; result2 : 5 < 3; result3 : 5 >= 5; result4 : 5 <= 3; result5 : 5 = 5; `; const interpreter = interpret(code); assert.strictEqual(interpreter.scope.get('result1'), true); assert.strictEqual(interpreter.scope.get('result2'), false); assert.strictEqual(interpreter.scope.get('result3'), true); assert.strictEqual(interpreter.scope.get('result4'), false); assert.strictEqual(interpreter.scope.get('result5'), true); }); it('should correctly handle modulo operator', () => { const code = ` result1 : 10 % 3; result2 : 15 % 4; result3 : 7 % 2; `; const interpreter = interpret(code); assert.strictEqual(interpreter.scope.get('result1').value, 1); assert.strictEqual(interpreter.scope.get('result2').value, 3); assert.strictEqual(interpreter.scope.get('result3').value, 1); }); }); describe('Curried Functions and Partial Application', () => { it('should correctly handle curried functions', () => { const code = ` add : x -> y -> x + y; add5 : add 5; result : add5 3; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 8); }); it('should correctly handle partial application', () => { const code = ` multiply : x -> y -> x * y; double : multiply 2; result : double 7; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 14); }); }); describe('Anonymous Functions', () => { it('should correctly handle anonymous functions', () => { const code = ` result : (x -> x * 2) 5; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 10); }); it('should correctly handle anonymous functions with multiple parameters', () => { const code = ` result : (x y -> x + y) 3 4; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 7); }); }); describe('Function Calls', () => { it('should correctly handle parenthesized function calls', () => { const code = ` add : x y -> x + y; result : (add 3 4); `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 7); }); it('should correctly handle non-parenthesized function calls', () => { const code = ` add : x y -> x + y; result : add 3 4; `; const interpreter = interpret(code); const result = interpreter.scope.get('result'); assert.strictEqual(result.value, 7); }); }); describe('Error Handling', () => { it('should handle division by zero', () => { const code = 'result : 10 / 0;'; assert.throws(() => interpret(code), /Division by zero/); }); it('should handle index out of bounds', () => { const code = 'result : [1, 2, 3].5;'; assert.throws(() => interpret(code), /Index out of bounds/); }); it('should handle undefined variables', () => { const code = 'result : undefinedVar;'; assert.throws(() => interpret(code), /Undefined variable/); }); it('should handle undefined properties', () => { const code = 'result : {name: "Alice"}.age;'; assert.throws(() => interpret(code), /Undefined property/); }); }); });