about summary refs log tree commit diff stats
path: root/js/baba-yaga/tests/language_features.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/baba-yaga/tests/language_features.test.js')
-rw-r--r--js/baba-yaga/tests/language_features.test.js450
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