diff options
Diffstat (limited to 'js/baba-yaga/tests/language_features.test.js')
-rw-r--r-- | js/baba-yaga/tests/language_features.test.js | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/js/baba-yaga/tests/language_features.test.js b/js/baba-yaga/tests/language_features.test.js new file mode 100644 index 0000000..0550f70 --- /dev/null +++ b/js/baba-yaga/tests/language_features.test.js @@ -0,0 +1,450 @@ +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/); + }); + }); +}); \ No newline at end of file |