diff --git a/Documentation/app/docs/functions/page.tsx b/Documentation/app/docs/functions/page.tsx index ff7edb0..e12f20b 100644 --- a/Documentation/app/docs/functions/page.tsx +++ b/Documentation/app/docs/functions/page.tsx @@ -17,6 +17,48 @@ export default function Functions() { or "task"). Functions are first-class values and support closures.

+

New Utility Methods

+ +

+ BanglaCode now includes additional JavaScript-style helpers for arrays, strings, number parsing, + and URI encoding. +

+ + 5; }); // 8 +dhoro idx = khojo_index([1, 3, 8, 10], kaj(x) { ferao x > 5; }); // 2 +dhoro last = khojo_shesh([1, 3, 8, 10], kaj(x) { ferao x % 2 == 0; }); // 10 + +// String helpers +ache_text("banglacode", "code"); // sotti +shuru_diye("banglacode", "bang"); // sotti +shesh_diye("banglacode", "code"); // sotti +baro("ha", 3); // "hahaha" +text_at("bangla", -1); // "a" + +// Number + URI helpers +purno_sonkhya("42"); // 42 +doshomik_sonkhya("3.14abc"); // 3.14 +sonkhya_na("abc"); // sotti +uri_ongsho_encode("hello world"); // "hello%20world" + +// Date + Regex helpers +dhoro ts = tarikh_ekhon(); +tarikh_format(ts, "2006-01-02"); +regex_test("[a-z]+", "bangla"); // sotti +regex_search("la", "bangla"); // 4 + +// Object helpers +nijer_ache({a: 1}, "a"); // sotti +jora_theke([["x", 10], ["y", 20]]); // {x: 10, y: 20} +ekoi_ki(1, 1); // sotti + +// Timers +dhoro id = setInterval(kaj() { dekho("tick"); }, 1000); +clearInterval(id);`} + /> +

Defining Functions

diff --git a/Documentation/app/docs/syntax/page.tsx b/Documentation/app/docs/syntax/page.tsx index fd8ff53..5da1ff3 100644 --- a/Documentation/app/docs/syntax/page.tsx +++ b/Documentation/app/docs/syntax/page.tsx @@ -24,7 +24,13 @@ export default function Syntax() { { k: "dhoro", m: "hold/var", e: "var/let" }, { k: "jodi", m: "if", e: "if" }, { k: "nahole", m: "else", e: "else" }, + { k: "jotokkhon", m: "while", e: "while" }, + { k: "do", m: "do once then loop", e: "do...while" }, { k: "ghuriye", m: "loop", e: "for" }, + { k: "of", m: "iterate values", e: "of" }, + { k: "in", m: "property/index check", e: "in" }, + { k: "instanceof", m: "instance check", e: "instanceof" }, + { k: "delete", m: "delete key/index", e: "delete" }, { k: "kaj", m: "work", e: "function" }, { k: "ferao", m: "return", e: "return" }, { k: "dekho", m: "see/print", e: "print" }, @@ -47,6 +53,18 @@ export default function Syntax() { dhoro isStudent = sotti; // true +

New Core Syntax

+
+ do {"{"}
+   dekho("run once");
+ {"}"} jotokkhon (mittha);

+ dhoro obj = {"{"}a: 1{"}"};
+ dekho("a" in obj);
+ delete obj.a;

+ dhoro double = x => x * 2;
+ dhoro [a, b] = [10, 20]; +
+
${4:0};", + "});", + "$0" + ], + "description": "Find first matching element in array" + }, + "Array FlatMap": { + "prefix": "somtol-manchitro", + "body": [ + "dhoro ${1:result} = somtol_manchitro(${2:arr}, kaj(${3:x}) {", + "\tferao [${3:x}, ${3:x} * ${4:2}];", + "});", + "$0" + ], + "description": "Map then flatten one level" + }, + "String Includes": { + "prefix": "ache-text", + "body": [ + "jodi (ache_text(${1:text}, ${2:query})) {", + "\t${3:// found}", + "}", + "$0" + ], + "description": "Check if string contains substring" + }, + "Parse Int": { + "prefix": "purno-sonkhya", + "body": [ + "dhoro ${1:value} = purno_sonkhya(${2:text});", + "$0" + ], + "description": "Parse integer from string" + }, + "URI Encode Component": { + "prefix": "uri-encode", + "body": [ + "dhoro ${1:encoded} = uri_ongsho_encode(${2:text});", + "$0" + ], + "description": "Encode URI component" + }, + "Do-While Loop": { + "prefix": "do-jotokkhon", + "body": [ + "do {", + "\t${1:// code}", + "} jotokkhon (${2:condition});", + "$0" + ], + "description": "Do-while loop" + }, + "Delete Property": { + "prefix": "delete", + "body": [ + "delete ${1:obj}.${2:prop};", + "$0" + ], + "description": "Delete object property" + }, + "Array Flat": { + "prefix": "somtol", + "body": [ + "dhoro ${1:flat} = somtol(${2:arr}, ${3:1});", + "$0" + ], + "description": "Flatten nested array" + }, + "String Compare": { + "prefix": "tulona-text", + "body": [ + "dhoro ${1:cmp} = tulona_text(${2:left}, ${3:right});", + "$0" + ], + "description": "Compare two strings" + }, + "Arrow Function": { + "prefix": "arrow", + "body": [ + "dhoro ${1:fn} = ${2:x} => ${3:x};", + "$0" + ], + "description": "Arrow function with implicit return" + }, + "For Of Loop": { + "prefix": "for-of", + "body": [ + "ghuriye (${1:item} of ${2:array}) {", + "\t${3:// code}", + "}", + "$0" + ], + "description": "for...of loop" + }, + "For In Loop": { + "prefix": "for-in", + "body": [ + "ghuriye (${1:key} in ${2:obj}) {", + "\t${3:// code}", + "}", + "$0" + ], + "description": "for...in loop" + }, + "Date Now": { + "prefix": "date-now", + "body": [ + "dhoro ${1:ts} = tarikh_ekhon();", + "$0" + ], + "description": "Current timestamp in milliseconds" + }, + "Regex Test": { + "prefix": "regex-test", + "body": [ + "dhoro ${1:ok} = regex_test(\"${2:pattern}\", ${3:text});", + "$0" + ], + "description": "Regex test against text" + }, + "Object HasOwn": { + "prefix": "nijer-ache", + "body": [ + "dhoro ${1:has} = nijer_ache(${2:obj}, ${3:\"key\"});", + "$0" + ], + "description": "Check own property existence on map" + }, + "Array Destructuring": { + "prefix": "dhoro-array", + "body": [ + "dhoro [${1:a}, ${2:b}] = ${3:arr};", + "$0" + ], + "description": "Array destructuring declaration" + }, + "Object Destructuring": { + "prefix": "dhoro-obj", + "body": [ + "dhoro {${1:x}, ${2:y}} = ${3:obj};", + "$0" + ], + "description": "Object destructuring declaration" + }, + "Set Timeout": { + "prefix": "set-timeout", + "body": [ + "setTimeout(kaj() {", + "\t${1:// callback}", + "}, ${2:1000});", + "$0" + ], + "description": "Schedule callback once" + }, + "Set Interval": { + "prefix": "set-interval", + "body": [ + "dhoro ${1:id} = setInterval(kaj() {", + "\t${2:// repeated callback}", + "}, ${3:1000});", + "$0" + ], + "description": "Schedule repeated callback" } } diff --git a/Extension/syntaxes/banglacode.tmLanguage.json b/Extension/syntaxes/banglacode.tmLanguage.json index dea6181..dae7944 100644 --- a/Extension/syntaxes/banglacode.tmLanguage.json +++ b/Extension/syntaxes/banglacode.tmLanguage.json @@ -109,7 +109,7 @@ }, { "name": "keyword.control.loop.js", - "match": "\\b(jotokkhon|ghuriye|thamo|chharo)\\b" + "match": "\\b(jotokkhon|ghuriye|thamo|chharo|do)\\b" }, { "name": "storage.type.function.js", @@ -163,6 +163,10 @@ "name": "keyword.operator.logical.js", "match": "\\b(ebong|ba|na)\\b" }, + { + "name": "keyword.operator.js", + "match": "\\b(in|of|instanceof|delete)\\b" + }, { "name": "keyword.control.switch.js", "match": "\\b(bikolpo|khetre|manchito)\\b" @@ -177,7 +181,7 @@ }, { "name": "keyword.control.loop.js", - "match": "\\b(jotokkhon|ghuriye|thamo|chharo)\\b" + "match": "\\b(jotokkhon|ghuriye|thamo|chharo|do)\\b" }, { "name": "keyword.control.trycatch.js", @@ -206,6 +210,10 @@ { "name": "keyword.operator.assignment.js", "match": "=" + }, + { + "name": "keyword.operator.arrow.js", + "match": "=>" } ] }, @@ -219,18 +227,34 @@ "name": "support.function.js", "match": "\\b(dhoron|lipi|sonkha|dorghyo)\\b" }, + { + "name": "support.function.js", + "match": "\\b(purno_sonkhya|doshomik_sonkhya|sonkhya_na|sonkhya_shimito|uri_encode|uri_decode|uri_ongsho_encode|uri_ongsho_decode)\\b" + }, + { + "name": "support.function.js", + "match": "\\b(tarikh_ekhon|tarikh_parse|tarikh_format|regex_test|regex_match|regex_match_all|regex_search|regex_replace|match|matchAll|search)\\b" + }, { "name": "support.function.array.js", "match": "\\b(manchitro|chhanno|sonkuchito|proti)\\b" }, + { + "name": "support.function.array.js", + "match": "\\b(khojo_prothom|khojo_index|khojo_shesh|khojo_shesh_index|prottek|kono|somtol_manchitro|array_at|shesh_index_of|joro_array|somtol|sonkuchito_dan)\\b" + }, { "name": "support.function.object.js", - "match": "\\b(maan|jora|mishra)\\b" + "match": "\\b(maan|jora|mishra|nijer_ache|jora_theke|ekoi_ki|notun_map|joma)\\b" }, { "name": "support.function.js", "match": "\\b(boroHater|chotoHater|chhanto|bhag|joro|khojo|angsho|bodlo)\\b" }, + { + "name": "support.function.js", + "match": "\\b(ache_text|shuru_diye|shesh_diye|baro|agey_bhoro|pichoney_bhoro|okkhor|text_at|okkhor_code|codepoint_at|tulona_text|shadharon_text|chhanto_shuru|chhanto_shesh|shesh_khojo)\\b" + }, { "name": "support.function.js", "match": "\\b(dhokao|berKoro|kato|ulto|saja|ache|chabi)\\b" @@ -241,7 +265,7 @@ }, { "name": "support.function.js", - "match": "\\b(somoy|ghum|nao|bondho|poro|lekho)\\b" + "match": "\\b(somoy|ghum|nao|bondho|poro|lekho|setTimeout|setInterval|clearTimeout|clearInterval)\\b" }, { "name": "support.function.js", diff --git a/FEATURE_LIST.md b/FEATURE_LIST.md new file mode 100644 index 0000000..ef456f9 --- /dev/null +++ b/FEATURE_LIST.md @@ -0,0 +1,413 @@ +# BanglaCode Feature List - Implemented Features + +**Last Updated**: February 2026 (v7.0.8 - Verified Batch 4) + +> Verification note: Parts of older Phase 2 entries were out of sync with the codebase. +> The items listed under "v7.0.5 - Verified Batch 1" below are now code-verified and tested. + +This document lists all features that are **currently implemented** in BanglaCode, organized by category and implementation phase. + +--- + +## Table of Contents + +1. [v7.0.8 - Verified Batch 4](#v708---verified-batch-4) +1. [v7.0.7 - Verified Batch 3](#v707---verified-batch-3) +1. [v7.0.6 - Verified Batch 2](#v706---verified-batch-2) +1. [v7.0.5 - Verified Batch 1](#v705---verified-batch-1) +1. [Phase 1 Features (v7.0.3)](#phase-1-features-v703) +2. [Core Language Features](#core-language-features) +3. [Data Types & Literals](#data-types--literals) +4. [Operators](#operators) +5. [Control Flow](#control-flow) +6. [Functions](#functions) +7. [Array Methods](#array-methods) +8. [String Methods](#string-methods) +9. [Object Methods](#object-methods) +10. [Built-in Functions](#built-in-functions) +11. [OOP Features](#oop-features) +12. [Async/Await](#asyncawait) +13. [Module System](#module-system) +14. [Error Handling](#error-handling) +15. [I/O Operations](#io-operations) +16. [Networking](#networking) +17. [Database Operations](#database-operations) + +--- + +## v7.0.8 - Verified Batch 4 + +### ✅ COMPLETED: Maturity Pack Extensions + +| Feature | BanglaCode | Status | +|---------|------------|--------| +| Destructuring (array) | `dhoro [a, b] = arr` | ✅ DONE | +| Destructuring (object) | `dhoro {x, y} = obj` | ✅ DONE | +| Multi-parameter arrows | `(a, b) => ...`, `() => ...` | ✅ DONE | +| Timers | `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` | ✅ DONE | +| Regex wrappers + flags | `match`, `matchAll`, `search`, optional flags in `regex_*` | ✅ DONE | + +--- + +## v7.0.7 - Verified Batch 3 + +### ✅ COMPLETED: Maturity Syntax + APIs + +| Feature | BanglaCode | Status | +|---------|------------|--------| +| Arrow functions | `x => expr`, `x => { ... }` | ✅ DONE | +| for...of loops | `ghuriye (x of iterable) { ... }` | ✅ DONE | +| for...in loops | `ghuriye (k in target) { ... }` | ✅ DONE | +| Date core | `tarikh_ekhon`, `tarikh_parse`, `tarikh_format` | ✅ DONE | +| RegExp core | `regex_test`, `regex_match`, `regex_match_all`, `regex_search`, `regex_replace` | ✅ DONE | +| Object utilities | `nijer_ache`, `jora_theke`, `ekoi_ki`, `notun_map`, `joma` | ✅ DONE (freeze semantic partial) | + +--- + +## v7.0.6 - Verified Batch 2 + +### ✅ COMPLETED: Core Syntax Operators + Loop + +| Feature | Syntax | JS Equivalent | Status | +|---------|--------|---------------|--------| +| do...while | `do { ... } jotokkhon (cond);` | `do { ... } while (cond)` | ✅ DONE | +| in operator | `"key" in obj` | `'key' in obj` | ✅ DONE | +| instanceof operator | `obj instanceof Class` | `obj instanceof Class` | ✅ DONE | +| delete operator | `delete obj.key` | `delete obj.key` | ✅ DONE | + +### ✅ COMPLETED: Additional Utility Parity (Batch 2B) + +| Feature | BanglaCode | JS Equivalent | Status | +|---------|------------|---------------|--------| +| reduceRight | `sonkuchito_dan()` | `arr.reduceRight()` | ✅ DONE | +| concat | `joro_array()` | `arr.concat()` | ✅ DONE | +| flat | `somtol()` | `arr.flat()` | ✅ DONE | +| codePointAt | `codepoint_at()` | `str.codePointAt()` | ✅ DONE | +| localeCompare | `tulona_text()` | `str.localeCompare()` | ✅ DONE | +| normalize | `shadharon_text()` | `str.normalize()` | ✅ DONE | + +--- + +## v7.0.5 - Verified Batch 1 + +### ✅ COMPLETED: Additional Array Methods + +| Function | Bengali | JS Equivalent | Status | +|----------|---------|---------------|--------| +| find | `khojo_prothom()` | `arr.find()` | ✅ DONE | +| findIndex | `khojo_index()` | `arr.findIndex()` | ✅ DONE | +| findLast | `khojo_shesh()` | `arr.findLast()` | ✅ DONE | +| findLastIndex | `khojo_shesh_index()` | `arr.findLastIndex()` | ✅ DONE | +| every | `prottek()` | `arr.every()` | ✅ DONE | +| some | `kono()` | `arr.some()` | ✅ DONE | +| flatMap | `somtol_manchitro()` | `arr.flatMap()` | ✅ DONE | +| at | `array_at()` | `arr.at()` | ✅ DONE | +| lastIndexOf | `shesh_index_of()` | `arr.lastIndexOf()` | ✅ DONE | + +### ✅ COMPLETED: Additional String Methods + +| Function | Bengali | JS Equivalent | Status | +|----------|---------|---------------|--------| +| includes | `ache_text()` | `str.includes()` | ✅ DONE | +| startsWith | `shuru_diye()` | `str.startsWith()` | ✅ DONE | +| endsWith | `shesh_diye()` | `str.endsWith()` | ✅ DONE | +| repeat | `baro()` | `str.repeat()` | ✅ DONE | +| padStart | `agey_bhoro()` | `str.padStart()` | ✅ DONE | +| padEnd | `pichoney_bhoro()` | `str.padEnd()` | ✅ DONE | +| charAt | `okkhor()` | `str.charAt()` | ✅ DONE | +| at | `text_at()` | `str.at()` | ✅ DONE | +| charCodeAt | `okkhor_code()` | `str.charCodeAt()` | ✅ DONE | +| trimStart | `chhanto_shuru()` | `str.trimStart()` | ✅ DONE | +| trimEnd | `chhanto_shesh()` | `str.trimEnd()` | ✅ DONE | +| lastIndexOf | `shesh_khojo()` | `str.lastIndexOf()` | ✅ DONE | + +### ✅ COMPLETED: Numeric + URI Globals + +| Function | Bengali | JS Equivalent | Status | +|----------|---------|---------------|--------| +| parseInt | `purno_sonkhya()` | `parseInt()` | ✅ DONE | +| parseFloat | `doshomik_sonkhya()` | `parseFloat()` | ✅ DONE | +| isNaN | `sonkhya_na()` | `isNaN()` | ✅ DONE | +| isFinite | `sonkhya_shimito()` | `isFinite()` | ✅ DONE | +| encodeURI | `uri_encode()` | `encodeURI()` | ✅ DONE | +| decodeURI | `uri_decode()` | `decodeURI()` | ✅ DONE | +| encodeURIComponent | `uri_ongsho_encode()` | `encodeURIComponent()` | ✅ DONE | +| decodeURIComponent | `uri_ongsho_decode()` | `decodeURIComponent()` | ✅ DONE | + +--- + +## Phase 1 Features (v7.0.3) + +### ✅ COMPLETED: Array Methods (4 new methods) + +**Bengali Names & Descriptions:** + +| Method | Bengali | Purpose | Example | Status | +|--------|---------|---------|---------|--------| +| **map()** | `manchitro()` | Transform each element | `manchitro(arr, kaj(x) { ferao x * 2; })` | ✅ DONE | +| **filter()** | `chhanno()` | Filter elements by condition | `chhanno(arr, kaj(x) { ferao x > 5; })` | ✅ DONE | +| **reduce()** | `sonkuchito()` | Reduce to single value | `sonkuchito(arr, kaj(a,b) { ferao a+b; })` | ✅ DONE | +| **forEach()** | `proti()` | Execute for each element | `proti(arr, kaj(x) { dekho(x); })` | ✅ DONE | + +**Features:** +- ✅ Callback receives `(element, index, array)` parameters +- ✅ Performance optimized with pre-allocated arrays +- ✅ Error propagation from callbacks +- ✅ Works with nested arrays and objects +- ✅ Comprehensive test coverage (25+ tests) + +--- + +### ✅ COMPLETED: Object Methods (3 new methods) + +**Bengali Names & Descriptions:** + +| Method | Bengali | Purpose | Example | Status | +|--------|---------|---------|---------|--------| +| **values()** | `maan()` | Extract object values | `maan(obj)` → array of values | ✅ DONE | +| **entries()** | `jora()` | Extract key-value pairs | `jora(obj)` → array of [key, value] pairs | ✅ DONE | +| **assign()** | `mishra()` | Merge objects (in-place) | `mishra(target, source1, source2)` | ✅ DONE | + +**Features:** +- ✅ `maan()` - Returns array of all object values +- ✅ `jora()` - Returns array of [key, value] pairs +- ✅ `mishra()` - Merges objects (mutates target, supports multiple sources) +- ✅ Proper error handling for non-object arguments +- ✅ Comprehensive test coverage (20+ tests) + +--- + +### ✅ COMPLETED: Switch/Case Control Flow + +**Bengali Keywords:** + +| Keyword | Bengali | Meaning | English Equivalent | +|---------|---------|---------|-------------------| +| **switch** | `bikolpo` | বিকল্প (alternative) | switch | +| **case** | `khetre` | ক্ষেত্রে (in case of) | case | +| **default** | `manchito` | মানচিত্র (default/standard) | default | +| **break** | `thamo` | থামো (stop) | break | + +**Syntax & Features:** +```bangla +bikolpo (expression) { + khetre value1: { /* code */ } + khetre value2: { /* code */ } + manchito: { /* default code */ } +} +``` + +- ✅ Type-safe comparison using `objectsEqual()` +- ✅ Break statement (`thamo`) support +- ✅ Default case handling +- ✅ Works with numbers, strings, booleans, null +- ✅ Comprehensive test coverage (15+ tests) +- ✅ No fall-through (each case is independent) + +--- + +### ✅ COMPLETED: Template Literals + +**Syntax:** + +```bangla +`Hello ${name}!` +`Result: ${5 + 3}` +`Array length: ${dorghyo(arr)}` +``` + +**Features:** +- ✅ Backtick syntax with `${}` interpolation +- ✅ Support for expressions inside `${}` +- ✅ Function calls within expressions +- ✅ Nested objects/arrays with balanced brace counting +- ✅ Empty template support +- ✅ Special character handling (Unicode, newlines) +- ✅ Comprehensive test coverage (18+ tests) +- ✅ Error propagation from invalid expressions + +--- + +## Summary Statistics + +### Phase 1 Implementation Summary +- ✅ **9 features** completed +- ✅ **4 array methods** (manchitro, chhanno, sonkuchito, proti) +- ✅ **3 object methods** (maan, jora, mishra) +- ✅ **1 control flow** (bikolpo/khetre/manchito with thamo) +- ✅ **1 string feature** (template literals with ${} interpolation) +- ✅ **78+ test cases** written +- ✅ **All tests passing** (291/291) +- ✅ **VS Code extension updated** with syntax highlighting and snippets +- ✅ **Documentation website updated** with examples and usage + +--- + +## Phase 2 Features (v7.0.4) + +### ✅ COMPLETED: Core Language Essentials (Phase 2A - 7 features) + +| Feature | Bengali | Purpose | Syntax | Status | +|---------|---------|---------|--------|--------| +| **Ternary operator** | — | Inline conditionals | `condition ? trueVal : falseVal` | ✅ DONE | +| **Optional chaining** | — | Safe property access | `obj?.prop`, `obj?.[expr]` | ✅ DONE | +| **Nullish coalescing** | — | Default for null | `left ?? right` | ✅ DONE | +| **Array find** | `khojo_prothom()` | Find first element | `khojo_prothom(arr, kaj(x) { ferao x > 5; })` | ✅ DONE | +| **Array findIndex** | `khojo_index()` | Find first index | `khojo_index(arr, kaj(x) { ferao x > 5; })` | ✅ DONE | +| **Array every** | `prottek()` | All pass test | `prottek(arr, kaj(x) { ferao x > 0; })` | ✅ DONE | +| **Array some** | `kono()` | Any pass test | `kono(arr, kaj(x) { ferao x > 10; })` | ✅ DONE | + +--- + +### ✅ COMPLETED: String & Array Utility Methods (Phase 2B - 14 functions) + +| Function | Bengali | JS Equivalent | Status | +|----------|---------|---------------|--------| +| **String includes** | `ache_text()` | `str.includes()` | ✅ DONE | +| **String startsWith** | `shuru_diye()` | `str.startsWith()` | ✅ DONE | +| **String endsWith** | `shesh_diye()` | `str.endsWith()` | ✅ DONE | +| **String repeat** | `baro()` | `str.repeat()` | ✅ DONE | +| **String padStart** | `agey_bhoro()` | `str.padStart()` | ✅ DONE | +| **String padEnd** | `pichoney_bhoro()` | `str.padEnd()` | ✅ DONE | +| **String charAt** | `okkhor()` | `str.charAt()` | ✅ DONE | +| **String trimStart** | `chhanto_shuru()` | `str.trimStart()` | ✅ DONE | +| **String trimEnd** | `chhanto_shesh()` | `str.trimEnd()` | ✅ DONE | +| **Array concat** | `joro_array()` | `arr.concat()` | ✅ DONE | +| **Array flat** | `somtol()` | `arr.flat()` | ✅ DONE | +| **parseInt** | `purno_sonkhya()` | `parseInt()` | ✅ DONE | +| **parseFloat** | `doshomik_sonkhya()` | `parseFloat()` | ✅ DONE | +| **isNaN** | `sonkhya_na()` | `isNaN()` | ✅ DONE | + +--- + +### ✅ COMPLETED: OOP Enhancements (Phase 2C - 3 features) + +| Feature | Bengali | JS Equivalent | Syntax | Status | +|---------|---------|---------------|--------|--------| +| **Class inheritance** | `theke` | `extends` | `sreni Child theke Parent { }` | ✅ DONE | +| **Super calls** | `upor` | `super` | `upor.method()` | ✅ DONE | +| **Static methods** | `sthir kaj` | `static` | `sthir kaj method() { }` | ✅ DONE | + +--- + +### ✅ COMPLETED: HTTP Full Methods (Phase 2D - 4 methods) + +| Method | Bengali | Usage | Status | +|--------|---------|-------|--------| +| **HTTP POST** | `pathao_post()` | `pathao_post(url, body, headers)` | ✅ DONE | +| **HTTP PUT** | `pathao_put()` | `pathao_put(url, body, headers)` | ✅ DONE | +| **HTTP DELETE** | `pathao_delete()` | `pathao_delete(url)` | ✅ DONE | +| **HTTP PATCH** | `pathao_patch()` | `pathao_patch(url, body, headers)` | ✅ DONE | + +--- + +### ✅ COMPLETED: Crypto Module (Phase 2E - 7 functions) + +| Function | Bengali | Purpose | Status | +|----------|---------|---------|--------| +| **SHA-256** | `hash_sha256()` | Hash string to hex | ✅ DONE | +| **SHA-512** | `hash_sha512()` | Hash string to hex | ✅ DONE | +| **MD5** | `hash_md5()` | Hash string to hex | ✅ DONE | +| **HMAC-SHA256** | `hmac_sha256()` | Keyed hash | ✅ DONE | +| **Random bytes** | `lotto_bytes()` | Crypto-secure random | ✅ DONE | +| **Base64 encode** | `base64_encode()` | Encode to base64 | ✅ DONE | +| **Base64 decode** | `base64_decode()` | Decode from base64 | ✅ DONE | + +--- + +### Test Coverage +- Phase 1: 78+ tests +- Phase 2: 54 new tests +- Total: **345 passing tests** + +### Code Quality Metrics +- File size compliance: All files under 500 lines (ideal < 300) +- No code violations: All CLAUDE.md rules followed +- Performance: No regression in existing features +- 0 test regressions across all phases + +--- + +## Version History + +### v7.0.4 (Phase 2 Complete) ✅ +- ✅ Ternary operator, optional chaining (`?.`), nullish coalescing (`??`) +- ✅ Array search methods: `khojo_prothom`, `khojo_index`, `prottek`, `kono` +- ✅ 9 string methods: `ache_text`, `shuru_diye`, `shesh_diye`, `baro`, `agey_bhoro`, `pichoney_bhoro`, `okkhor`, `chhanto_shuru`, `chhanto_shesh` +- ✅ Array utilities: `joro_array` (concat), `somtol` (flat) +- ✅ Number parsing: `purno_sonkhya`, `doshomik_sonkhya`, `sonkhya_na` +- ✅ OOP: class inheritance (`theke`), super (`upor`), static methods (`sthir kaj`) +- ✅ HTTP methods: `pathao_post`, `pathao_put`, `pathao_delete`, `pathao_patch` +- ✅ Crypto: `hash_sha256`, `hash_sha512`, `hash_md5`, `hmac_sha256`, `lotto_bytes`, `base64_encode`, `base64_decode` +- ✅ 54 new tests, 345 total, 0 regressions + +### v7.0.3 (Phase 1 Complete) ✅ +- ✅ Added 4 array methods (manchitro, chhanno, sonkuchito, proti) +- ✅ Added 3 object methods (maan, jora, mishra) +- ✅ Added switch/case control flow (bikolpo/khetre/manchito/thamo) +- ✅ Added template literals (backtick syntax with ${} interpolation) +- ✅ 78+ new test cases with 100% pass rate +- ✅ Updated VS Code extension with syntax highlighting and snippets +- ✅ Updated documentation website with examples for all features +- ✅ All code follows CLAUDE.md standards (file size, architecture, performance) + +### Earlier Versions +- v7.0.2: Network and database features +- v7.0.1: Core language features and basic OOP support + +--- + +## Implementation Details + +### Array Methods Implementation +File: `src/evaluator/builtins/builtins_array.go` (320 lines) + +**Features:** +- `manchitro()`: Transform elements with callback(element, index, array) +- `chhanno()`: Filter elements with boolean callback +- `sonkuchito()`: Reduce to single value with optional initial value +- `proti()`: Iterate and execute for each element +- Pre-allocated arrays for optimal performance +- Error propagation from callbacks + +### Object Methods Implementation +File: `src/evaluator/builtins/builtins_object.go` (85 lines) + +**Features:** +- `maan()`: Extract and return array of object values +- `jora()`: Extract and return array of [key, value] pairs +- `mishra()`: Merge multiple objects into target (mutates target) +- Proper error handling for non-object arguments +- Maintains insertion order + +### Switch/Case Statement Implementation +Files: +- `src/parser/statements.go`: Parse switch syntax +- `src/evaluator/evaluator.go`: Evaluate switch cases +- `src/lexer/token.go`: Token definitions + +**Features:** +- `bikolpo` (switch), `khetre` (case), `manchito` (default), `thamo` (break) +- Type-safe comparison using objectsEqual() +- No fall-through between cases +- Support for all data types (numbers, strings, booleans, null) + +### Template Literals Implementation +Files: +- `src/lexer/lexer.go`: Parse backtick syntax +- `src/evaluator/expressions.go`: Evaluate template expressions +- `src/lexer/token.go`: Template token definition + +**Features:** +- Backtick (`) syntax for template strings +- ${expression} interpolation with balanced brace counting +- Nested objects/arrays support +- Function calls within expressions +- Error propagation from invalid expressions + +--- + +**Document Purpose**: Complete inventory of Phase 1+2 completed features for BanglaCode v7.0.4 + +**Last Updated**: February 2026 +**Status**: Phase 2 Implementation Complete ✅ diff --git a/MISSING.md b/MISSING.md new file mode 100644 index 0000000..b84e7d3 --- /dev/null +++ b/MISSING.md @@ -0,0 +1,990 @@ +# BanglaCode vs JavaScript/Node.js - Missing Features Analysis + +**Document Purpose**: Comprehensive comparison of BanglaCode with JavaScript (ES6+) and Node.js, identifying all missing features. + +**Note**: This document lists features that exist in JavaScript/Node.js but are **NOT** implemented in BanglaCode. +Items marked with v7.0.5 were verified and implemented in the latest batch. + +--- + +## Table of Contents + +1. [Core Language Features](#core-language-features) +2. [Data Structures & Types](#data-structures--types) +3. [Built-in Objects & Functions](#built-in-objects--functions) +4. [Array Methods](#array-methods) +5. [String Methods](#string-methods) +6. [Object Methods](#object-methods) +7. [Number/Math Methods](#numbermath-methods) +8. [Error Handling](#error-handling) +9. [Control Flow](#control-flow) +10. [OOP Features](#oop-features) +11. [Node.js Specific](#nodejs-specific) +12. [Module System & Package Management](#module-system--package-management) +13. [HTTP & Networking](#http--networking) +14. [File System](#file-system) +15. [Cryptography & Security](#cryptography--security) +16. [Testing & Development Tools](#testing--development-tools) +17. [Advanced Features](#advanced-features) +18. [Deprecated but Still Used](#deprecated-but-still-used) + +--- + +## Core Language Features + +### Missing 17+ Core Features + +| Feature | JS/Node | BanglaCode | Status | Impact | +|---------|---------|-----------|--------|--------| +| **do...while loop** | ✅ | ✅ (as `do { } jotokkhon (...)`) | Implemented v7.0.6 | Loop syntax - Medium priority | +| **Destructuring (arrays)** | ✅ | ✅ | Implemented v7.0.8 | `dhoro [a, b] = arr` | +| **Destructuring (objects)** | ✅ | ✅ | Implemented v7.0.8 | `dhoro {x, y} = obj` | +| **Arrow functions** | ✅ | ✅ (as `x => expr`, `(a,b)=>expr`, `()=>expr`) | Implemented v7.0.8 | Mature support | +| **for...in loop** | ✅ | ✅ (as `ghuriye (k in obj)`) | Implemented v7.0.7 | Medium priority | +| **for...of loop** | ✅ | ✅ (as `ghuriye (x of arr)`) | Implemented v7.0.7 | High priority | +| **Generators** | ✅ | ❌ | Missing | `function* name() { yield value; }` - Low priority | +| **Iterators** | ✅ | ❌ | Missing | `[Symbol.iterator]()` - Low priority | +| **Symbols** | ✅ | ❌ | Missing | Unique identifiers - Low priority | +| **BigInt** | ✅ | ❌ | Missing | Large numbers: `123n` - Low priority | +| **Optional chaining** | ✅ | ✅ | Implemented | `obj?.prop`, `obj?.[expr]` - v7.0.4 | +| **Nullish coalescing** | ✅ | ✅ | Implemented | `value ?? default` - v7.0.4 | +| **Logical assignment** | ✅ | ❌ | Missing | `a ??= b`, `a &&= b`, `a ||= b` - Low priority | +| **Ternary operator** | ✅ | ✅ | Implemented | `condition ? trueVal : falseVal` - v7.0.4 | +| **Comma operator** | ✅ | ❌ | Missing | `expr1, expr2` - Very low priority | +| **typeof operator** | ✅ | ✅ (as `dhoron`) | Partial | Works but different naming | +| **instanceof operator** | ✅ | ✅ (`instanceof`) | Implemented v7.0.6 | `obj instanceof Class` - Medium priority | +| **in operator** | ✅ | ✅ (`in`) | Implemented v7.0.6 | `'prop' in obj` - Low priority | +| **delete operator** | ✅ | ✅ (`delete`) | Implemented v7.0.6 | `delete obj.prop` - Medium priority | +| **Comma in variable declaration** | ✅ | ❌ | Missing | `let a = 1, b = 2;` - Low priority | + +--- + +## Data Structures & Types + +### Missing 10+ Data Structures + +| Feature | JS/Node | BanglaCode | Details | +|---------|---------|-----------|---------| +| **Date object** | ✅ | ✅ (core via `tarikh_*`) | Implemented v7.0.7 | Date/time handling - **HIGH PRIORITY** | +| **RegExp (full)** | ✅ | ⚠️ Partial (`regex_*`) | Core implemented v7.0.7 | Regular expressions - **HIGH PRIORITY** | +| **Map (ES6)** | ✅ | ❌ | Key-value with non-string keys - **HIGH PRIORITY** | +| **Set** | ✅ | ❌ | Unique values collection - **MEDIUM PRIORITY** | +| **WeakMap** | ✅ | ❌ | Weak reference keys - Low priority | +| **WeakSet** | ✅ | ❌ | Weak reference values - Low priority | +| **TypedArray** | ✅ | ❌ | Float32Array, Int8Array, etc. - Low priority | +| **ArrayBuffer** | ✅ | ❌ | Binary data buffer - Low priority | +| **DataView** | ✅ | ❌ | Buffer view - Low priority | +| **Intl objects** | ✅ | ❌ | Internationalization (Intl.Collator, etc.) - Low priority | +| **Temporal API** | ✅ (ES2026) | ❌ | Modern date/time - Low priority | +| **Promise as explicit creation** | ✅ | ❌ | `new Promise((resolve, reject) => {})` - **MEDIUM PRIORITY** | + +--- + +## Built-in Objects & Functions + +### Missing 20+ Global Functions/Objects + +| Feature | Type | JS/Node | BanglaCode | Impact | +|---------|------|---------|-----------|--------| +| **Math object** | Object | ✅ | ❌ (has functions) | Math.PI, Math.E unavailable - **HIGH** | +| **Number object** | Object | ✅ | ❌ | Number.MAX_SAFE_INTEGER, etc. - **HIGH** | +| **Boolean object** | Object | ✅ | ❌ | Low priority | +| **console object** | Object | ✅ | ✅ (as `dekho`) | Partial - only basic logging | +| **parseInt()** | Function | ✅ | ✅ (as `purno_sonkhya`) | Implemented v7.0.4 | +| **parseFloat()** | Function | ✅ | ✅ (as `doshomik_sonkhya`) | Implemented v7.0.4 | +| **isNaN()** | Function | ✅ | ✅ (as `sonkhya_na`) | Implemented v7.0.4 | +| **isFinite()** | Function | ✅ | ✅ (as `sonkhya_shimito`) | Implemented v7.0.5 | +| **encodeURI()** | Function | ✅ | ✅ (as `uri_encode`) | Implemented v7.0.5 | +| **decodeURI()** | Function | ✅ | ✅ (as `uri_decode`) | Implemented v7.0.5 | +| **encodeURIComponent()** | Function | ✅ | ✅ (as `uri_ongsho_encode`) | Implemented v7.0.5 | +| **decodeURIComponent()** | Function | ✅ | ✅ (as `uri_ongsho_decode`) | Implemented v7.0.5 | +| **atob()** | Function | ✅ | ✅ (as `base64_decode`) | Implemented v7.0.4 | +| **btoa()** | Function | ✅ | ✅ (as `base64_encode`) | Implemented v7.0.4 | +| **eval()** | Function | ✅ | ❌ | Execute code (intentionally missing, good) | Security feature | +| **TextEncoder** | Object | ✅ | ❌ | UTF-8 encoding - Low priority | +| **TextDecoder** | Object | ✅ | ❌ | UTF-8 decoding - Low priority | +| **Function.prototype.bind()** | Function | ✅ | ❌ | Bind context - **MEDIUM** | +| **Function.prototype.call()** | Function | ✅ | ❌ | Call with context - **MEDIUM** | +| **Function.prototype.apply()** | Function | ✅ | ❌ | Apply with context - **MEDIUM** | + +--- + +## Array Methods + +### Missing 25+ Array Methods + +**HIGH PRIORITY (Core iteration methods):** + +| Method | Purpose | Example | Impact | +|--------|---------|---------|--------| +| **reduceRight()** | Reduce right to left | `arr.reduceRight((a, b) => a + b)` | ✅ Implemented as `sonkuchito_dan()` v7.0.6 | +| **find()** | Find first element | `arr.find(x => x > 5)` | ✅ Implemented as `khojo_prothom()` v7.0.4 | +| **findIndex()** | Find first index | `arr.findIndex(x => x > 5)` | ✅ Implemented as `khojo_index()` v7.0.4 | +| **findLast()** | Find last element | `arr.findLast(x => x > 5)` | ✅ Implemented as `khojo_shesh()` v7.0.5 | +| **findLastIndex()** | Find last index | `arr.findLastIndex(x => x > 5)` | ✅ Implemented as `khojo_shesh_index()` v7.0.5 | +| **every()** | All pass test | `arr.every(x => x > 0)` | ✅ Implemented as `prottek()` v7.0.4 | +| **some()** | Any pass test | `arr.some(x => x > 10)` | ✅ Implemented as `kono()` v7.0.4 | + +**MEDIUM PRIORITY (Utility methods):** + +| Method | Purpose | Example | +|--------|---------|---------| +| **concat()** | Merge arrays | ✅ Implemented as `joro_array()` v7.0.6 | +| **flat()** | Flatten nested | ✅ Implemented as `somtol()` v7.0.6 | +| **flatMap()** | Map then flatten | ✅ Implemented as `somtol_manchitro()` v7.0.5 | +| **splice()** | Add/remove anywhere | `arr.splice(1, 2, 'a', 'b')` | +| **at()** | Access with negative | ✅ Implemented as `array_at()` v7.0.5 | +| **toReversed()** | Non-mutating reverse | `arr.toReversed()` | +| **toSorted()** | Non-mutating sort | `arr.toSorted()` | +| **toSpliced()** | Non-mutating splice | `arr.toSpliced(1, 2)` | +| **with()** | Non-mutating replace | `arr.with(0, 'new')` | +| **includes()** | Check existence | Has `ache()` - already exists ✅ | +| **indexOf()** | Find index | Has `index_of()` - already exists ✅ | +| **lastIndexOf()** | Find last index | ✅ Implemented as `shesh_index_of()` v7.0.5 | +| **join()** | Join to string | Has `joro()` - already exists ✅ | + +--- + +## String Methods + +### Missing 40+ String Methods + +**HIGH PRIORITY:** + +| Method | Purpose | Example | Status | +|--------|---------|---------|--------| +| **match()** | Find matches | `str.match(/pattern/)` | ✅ Implemented as `regex_match()` v7.0.7 | +| **matchAll()** | All matches | `str.matchAll(/pattern/g)` | ✅ Implemented as `regex_match_all()` v7.0.7 | +| **search()** | Find position | `str.search(/pattern/)` | ✅ Implemented as `regex_search()` v7.0.7 | +| **replace()** | Replace all | Has `bodlo()` - **already exists ✅** | | +| **replaceAll()** | Replace all | Has `bodlo()` - **already exists ✅** | | +| **charAt()** | Get character | ✅ Implemented as `okkhor()` v7.0.4 | +| **charCodeAt()** | Get char code | `str.charCodeAt(0)` | ✅ Implemented as `okkhor_code()` v7.0.5 | +| **codePointAt()** | Get code point | `str.codePointAt(0)` | ✅ Implemented as `codepoint_at()` v7.0.6 | +| **concat()** | Concatenate | `str1.concat(str2)` | ❌ (use + instead) | +| **repeat()** | Repeat string | ✅ Implemented as `baro()` v7.0.4 | +| **padStart()** | Pad start | ✅ Implemented as `agey_bhoro()` v7.0.4 | +| **padEnd()** | Pad end | ✅ Implemented as `pichoney_bhoro()` v7.0.4 | +| **slice()** | Extract portion | Has `angsho()` - **already exists ✅** | | +| **substring()** | Extract portion | Has `angsho()` - similar ✅ | | +| **substr()** | Extract portion (deprecated) | Has `angsho()` - similar ✅ | | +| **at()** | Access negative index | `str.at(-1)` gets last | ✅ Implemented as `text_at()` v7.0.5 | +| **localeCompare()** | Compare strings | `str1.localeCompare(str2)` | ✅ Implemented as `tulona_text()` v7.0.6 | +| **normalize()** | Unicode normalize | `str.normalize()` | ✅ Implemented as `shadharon_text()` v7.0.6 | +| **toLowerCase()** | Has `chotoHater()` - **already exists ✅** | | +| **toUpperCase()** | Has `boroHater()` - **already exists ✅** | | +| **toLocaleLowerCase()** | Locale lowercase | ❌ | +| **toLocaleUpperCase()** | Locale uppercase | ❌ | +| **trim()** | Has `chhanto()` - **already exists ✅** | | +| **trimStart()** | Trim start | ✅ Implemented as `chhanto_shuru()` v7.0.4 | +| **trimEnd()** | Trim end | ✅ Implemented as `chhanto_shesh()` v7.0.4 | +| **indexOf()** | Has `khojo()` - **already exists ✅** | | +| **lastIndexOf()** | Find last | ✅ Implemented as `shesh_khojo()` v7.0.5 | +| **includes()** | Check existence | ✅ Implemented as `ache_text()` v7.0.4 | +| **startsWith()** | Starts with | ✅ Implemented as `shuru_diye()` v7.0.4 | +| **endsWith()** | Ends with | ✅ Implemented as `shesh_diye()` v7.0.4 | +| **split()** | Has `bhag()` - **already exists ✅** | | +| **toString()** | Convert to string | ❌ | +| **valueOf()** | Primitive value | ❌ | + +**HTML Methods (Deprecated, not critical):** +- `anchor()`, `big()`, `blink()`, `bold()`, `fixed()`, `fontcolor()`, `fontsize()`, `italics()`, `link()`, `small()`, `strike()`, `sub()`, `sup()` + +--- + +## Object Methods + +### Missing 24 Object Static Methods + +| Method | Purpose | Example | Priority | +|--------|---------|---------|----------| +| **Object.create()** | Create with prototype | `Object.create(proto)` | ✅ Implemented as `notun_map()` v7.0.7 | +| **Object.defineProperty()** | Define descriptor | `Object.defineProperty(obj, 'prop', {})` | **HIGH** | +| **Object.defineProperties()** | Define multiple | `Object.defineProperties(obj, {})` | **HIGH** | +| **Object.freeze()** | Make immutable | `Object.freeze(obj)` | ⚠️ Partial as `joma()` v7.0.7 | +| **Object.seal()** | Prevent add/remove | `Object.seal(obj)` | **MEDIUM** | +| **Object.preventExtensions()** | Prevent add | `Object.preventExtensions(obj)` | Low | +| **Object.fromEntries()** | Create from pairs | `Object.fromEntries(entries)` | ✅ Implemented as `jora_theke()` v7.0.7 | +| **Object.keys()** | Get keys | Has `chabi()` - **already exists ✅** | | +| **Object.getPrototypeOf()** | Get prototype | `Object.getPrototypeOf(obj)` | Low | +| **Object.setPrototypeOf()** | Set prototype | `Object.setPrototypeOf(obj, proto)` | Low | +| **Object.getOwnPropertyDescriptor()** | Get descriptor | `Object.getOwnPropertyDescriptor(obj, 'prop')` | Low | +| **Object.getOwnPropertyDescriptors()** | Get all descriptors | `Object.getOwnPropertyDescriptors(obj)` | Low | +| **Object.getOwnPropertyNames()** | All properties | `Object.getOwnPropertyNames(obj)` | Low | +| **Object.getOwnPropertySymbols()** | Symbol props | `Object.getOwnPropertySymbols(obj)` | Low | +| **Object.hasOwn()** | Check property | `Object.hasOwn(obj, 'prop')` | ✅ Implemented as `nijer_ache()` v7.0.7 | +| **Object.is()** | Strict equality | `Object.is(a, b)` | ✅ Implemented as `ekoi_ki()` v7.0.7 | +| **Object.isFrozen()** | Is frozen | `Object.isFrozen(obj)` | Low | +| **Object.isSealed()** | Is sealed | `Object.isSealed(obj)` | Low | +| **Object.isExtensible()** | Can extend | `Object.isExtensible(obj)` | Low | +| **Object.groupBy()** | Group elements | `Object.groupBy(arr, callback)` | **MEDIUM** | + +--- + +## Number/Math Methods + +### Missing Math & Number Features + +**Math Object Properties (MISSING):** + +| Property | Value | Status | +|----------|-------|--------| +| `Math.PI` | 3.14159... | ❌ (has individual functions) | +| `Math.E` | 2.71828... | ❌ | +| `Math.LN2` | ln(2) | ❌ | +| `Math.LN10` | ln(10) | ❌ | +| `Math.LOG2E` | log₂(e) | ❌ | +| `Math.LOG10E` | log₁₀(e) | ❌ | +| `Math.SQRT1_2` | √(1/2) | ❌ | +| `Math.SQRT2` | √2 | ❌ | + +**Math Methods (MISSING):** + +| Method | Purpose | Example | Status | +|--------|---------|---------|--------| +| **trigonometric** | sin, cos, tan, asin, acos, atan, atan2 | ❌ | +| **hyperbolic** | sinh, cosh, tanh, asinh, acosh, atanh | ❌ | +| **logarithmic** | log, log10, log2, log1p | ❌ | +| **exponential** | exp, expm1 | ❌ | +| **utilities** | imul, clz32, fround, f16round, hypot | ❌ | + +**Number Object (MISSING):** + +| Item | Value/Purpose | Status | +|------|---------------|--------| +| `Number.MAX_SAFE_INTEGER` | 2^53 - 1 | ❌ | +| `Number.MIN_SAFE_INTEGER` | -(2^53 - 1) | ❌ | +| `Number.MAX_VALUE` | ~1.8e308 | ❌ | +| `Number.MIN_VALUE` | ~5e-324 | ❌ | +| `Number.POSITIVE_INFINITY` | Infinity | ❌ | +| `Number.NEGATIVE_INFINITY` | -Infinity | ❌ | +| `Number.NaN` | NaN value | ❌ | +| `Number.EPSILON` | 2.2204e-16 | ❌ | +| `Number.isFinite()` | Check finite | ❌ | +| `Number.isInteger()` | Check integer | ❌ | +| `Number.isNaN()` | Check NaN | ❌ | +| `Number.isSafeInteger()` | Check safe range | ❌ | +| `Number.parseFloat()` | Parse to float | ❌ | +| `Number.parseInt()` | Parse to int | ❌ | + +--- + +## Error Handling + +### Missing 15+ Error Features + +| Feature | JS/Node | BanglaCode | Impact | +|---------|---------|-----------|--------| +| **Custom error classes** | ✅ | ❌ | `class MyError extends Error {}` - **MEDIUM** | +| **Error.captureStackTrace()** | ✅ | ❌ | Capture stack - Low | +| **Error.stack** | ✅ | ❌ | Stack trace access - **MEDIUM** | +| **TypeError** | ✅ | ❌ | Type error type - **MEDIUM** | +| **ReferenceError** | ✅ | ❌ | Undefined var error - **MEDIUM** | +| **RangeError** | ✅ | ❌ | Out of range error - **MEDIUM** | +| **SyntaxError** | ✅ | ❌ | Parse error - Low | +| **URIError** | ✅ | ❌ | Invalid URI error - Low | +| **AggregateError** | ✅ | ❌ | Multiple errors - Low | +| **EvalError** (deprecated) | ✅ | ❌ | Not used - Very low | +| **Error cause** | ✅ | ❌ | `new Error('msg', { cause: err })` - **MEDIUM** | +| **Stack trace parsing** | ✅ | ❌ | Parse error stacks - Low | +| **Error context** | ✅ | ❌ | Provide error context - Low | + +--- + +## Control Flow + +### Missing 1 Control Flow Feature + +| Feature | JS/Node | BanglaCode | Priority | +|---------|---------|-----------|----------| +| **do...while loop** | ✅ | ✅ (Implemented v7.0.6) | Completed | +| **Labeled statements** | ✅ | ❌ | Low | + +--- + +## OOP Features + +### Missing 12+ OOP Features + +| Feature | JS/Node | BanglaCode | Impact | +|---------|---------|-----------|--------| +| **extends keyword** | ✅ | ✅ (as `theke`) | Class inheritance - **Implemented v7.0.4** | +| **super keyword** | ✅ | ✅ (as `upor`) | Call parent - **Implemented v7.0.4** | +| **static methods** | ✅ | ✅ (as `sthir kaj`) | `static method() {}` - **Implemented v7.0.4** | +| **static properties** | ✅ | ❌ | `static prop = value` - **HIGH** | +| **getters** | ✅ | ❌ | `get prop() {}` - **MEDIUM** | +| **setters** | ✅ | ❌ | `set prop(val) {}` - **MEDIUM** | +| **private fields** | ✅ | ❌ | `#field` - **MEDIUM** | +| **private methods** | ✅ | ❌ | `#method()` - **MEDIUM** | +| **protected fields** | ✅ (TypeScript) | ❌ | Protected access - Low | +| **Abstract classes** | ✅ (TypeScript) | ❌ | Abstract methods - Low | +| **Interfaces** | ✅ (TypeScript) | ❌ | Type definitions - Low | +| **Mixins pattern** | ✅ | ❌ | Object.assign pattern - Low | +| **Method binding** | ✅ | ❌ | Arrow vs regular - Low | +| **Prototype chain** | ✅ | ❌ | Manual prototypes - Low | + +--- + +## Node.js Specific + +### Missing 50+ Node.js Features + +#### Global Objects (Missing) + +| Object | Purpose | Status | +|--------|---------|--------| +| **global** | Global object | ❌ | +| **globalThis** | Global object (ES2020) | ❌ | +| **__dirname** | Current directory | ❌ | +| **__filename** | Current file | ❌ | +| **process** | Process object | Partial ✅ (some functions) | + +#### Callback-based APIs (Missing) + +| API | Purpose | Status | +|-----|---------|--------| +| **fs callbacks** | File operations with callbacks | ❌ (only sync/async) | +| **http callbacks** | HTTP server with callbacks | Partial (event-based) | +| **net callbacks** | TCP/UDP callbacks | Partial | +| **child_process callbacks** | Process management | Partial | + +#### Timers (Missing) + +| Timer | Purpose | Status | +|-------|---------|--------| +| **setTimeout()** | Delayed execution | ✅ Implemented v7.0.8 | +| **setInterval()** | Repeated execution | ✅ Implemented v7.0.8 | +| **setImmediate()** | Next phase execution | ❌ | +| **process.nextTick()** | Next tick execution | ❌ | +| **clearTimeout()** | Clear timeout | ✅ Implemented v7.0.8 | +| **clearInterval()** | Clear interval | ✅ Implemented v7.0.8 | + +#### Streams (Missing - CRITICAL) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **ReadableStream** | Read data efficiently | **CRITICAL** | +| **WritableStream** | Write data efficiently | **CRITICAL** | +| **TransformStream** | Transform stream | **HIGH** | +| **DuplexStream** | Read and write | **HIGH** | +| **stream.pipe()** | Connect streams | **CRITICAL** | +| **stream.unpipe()** | Disconnect | High | +| **Backpressure handling** | Flow control | **CRITICAL** | +| **Stream events** | on('data'), on('end') | Partial | +| **fs.createReadStream()** | File streaming | ❌ | +| **fs.createWriteStream()** | File streaming | ❌ | + +#### EventEmitter (Missing - CRITICAL) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **EventEmitter class** | Event-driven architecture | **CRITICAL** | +| **emitter.on()** | Listen to event | **CRITICAL** | +| **emitter.once()** | Listen once | **CRITICAL** | +| **emitter.emit()** | Emit event | **CRITICAL** | +| **emitter.off()** | Remove listener | **CRITICAL** | +| **emitter.removeAllListeners()** | Remove all | High | +| **emitter.listeners()** | Get listeners | Medium | +| **emitter.eventNames()** | Get all events | Medium | + +#### Worker Threads (Missing) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **new Worker()** | Create worker thread | ❌ | +| **parentPort** | Communicate with parent | ❌ | +| **workerData** | Pass data to worker | ❌ | +| **SharedArrayBuffer** | Shared memory | ❌ | +| **Worker pool** | Multiple workers | ❌ | + +#### Cluster (Missing) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **cluster.isMaster** | Check if master | ❌ | +| **cluster.isWorker** | Check if worker | ❌ | +| **cluster.fork()** | Create worker | ❌ | +| **cluster.workers** | All workers | ❌ | +| **Load balancing** | Round-robin | ❌ | + +#### Buffer Module (Missing - CRITICAL for binary) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **Buffer.alloc()** | Allocate buffer | **CRITICAL** | +| **Buffer.allocUnsafe()** | Allocate fast | **CRITICAL** | +| **Buffer.from()** | Create from data | **CRITICAL** | +| **Buffer.concat()** | Merge buffers | **HIGH** | +| **buffer.toString()** | Convert to string | **HIGH** | +| **buffer.write()** | Write to buffer | **HIGH** | +| **buffer.slice()** | Get slice | High | +| **buffer.compare()** | Compare buffers | Medium | + +#### Path Module (Partial - Missing Some) + +| Feature | JS Path | BanglaCode | Status | +|---------|---------|-----------|--------| +| **path.join()** | Yes | Has `path_joro()` | ✅ | +| **path.resolve()** | Yes | ❌ | Missing | +| **path.dirname()** | Yes | ❌ | Missing | +| **path.basename()** | Yes | Has `path_naam()` | ✅ | +| **path.extname()** | Yes | Has `file_ext()` | ✅ | +| **path.normalize()** | Yes | ❌ | Missing | +| **path.relative()** | Yes | ❌ | Missing | +| **path.sep** | Yes | ❌ | Missing | +| **path.delimiter** | Yes | ❌ | Missing | +| **path.win32** | Yes | ❌ | Missing | +| **path.posix** | Yes | ❌ | Missing | + +#### URL Module (Missing - IMPORTANT) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **new URL()** | Parse URL | **CRITICAL** | +| **url.href** | Full URL | **CRITICAL** | +| **url.protocol** | Protocol | High | +| **url.hostname** | Hostname | High | +| **url.port** | Port | High | +| **url.pathname** | Path | High | +| **url.search** | Query string | High | +| **url.hash** | Fragment | Medium | +| **url.username** | Username | Medium | +| **url.password** | Password | Medium | +| **URLSearchParams** | Query params | **CRITICAL** | +| **url.parse()** (legacy) | Parse URL | High | +| **url.format()** (legacy) | Format URL | High | + +#### Crypto Module (CRITICAL - MISSING) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **crypto.createHash()** | Hash creation | **CRITICAL** | +| **crypto.createHmac()** | HMAC creation | **CRITICAL** | +| **crypto.createCipheriv()** | Encryption | **CRITICAL** | +| **crypto.createDecipheriv()** | Decryption | **CRITICAL** | +| **crypto.randomBytes()** | Random data | **CRITICAL** | +| **crypto.generateKeyPair()** | RSA/EC keys | **HIGH** | +| **crypto.sign()** / **verify()** | Digital signatures | **HIGH** | +| **crypto.subtle** | Web Crypto API | **MEDIUM** | +| **crypto.getHashes()** | List algorithms | Medium | +| **crypto.getCiphers()** | List ciphers | Medium | + +#### Compression (Missing - IMPORTANT for APIs) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **zlib.gzip()** | Compress gzip | ❌ | +| **zlib.gunzip()** | Decompress gzip | ❌ | +| **zlib.deflate()** | Compress deflate | ❌ | +| **zlib.inflate()** | Decompress deflate | ❌ | +| **zlib.brotliCompress()** | Brotli compress | ❌ | +| **zlib.brotliDecompress()** | Brotli decompress | ❌ | +| **createGzip()** | Streaming gzip | ❌ | +| **createGunzip()** | Streaming gunzip | ❌ | + +#### DNS Module (Missing) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **dns.lookup()** | IP lookup | ❌ | +| **dns.resolve()** | DNS resolve | ❌ | +| **dns.resolve4()** / **resolve6()** | IPv4/IPv6 | ❌ | +| **dns.resolveMx()** | Mail exchange | ❌ | +| **dns.reverseLookup()** | Reverse DNS | ❌ | + +#### OS Module (Partial - Some Missing) + +| Feature | JS Node | BanglaCode | Status | +|---------|---------|-----------|--------| +| **os.platform()** | Yes | ❌ | Missing | +| **os.arch()** | Yes | Has `bebosthok_naam()` | ✅ | +| **os.cpus()** | Yes | ❌ | Missing | +| **os.totalmem()** | Yes | Has `memory_total()` | ✅ | +| **os.freemem()** | Yes | Has `memory_mukt()` | ✅ | +| **os.homedir()** | Yes | ❌ | Missing | +| **os.tmpdir()** | Yes | ❌ | Missing | +| **os.type()** | Yes | ❌ | Missing | +| **os.release()** | Yes | ❌ | Missing | +| **os.uptime()** | Yes | Has `uptime()` | ✅ | +| **os.userInfo()** | Yes | ❌ | Missing | +| **os.EOL** | Yes | ❌ | Missing | + +#### TLS/SSL (Missing) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **tls.createServer()** | Secure server | ❌ | +| **tls.connect()** | Secure client | ❌ | +| **https module** | HTTPS support | ❌ | + +#### Process Object (Partial) + +| Feature | JS Node | BanglaCode | Status | +|---------|---------|-----------|--------| +| **process.pid** | Yes | Has `process_id()` | ✅ | +| **process.ppid** | Yes | Has `process_parent_id()` | ✅ | +| **process.platform** | Yes | ❌ | Missing | +| **process.arch** | Yes | ❌ | Missing | +| **process.version** | Yes | ❌ | Missing | +| **process.versions** | Yes | ❌ | Missing | +| **process.cwd()** | Yes | Has `kaj_directory()` | ✅ | +| **process.chdir()** | Yes | Has `kaj_directory_bodol()` | ✅ | +| **process.env** | Yes | Has `env_*` functions | Partial ✅ | +| **process.exit()** | Yes | Has `bondho()` | ✅ | +| **process.abort()** | Yes | ❌ | Missing | +| **process.uptime()** | Yes | ❌ | Missing | +| **process.memoryUsage()** | Yes | ❌ | Missing | +| **process.cpuUsage()** | Yes | ❌ | Missing | +| **process.on('SIGTERM')** | Yes | ❌ | Missing | +| **process.on('SIGINT')** | Yes | ❌ | Missing | +| **process.stdin/stdout/stderr** | Yes | ❌ | Missing | + +#### Util Module (Partial) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **util.inspect()** | Debug format | ❌ | +| **util.isDeepStrictEqual()** | Deep equality | ❌ | +| **util.inherits()** | Inherit pattern | ❌ | +| **util.promisify()** | Callback to Promise | ❌ | +| **util.callbackify()** | Promise to callback | ❌ | +| **util.format()** | Format strings | ❌ | + +#### Readline (Missing - Important for CLI) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **readline.createInterface()** | Create interface | ❌ | +| **rl.question()** | Prompt user | Has `nao()` - partial ✅ | +| **rl.on('line')** | Line event | ❌ | +| **rl.close()** | Close interface | ❌ | + +#### Other Node APIs (Missing) + +| Module | Purpose | Status | +|--------|---------|--------| +| **REPL** | Interactive shell | Has REPL but not programmatic | +| **VM** | Code execution sandbox | ❌ | +| **Inspector** | V8 debugger | ❌ | +| **v8** | V8 engine access | ❌ | +| **perf_hooks** | Performance measurement | ❌ | +| **AsyncContext** | Context propagation (ES2026) | ❌ | +| **SQLite** | Built-in SQLite (ES2025) | ❌ | +| **Test Runner** | Built-in tests | ❌ | +| **WASI** | WebAssembly System Interface | ❌ | + +--- + +## Module System & Package Management + +### Missing 25+ Package Management Features + +| Feature | JS/Node | BanglaCode | Impact | +|---------|---------|-----------|--------| +| **npm** | ✅ | ❌ | Package manager - **CRITICAL** | +| **package.json** | ✅ | ❌ | Metadata file - **CRITICAL** | +| **package-lock.json** | ✅ | ❌ | Lock file - **CRITICAL** | +| **Dependency resolution** | ✅ | ❌ | Auto dependency install - **CRITICAL** | +| **Version management** | ✅ | ❌ | Semver - **CRITICAL** | +| **npm install** | ✅ | ❌ | Install dependencies - **CRITICAL** | +| **npm run** | ✅ | ❌ | Run scripts - **HIGH** | +| **devDependencies** | ✅ | ❌ | Dev-only packages - **HIGH** | +| **peerDependencies** | ✅ | ❌ | Peer requirements - High | +| **optionalDependencies** | ✅ | ❌ | Optional packages - High | +| **scripts** | ✅ | ❌ | Run scripts | High | +| **npm publish** | ✅ | ❌ | Publish package | Medium | +| **npm link** | ✅ | ❌ | Link local package | Medium | +| **Dynamic import** | ✅ | ❌ | `import('path')` - **HIGH** | +| **import.meta** | ✅ | ❌ | Module metadata - Medium | +| **CommonJS require** | ✅ | ❌ | Legacy imports | Medium | +| **Module caching** | ✅ | ❌ | Cache modules | Medium | + +--- + +## HTTP & Networking + +### Missing 40+ HTTP/Network Features + +#### HTTP Request Methods (Missing) + +| Method | Purpose | Status | +|--------|---------|--------| +| **GET** | Retrieve data | Has `anun()` - **✅** | +| **POST** | Submit data | ✅ Implemented as `pathao_post()` v7.0.4 | +| **PUT** | Replace resource | ✅ Implemented as `pathao_put()` v7.0.4 | +| **PATCH** | Partial update | ✅ Implemented as `pathao_patch()` v7.0.4 | +| **DELETE** | Delete resource | ✅ Implemented as `pathao_delete()` v7.0.4 | +| **HEAD** | Like GET, no body | ❌ | +| **OPTIONS** | Describe options | ❌ | +| **TRACE** | Trace request | ❌ | +| **CONNECT** | Establish tunnel | ❌ | + +#### HTTP Request Features (Missing) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **Custom headers** | Set headers | ✅ Implemented via headers map arg v7.0.4 | +| **Cookies** | Manage cookies | **CRITICAL** | +| **Authorization** | Auth headers | **CRITICAL** | +| **Multipart form data** | File uploads | **CRITICAL** | +| **Form data** | Form submission | **CRITICAL** | +| **Request body** | Send body | ✅ Implemented via body arg v7.0.4 | +| **Request timeout** | Timeout handling | ❌ | +| **Request retry** | Retry logic | ❌ | +| **Request compression** | gzip request | ❌ | +| **Response compression** | gzip response | ❌ | +| **Connection pooling** | Reuse connections | ❌ | +| **Keep-alive** | Keep connection alive | ❌ | +| **Redirect handling** | Follow redirects | ❌ | +| **Status codes** | HTTP status methods | Has basic support | +| **Response streaming** | Stream response | ❌ | + +#### HTTP Server Features (Partial - Missing Many) + +| Feature | JS Node | BanglaCode | Status | +|---------|---------|-----------|--------| +| **createServer()** | Yes | Has `server_chalu()` | Partial ✅ | +| **Request object** | Yes | ❌ | Missing | +| **Response object** | Yes | Has `uttor()` | Partial ✅ | +| **Headers** | Yes | ❌ | Missing | +| **Status codes** | Yes | ❌ | Missing | +| **Cookies** | Yes | ❌ | Missing | +| **Middleware** | Yes | ❌ | Missing | +| **Routing** | Yes | ❌ | Missing | +| **Request body parsing** | Yes | ❌ | Missing | +| **Response compression** | Yes | ❌ | Missing | +| **Static files** | Yes | ❌ | Missing | +| **Templating** | Yes | ❌ | Missing | + +#### WebSocket (Missing - IMPORTANT) + +| Feature | Purpose | Status | +|---------|---------|--------| +| **WebSocket server** | Has `websocket_server_chalu()` | ✅ | +| **WebSocket client** | Has `websocket_jukto()` | ✅ | +| **Message events** | Has `websocket_pathao()` | ✅ | +| **Binary frames** | Binary data | ❌ | +| **Ping/Pong** | Keep-alive | ❌ | +| **Subprotocols** | Custom protocols | ❌ | +| **Extensions** | Protocol extensions | ❌ | + +#### HTTPS (Missing - CRITICAL) + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **https.createServer()** | Secure server | **CRITICAL** | +| **SSL/TLS certificates** | Security | **CRITICAL** | +| **Certificate validation** | Verify server | **CRITICAL** | + +--- + +## File System + +### Missing File System Features + +| Feature | JS Node | BanglaCode | Status | +|---------|---------|-----------|--------| +| **fs.readFileSync()** | Yes | Has `poro()` | ✅ | +| **fs.readFile()** | Yes | Has `poro_async()` | ✅ | +| **fs.promises.readFile()** | Yes | Has `poro_async()` | ✅ | +| **fs.writeFileSync()** | Yes | Has `lekho()` | ✅ | +| **fs.writeFile()** | Yes | Has `lekho_async()` | ✅ | +| **fs.appendFile()** | Yes | ❌ | Missing | +| **fs.unlink()** | Yes | ❌ | Missing | +| **fs.mkdir()** | Yes | Has `folder_banao()` | ✅ | +| **fs.rmdir()** | Yes | ❌ | Missing | +| **fs.readdir()** | Yes | Has `directory_taliika()` | ✅ | +| **fs.stat()** | Yes | Has `file_akar()` etc. | Partial ✅ | +| **fs.copyFile()** | Yes | ❌ | Missing | +| **fs.rename()** | Yes | Has `file_rename()` | ✅ | +| **fs.watch()** | Yes | ❌ | Missing | +| **fs.watchFile()** | Yes | ❌ | Missing | +| **fs.createReadStream()** | Yes | ❌ | Missing - **CRITICAL** | +| **fs.createWriteStream()** | Yes | ❌ | Missing - **CRITICAL** | +| **fs.chmod()** | Yes | Has `file_permission_set()` | ✅ | +| **fs.chown()** | Yes | Has `file_malikan_set()` | ✅ | +| **fs.access()** | Yes | ❌ | Missing | + +--- + +## Cryptography & Security + +### Missing 30+ Crypto Features + +**CRITICAL Missing:** + +| Feature | Purpose | Impact | +|---------|---------|--------| +| **Hash algorithms** | SHA256, SHA512, MD5 | ✅ Implemented v7.0.4 | +| **HMAC** | Message authentication | ✅ Implemented v7.0.4 | +| **Encryption** | AES, RSA | **CRITICAL** | +| **Decryption** | Reverse encryption | **CRITICAL** | +| **Random bytes** | `crypto.randomBytes()` | ✅ Implemented as `lotto_bytes()` v7.0.4 | +| **Key generation** | Generate RSA/EC keys | **CRITICAL** | +| **Digital signatures** | Sign and verify | **CRITICAL** | +| **Password hashing** | bcrypt, Argon2 | **CRITICAL** | +| **Web Crypto API** | `crypto.subtle` | **HIGH** | + +**Specific Algorithms:** + +| Algorithm | Use Case | Status | +|-----------|----------|--------| +| SHA256 | Hashing | ✅ Implemented as `hash_sha256()` v7.0.4 | +| SHA512 | Hashing | ✅ Implemented as `hash_sha512()` v7.0.4 | +| MD5 | Hashing (legacy) | ✅ Implemented as `hash_md5()` v7.0.4 | +| HMAC-SHA256 | Authentication | ✅ Implemented as `hmac_sha256()` v7.0.4 | +| AES-256-CBC | Encryption | ❌ | +| AES-128-GCM | Authenticated encryption | ❌ | +| RSA-2048 | Asymmetric | ❌ | +| ECDSA | Digital signatures | ❌ | +| PBKDF2 | Key derivation | ❌ | +| Argon2 | Password hashing | ❌ | +| bcrypt | Password hashing | ❌ | + +--- + +## Testing & Development Tools + +### Missing 40+ Development Features + +| Tool/Feature | Purpose | Status | +|--------------|---------|--------| +| **Jest** | Test framework | ❌ | +| **Mocha** | Test runner | ❌ | +| **Vitest** | Test runner | ❌ | +| **AVA** | Test runner | ❌ | +| **Jasmine** | Test framework | ❌ | +| **Chai** | Assertion library | ❌ | +| **Sinon** | Mocking library | ❌ | +| **Nock** | HTTP mocking | ❌ | +| **ESLint** | Code linting | ❌ | +| **Prettier** | Code formatting | ❌ | +| **StandardJS** | Style guide | ❌ | +| **TypeScript** | Static typing | ❌ | +| **Flow** | Type checking | ❌ | +| **JSDoc** | Type hints | ❌ | +| **Node inspector** | Debugger | ❌ | +| **Chrome DevTools** | Debugger | ❌ | +| **VS Code Debugger** | IDE debugging | ❌ | +| **Test coverage** | Coverage reports | ❌ | +| **Benchmark tools** | Performance testing | ❌ | +| **Memory profiling** | Memory analysis | ❌ | + +--- + +## Advanced Features + +### Missing 30+ Advanced Features + +| Feature | Purpose | Priority | +|---------|---------|----------| +| **Generators** | `function*` and `yield` | Low | +| **Async generators** | `async function*` | Low | +| **for await...of** | Async iteration | **MEDIUM** | +| **Iterators** | Custom iteration | Low | +| **Symbol.iterator** | Iteration protocol | Low | +| **Proxy** | Intercept operations | Low | +| **Reflect** | Mirror of Proxy | Low | +| **Regular expressions** | `/pattern/flags` - Full support | **MEDIUM** | +| **Immutable methods** | `toReversed()`, `toSorted()` | Low | +| **Using declaration** | Resource management | Low | +| **Temporal API** | Modern dates | Low | +| **Intl API** | Internationalization | Medium | +| **Web Streams API** | Streaming standard | **MEDIUM** | +| **AsyncContext** | Context propagation | Low | +| **Decorator syntax** | `@decorator` | Low | +| **Records (Proposal)** | Immutable records | Very low | +| **Tuples (Proposal)** | Immutable tuples | Very low | +| **Pattern matching (Proposal)** | Future feature | Very low | +| **Pipe operator (Proposal)** | Future feature | Very low | + +--- + +## Deprecated but Still Used + +| Feature | Status | Notes | +|---------|--------|-------| +| **Date methods** | Not implemented | Many Date methods missing | +| **HTML string methods** | Not implemented | `anchor()`, `big()`, etc. (low priority) | +| **with statement** | Not implemented | Intentionally bad practice | +| **var declaration** | Has `dhoro` | Works with `dhoro` | +| **Callback APIs** | Partial | Limited callback support | +| **prototype-based inheritance** | Not implemented | Use classes instead | + +--- + +## Summary: Impact Classification + +### CRITICAL (Severely Limits Functionality) +1. Date/time handling - **Top priority** +2. Crypto module - **Essential for security** +3. Streams API - **Essential for performance** +4. EventEmitter - **Essential for event-driven code** +5. npm/package management - **Essential for ecosystem** +6. Buffer API - **Essential for binary data** +7. URL parsing with URLSearchParams - **Essential for web development** + +### HIGH (Important Features) +1. ~~Arrow functions~~ - ✅ **Implemented v7.0.7** +2. ~~Destructuring~~ - ✅ **Implemented v7.0.8** +3. Optional chaining & Nullish coalescing - **Safety features** +4. Class inheritance (extends/super) - **OOP essential** +5. Custom error classes - **Error handling** +6. String methods (match, search, charAt, etc.) - **String manipulation** +7. ~~for...of loop~~ - ✅ **Implemented v7.0.7** +8. ~~setTimeout/setInterval~~ - ✅ **Implemented v7.0.8** +9. HTTP POST/PUT/PATCH/DELETE - **Web development** + +### MEDIUM (Nice to Have) +1. Static methods/properties - **Design pattern** +2. Getters/setters - **Code elegance** +3. Private fields/methods - **Encapsulation** +4. Regular expressions (full) - **Pattern matching** (core implemented; advanced flags/backrefs still partial) +5. Generators - **Advanced pattern** +6. Proxy/Reflect - **Advanced pattern** +7. Worker threads - **Parallelism** +8. TypeScript - **Type safety** +9. Set/Map - **Data structures** +10. Internationalization - **Localization** + +### LOW (Edge Cases) +1. BigInt - **Large number handling** +2. Symbols - **Unique identifiers** +3. Weak collections - **Memory optimization** +4. Intl API - **i18n** +5. Temporal API - **Better dates** +6. Decorators - **Code organization** +7. Async iterators - **Advanced async** +8. WASI - **WebAssembly** + +--- + +## Top 20 Missing Features by Impact + +### Rank 1-5 (Most Critical) + +| Rank | Feature | Impact | Status | +|------|---------|--------|--------| +| **1** | **Date/time handling** | Core implemented (`tarikh_*`) | Advanced Date object behavior still partial | +| **2** | **Crypto module** | No security operations | Can't encrypt/hash | +| **3** | **Streams API** | Large files cause memory issues | Load entire files | +| **4** | ~~**HTTP POST/PUT/DELETE**~~ | ~~Limited API interactions~~ | ✅ **Implemented v7.0.4** | +| **5** | **EventEmitter** | Limited event-driven architecture | Limited event support | + +### Rank 6-10 + +| Rank | Feature | Impact | Workaround | +|------|---------|--------|-----------| +| **6** | **npm/packages** | No ecosystem integration | Manual code duplication | +| **7** | ~~**Arrow functions**~~ | ✅ Implemented | Use `x => expr` | +| **8** | ~~**Destructuring**~~ | ✅ Implemented | Use destructuring declaration | +| **9** | ~~**Class inheritance**~~ | ~~Limited OOP capabilities~~ | ✅ **Implemented v7.0.4** | +| **10** | ~~**Optional chaining**~~ | ~~More error handling needed~~ | ✅ **Implemented v7.0.4** | + +### Rank 11-20 + +| Rank | Feature | Impact | +|------|---------|--------| +| **11** | **String methods** | Limited string manipulation | +| **12** | ~~**for...of loop**~~ | ✅ Implemented | +| **13** | **Regular expressions** | Pattern matching limited | +| **14** | **Custom error classes** | Limited error handling | +| **15** | ~~**setTimeout/setInterval**~~ | ✅ Implemented | +| **16** | **Map/Set data structures** | Limited collection types | +| **17** | **Getters/setters** | No property interception | +| **18** | ~~**Static methods**~~ | ✅ **Implemented v7.0.4** | +| **19** | **Private fields** | No encapsulation | +| **20** | **Generators** | No lazy evaluation | + +--- + +## Recommendations for Implementation Priority + +### Phase 1 (Highest Impact) - ✅ COMPLETED v7.0.3: +1. ✅ **DONE** - Array iteration methods: `manchitro()`, `chhanno()`, `sonkuchito()`, `proti()` +2. ✅ **DONE** - Object methods: `mishra()`, `jora()`, `maan()` +3. ✅ **DONE** - Switch/case statements (bikolpo/khetre/manchito) +4. ✅ **DONE** - Template literals (backtick syntax with ${}) + +### Phase 2 (Implemented) - ✅ COMPLETED v7.0.4: +1. ✅ **DONE** - Ternary operator, optional chaining, nullish coalescing +2. ✅ **DONE** - Array methods: `find()`, `findIndex()`, `every()`, `some()`, `concat()`, `flat()` +3. ✅ **DONE** - Crypto: Basic hashing (SHA256/512, MD5), HMAC, random bytes, Base64 +4. ✅ **DONE** - String methods: `charAt()`, `includes()`, `startsWith()`, `endsWith()`, `repeat()`, `padStart()`, `padEnd()`, `trimStart()`, `trimEnd()` +5. ✅ **DONE** - Number parsing: `parseInt()`, `parseFloat()`, `isNaN()` +6. ✅ **DONE** - Class inheritance (`extends`/`theke`), super (`upor`), static methods (`sthir kaj`) +7. ✅ **DONE** - HTTP POST/PUT/PATCH/DELETE with body and custom headers + +### Phase 2 (Still Missing) - Next priorities: + +### Phase 3 (Medium Impact) - Enhancement features: +1. Static methods/properties +2. Getters/setters +3. Private fields/methods +4. Generators +5. Async generators +6. EventEmitter +7. Worker threads +8. TypeScript (optional) +9. Test framework integration +10. Proxy/Reflect + +### Phase 4 (Lower Impact) - Nice-to-have features: +1. Symbols +2. Weak collections +3. BigInt +4. Intl API +5. Temporal API (when mature) +6. Decorators +7. WASI +8. Complete Buffer API + +--- + +## Comparison Statistics + +| Category | JS/Node | BanglaCode | Missing | % Implemented | +|----------|---------|-----------|---------|---------------| +| Core Language | 60+ | 45+ | 15+ | 75% | +| ES6+ Features | 50+ | 20+ | 30+ | 40% | +| Node.js APIs | 33+ | 15+ | 18+ | 45% | +| Global Functions | 15+ | 8+ | 7+ | 53% | +| Array Methods | 30+ | 10+ | 20+ | 33% | +| String Methods | 40+ | 15+ | 25+ | 37% | +| Object Methods | 25+ | 5+ | 20+ | 20% | +| Math/Number | 50+ | 10+ | 40+ | 20% | +| **TOTAL** | **400+** | **140+** | **260+** | **35%** | + +--- + +## Conclusion + +BanglaCode currently implements approximately **35% of JavaScript/Node.js features**. The language is functional for basic to intermediate programming tasks but lacks features essential for: + +1. **Functional programming** - Missing array methods +2. **Web development** - Missing HTTP methods and body handling +3. **System security** - Missing cryptography +4. **Large-scale applications** - Missing streams and EventEmitter +5. **Package ecosystem** - Missing npm integration +6. **Modern syntax** - Missing arrow functions, template literals, destructuring +7. **Date/time handling** - Limited Date support +8. **Data manipulation** - Missing Object methods and Map/Set + +The most impactful additions would be: +1. Array methods (map, filter, reduce, forEach) +2. Object methods (assign, entries, values) +3. Switch/case statements +4. Template literals +5. Crypto module +6. Class inheritance +7. HTTP POST/DELETE methods +8. Streams API + +--- + +**Document Status**: Comprehensive analysis complete +**Last Updated**: 2026-02-22 +**Total Missing Features Identified**: 260+ +**Estimated Effort for Phase 1**: Medium (6-8 weeks for experienced team) +**Estimated Effort for Phase 2**: High (8-12 weeks) +**Estimated Effort for All Phases**: Very High (6+ months) diff --git a/README.md b/README.md index 3f95c03..3d0ff7a 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,21 @@ dekho("Fetched users:", dorghyo(result["rows"])); - `angsho(str, start, end)` - Substring - `bodlo(str, old, new)` - Replace - `kato(str, len)` - String length +- `ache_text(str, part)` - Includes check +- `shuru_diye(str, prefix)` - Starts with +- `shesh_diye(str, suffix)` - Ends with +- `baro(str, count)` - Repeat string +- `agey_bhoro(str, len, pad?)` - Pad start +- `pichoney_bhoro(str, len, pad?)` - Pad end +- `okkhor(str, index)` - Character at index +- `text_at(str, index)` - Character at index (supports negative index) +- `okkhor_code(str, index)` - Character code +- `codepoint_at(str, index)` - Unicode code point at index +- `tulona_text(a, b)` - String compare (-1/0/1) +- `shadharon_text(str)` - Normalized text (NFC-like) +- `chhanto_shuru(str)` - Trim start +- `chhanto_shesh(str)` - Trim end +- `shesh_khojo(str, part)` - Last index of substring ### 📦 Array Operations - `dorghyo(arr)` - Array length @@ -528,6 +543,18 @@ dekho("Fetched users:", dorghyo(result["rows"])); - `saja(arr)` - Sort array - `ache(arr, val)` - Contains check - `chabi(map)` - Get map keys +- `khojo_prothom(arr, fn)` - Find first matching element +- `khojo_index(arr, fn)` - Find first matching index +- `khojo_shesh(arr, fn)` - Find last matching element +- `khojo_shesh_index(arr, fn)` - Find last matching index +- `prottek(arr, fn)` - Every element satisfies condition +- `kono(arr, fn)` - At least one element satisfies condition +- `somtol_manchitro(arr, fn)` - FlatMap (map + flatten one level) +- `somtol(arr, depth?)` - Flatten nested arrays +- `joro_array(arr, ...items)` - Concat arrays/values +- `sonkuchito_dan(arr, fn, init?)` - Reduce from right +- `array_at(arr, index)` - Index access with negative support +- `shesh_index_of(arr, value)` - Last index of value ### 🧮 Math Functions - `borgomul(x)` - Square root @@ -603,6 +630,30 @@ dekho("Fetched users:", dorghyo(result["rows"])); - `json_poro(str)` - Parse JSON - `json_banao(obj)` - Stringify JSON +### 🕒 Date & Regex +- `tarikh_ekhon()` - Current timestamp in ms +- `tarikh_parse(text)` - Parse date string to timestamp +- `tarikh_format(ts, layout?)` - Format timestamp +- `regex_test(pattern, text)` - Regex test +- `regex_match(pattern, text)` - First match with captures +- `regex_match_all(pattern, text)` - All matches +- `regex_search(pattern, text)` - Match start index +- `regex_replace(pattern, text, replacement)` - Regex replace +- `match(text, pattern, flags?)` - String-like regex match +- `matchAll(text, pattern, flags?)` - String-like regex all match +- `search(text, pattern, flags?)` - String-like regex search index +- `nijer_ache(obj, key)` - Object.hasOwn equivalent +- `jora_theke(entries)` - Object.fromEntries equivalent +- `ekoi_ki(a, b)` - Object.is equivalent +- `notun_map(proto, props?)` - Object.create-like helper +- `joma(obj)` - Object.freeze semantic helper + +### ⏱️ Timers +- `setTimeout(fn, ms, ...args)` - Run callback once after delay +- `setInterval(fn, ms, ...args)` - Run callback repeatedly +- `clearTimeout(id)` - Cancel timeout +- `clearInterval(id)` - Cancel interval + ### 🌐 Networking (TCP, UDP, WebSocket) **TCP Functions:** - `tcp_server_chalu(port, handler)` - Start TCP server @@ -731,6 +782,14 @@ BanglaCode provides production-grade database connectors with **connection pooli - `dhoron(x)` - Get type - `lipi(x)` - Convert to string - `sonkha(x)` - Convert to number +- `purno_sonkhya(text, radix?)` - Parse integer +- `doshomik_sonkhya(text)` - Parse float +- `sonkhya_na(x)` - Check NaN +- `sonkhya_shimito(x)` - Check finite number +- `uri_encode(uri)` - Encode full URI +- `uri_decode(uri)` - Decode full URI +- `uri_ongsho_encode(text)` - Encode URI component +- `uri_ongsho_decode(text)` - Decode URI component - `bondho(code)` - Exit program --- @@ -746,7 +805,9 @@ BanglaCode provides production-grade database connectors with **connection pooli | যদি | `jodi` | if | `jodi (x > 0) { }` | | নাহলে | `nahole` | else | `nahole { }` | | যতক্ষণ | `jotokkhon` | while | `jotokkhon (x < 10) { }` | +| করো | `do` | do | `do { ... } jotokkhon (cond);` | | ঘুরিয়ে | `ghuriye` | for | `ghuriye (dhoro i = 0; i < 5; i++) { }` | +| অফ | `of` | of | `ghuriye (x of arr) { }` | | কাজ | `kaj` | function | `kaj add(a, b) { }` | | ফেরাও | `ferao` | return | `ferao result;` | | থামো | `thamo` | break | `thamo;` | @@ -790,6 +851,26 @@ BanglaCode provides production-grade database connectors with **connection pooli | এবং | `ebong` | and | Logical AND | | বা | `ba` | or | Logical OR | | না | `na` | not | Logical NOT | +| মধ্যে | `in` | in | `"a" in obj` | +| উদাহরণ | `instanceof` | instanceof | `obj instanceof Class` | +| মুছে দাও | `delete` | delete | `delete obj.key` | + +### Arrow Functions + +```banglacode +dhoro double = x => x * 2; +dekho(double(10)); // 20 + +dhoro add = (a, b) => a + b; +dekho(add(3, 4)); // 7 +``` + +### Destructuring + +```banglacode +dhoro [a, b] = [10, 20]; +dhoro {name, age} = {name: "Ankan", age: 25}; +``` --- diff --git a/SYNTAX.md b/SYNTAX.md index d62d085..6aaf3ec 100644 --- a/SYNTAX.md +++ b/SYNTAX.md @@ -83,7 +83,10 @@ BanglaCode uses Banglish keywords that are intuitive for Bengali speakers: | `jodi` | if | if | | `nahole` | else | else | | `jotokkhon` | as long as | while | +| `do` | do/run once | do | | `ghuriye` | rotate/turn | for | +| `of` | iterate values | of | +| `of` | iterate values | of | | `kaj` | work/function | function | | `ferao` | return | return | | `sreni` | class/category | class | @@ -95,6 +98,9 @@ BanglaCode uses Banglish keywords that are intuitive for Bengali speakers: | `ebong` | and | && | | `ba` | or | \|\| | | `na` | not | ! | +| `in` | inside/check key | in | +| `instanceof` | is instance of class | instanceof | +| `delete` | remove property/index | delete | | `thamo` | stop | break | | `chharo` | leave | continue | | `dekho` | see/show | print | @@ -221,6 +227,8 @@ dhoro remainder = 10 % 3; // Modulo sotti ebong mittha // AND (&&) sotti ba mittha // OR (||) na sotti // NOT (!) +"a" in {a: 1} // true +obj instanceof Class // true/false ``` ### Assignment Operators @@ -233,6 +241,13 @@ x *= 2; // Compound multiplication x /= 2; // Compound division ``` +### Delete Operator +```banglacode +dhoro user = {naam: "Ankan", boyosh: 25}; +delete user.boyosh; +dekho("boyosh" in user); // Output: mittha +``` + ## Control Flow ### If Statement @@ -279,6 +294,15 @@ jotokkhon (i < 5) { } ``` +### Do-While Loop (`do ... jotokkhon`) +```banglacode +dhoro i = 0; +do { + dekho(i); + i = i + 1; +} jotokkhon (i < 3); +``` + ### For Loop (`ghuriye`) ```banglacode ghuriye (dhoro i = 0; i < 5; i = i + 1) { @@ -286,6 +310,21 @@ ghuriye (dhoro i = 0; i < 5; i = i + 1) { } ``` +### For-Of Loop (`ghuriye ... of`) +```banglacode +ghuriye (item of [10, 20, 30]) { + dekho(item); +} +``` + +### For-In Loop (`ghuriye ... in`) +```banglacode +dhoro user = {naam: "Ankan", boyosh: 25}; +ghuriye (k in user) { + dekho(k, user[k]); +} +``` + ### Break (`thamo`) ```banglacode dhoro i = 0; @@ -338,6 +377,24 @@ kaj calculate(x, y, z) { dekho(calculate(2, 3, 4)); // Output: 20 ``` +### Arrow Functions +```banglacode +dhoro double = x => x * 2; +dekho(double(5)); // Output: 10 + +dhoro inc = x => { ferao x + 1; }; +dekho(inc(10)); // Output: 11 + +dhoro add = (a, b) => a + b; +dekho(add(2, 3)); // Output: 5 +``` + +### Destructuring Assignment (Declaration) +```banglacode +dhoro [a, b] = [10, 20]; +dhoro {name, age} = {name: "Ankan", age: 25}; +``` + ### Rest Parameters (Variadic Functions) Use `...` to collect any number of arguments into an array: @@ -833,11 +890,21 @@ dekho(dorghyo(arr)); // Output: 3 ### Map Functions - `chabi(map)` - চাবি - Get array of keys +- `maan(map)` - Get values +- `jora(map)` - Get key-value entry arrays +- `mishra(target, ...sources)` - Merge maps +- `nijer_ache(map, key)` - Own-key check +- `jora_theke(entries)` - Build map from entries +- `ekoi_ki(a, b)` - Object.is-like equality +- `notun_map(proto, props?)` - Create map from base + props +- `joma(map)` - Freeze semantic helper ```banglacode dhoro obj = {"a": 1, "b": 2}; dhoro k = chabi(obj); dekho(k); // Output: ["a", "b"] +dekho(nijer_ache(obj, "a")); // Output: sotti +dekho(jora_theke([["x", 10], ["y", 20]])); // Output: {x: 10, y: 20} ``` ### Math Functions @@ -884,6 +951,35 @@ dekho(angsho("hello", 1, 4)); // Output: ell dekho(bodlo("hello", "l", "x")); // Output: hexxo ``` +### Extended String Functions +- `ache_text(str, part)` - Includes substring check +- `shuru_diye(str, prefix)` - Starts-with check +- `shesh_diye(str, suffix)` - Ends-with check +- `baro(str, count)` - Repeat string +- `agey_bhoro(str, len, pad?)` - Pad at start +- `pichoney_bhoro(str, len, pad?)` - Pad at end +- `okkhor(str, index)` - Character at index +- `text_at(str, index)` - Character at index (supports negative index) +- `okkhor_code(str, index)` - Character code +- `codepoint_at(str, index)` - Unicode code point +- `tulona_text(a, b)` - String compare (-1/0/1) +- `shadharon_text(str)` - Normalized text (NFC-like) +- `chhanto_shuru(str)` - Trim start +- `chhanto_shesh(str)` - Trim end +- `shesh_khojo(str, part)` - Last index of substring + +```banglacode +dekho(ache_text("banglacode", "code")); // Output: sotti +dekho(shuru_diye("banglacode", "bang")); // Output: sotti +dekho(shesh_diye("banglacode", "code")); // Output: sotti +dekho(baro("ha", 3)); // Output: hahaha +dekho(agey_bhoro("7", 3, "0")); // Output: 007 +dekho(text_at("bangla", -1)); // Output: a +dekho(okkhor_code("A", 0)); // Output: 65 +dekho(codepoint_at("A", 0)); // Output: 65 +dekho(tulona_text("a", "b")); // Output: -1 +``` + ### Additional Array Functions - `kato(array, start, end)` - কাটো - Extract subarray - `ulto(array)` - উল্টো - Reverse array (returns new array) @@ -899,17 +995,70 @@ dekho(ache(arr, 4)); // Output: sotti dekho(ache(arr, 9)); // Output: mittha ``` +### Advanced Array Functions +- `khojo_prothom(arr, fn)` - Find first matching element +- `khojo_index(arr, fn)` - Find first matching index +- `khojo_shesh(arr, fn)` - Find last matching element +- `khojo_shesh_index(arr, fn)` - Find last matching index +- `prottek(arr, fn)` - Every element satisfies condition +- `kono(arr, fn)` - At least one element satisfies condition +- `somtol_manchitro(arr, fn)` - Map then flatten one level +- `somtol(arr, depth?)` - Flatten nested arrays +- `joro_array(arr, ...items)` - Concat arrays/values +- `sonkuchito_dan(arr, fn, init?)` - Reduce from right +- `array_at(arr, index)` - Index access with negative support +- `shesh_index_of(arr, value)` - Last index of value + +```banglacode +dekho(khojo_prothom([1, 3, 8], kaj(x) { ferao x > 5; })); // Output: 8 +dekho(khojo_index([1, 3, 8], kaj(x) { ferao x > 5; })); // Output: 2 +dekho(prottek([2, 4, 6], kaj(x) { ferao x % 2 == 0; })); // Output: sotti +dekho(kono([1, 3, 4], kaj(x) { ferao x % 2 == 0; })); // Output: sotti +dekho(joro_array([1,2], [3], 4)); // Output: [1, 2, 3, 4] +dekho(somtol([1, [2, [3]]], 2)); // Output: [1, 2, 3] +dekho(array_at([10, 20, 30], -1)); // Output: 30 +dekho(shesh_index_of([1, 2, 3, 2], 2)); // Output: 3 +``` + ### Utility Functions - `somoy()` - সময় - Current timestamp in milliseconds - `ghum(ms)` - ঘুম - Pause execution for milliseconds - `nao(prompt)` - নাও - Read user input from console - `bondho(code)` - বন্ধ - Exit program with code +- `purno_sonkhya(text, radix?)` - Parse integer +- `doshomik_sonkhya(text)` - Parse float +- `sonkhya_na(x)` - Check NaN +- `sonkhya_shimito(x)` - Check finite number +- `uri_encode(uri)` - Encode URI +- `uri_decode(uri)` - Decode URI +- `uri_ongsho_encode(text)` - Encode URI component +- `uri_ongsho_decode(text)` - Decode URI component +- `tarikh_ekhon()` - Current timestamp (ms) +- `tarikh_parse(text)` - Parse date string to timestamp +- `tarikh_format(ts, layout?)` - Format timestamp +- `regex_test(pattern, text)` - Regex boolean check +- `regex_match(pattern, text)` - First match + captures +- `regex_match_all(pattern, text)` - All matches +- `regex_search(pattern, text)` - First match index +- `regex_replace(pattern, text, replacement)` - Regex replacement +- `match(text, pattern, flags?)` - String-like regex first match +- `matchAll(text, pattern, flags?)` - String-like regex all matches +- `search(text, pattern, flags?)` - String-like regex search index +- `setTimeout(fn, ms, ...args)` - Run callback after delay +- `setInterval(fn, ms, ...args)` - Run callback repeatedly +- `clearTimeout(id)` - Cancel timeout +- `clearInterval(id)` - Cancel interval ```banglacode dekho(somoy()); // Output: 1234567890123 ghum(1000); // Pauses for 1 second dhoro naam = nao("Tomar naam ki: "); dekho("Hello", naam); +dekho(purno_sonkhya("42")); // Output: 42 +dekho(doshomik_sonkhya("3.14abc")); // Output: 3.14 +dekho(sonkhya_na("abc")); // Output: sotti +dekho(uri_ongsho_encode("hello world")); // Output: hello%20world +dekho(regex_test("[a-z]+", "bangla")); // Output: sotti ``` ### File Functions diff --git a/VERSION b/VERSION index a3fcc71..ae9a76b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.1.0 +8.0.0 diff --git a/main.go b/main.go index ad8f413..65719cb 100644 --- a/main.go +++ b/main.go @@ -72,10 +72,10 @@ func printHelp() { func printVersion() { fmt.Println("\033[1;36m╔════════════════════════════════════════════════════════╗") - fmt.Println("║ BanglaCode v7.1.0 ║") + fmt.Println("║ BanglaCode v8.0.0 ║") fmt.Println("║ A Programming Language in Bengali (Banglish) ║") fmt.Println("╠════════════════════════════════════════════════════════╣\033[0m") - fmt.Println("\033[1;36m║\033[0m 📦 \033[1mVersion:\033[0m \033[1;32m7.1.0\033[0m \033[1;36m║\033[0m") + fmt.Println("\033[1;36m║\033[0m 📦 \033[1mVersion:\033[0m \033[1;32m8.0.0\033[0m \033[1;36m║\033[0m") fmt.Println("\033[1;36m║\033[0m 👨‍💻 \033[1mAuthor:\033[0m \033[1;35mAnkan Saha\033[0m \033[1;36m║\033[0m") fmt.Println("\033[1;36m║\033[0m 🌍 \033[1mFrom:\033[0m \033[1;37mWest Bengal, India\033[0m \033[1;36m║\033[0m") fmt.Println("\033[1;36m║\033[0m 🔗 \033[1mGitHub:\033[0m \033[1;34mhttps://github.com/nexoral/BanglaCode\033[0m \033[1;36m║\033[0m") diff --git a/src/ast/advanced.go b/src/ast/advanced.go new file mode 100644 index 0000000..3978d14 --- /dev/null +++ b/src/ast/advanced.go @@ -0,0 +1,40 @@ +package ast + +import ( + "BanglaCode/src/lexer" + "bytes" +) + +// DoWhileStatement represents: do { ... } jotokkhon (condition); +type DoWhileStatement struct { + Token lexer.Token // the DO token + Body *BlockStatement + Condition Expression +} + +func (dws *DoWhileStatement) statementNode() {} +func (dws *DoWhileStatement) TokenLiteral() string { return dws.Token.Literal } +func (dws *DoWhileStatement) String() string { + var out bytes.Buffer + out.WriteString("do ") + out.WriteString(dws.Body.String()) + out.WriteString(" jotokkhon (") + out.WriteString(dws.Condition.String()) + out.WriteString(");") + return out.String() +} + +// DeleteExpression represents: delete obj.prop or delete obj["prop"] +type DeleteExpression struct { + Token lexer.Token // the DELETE token + Target Expression +} + +func (de *DeleteExpression) expressionNode() {} +func (de *DeleteExpression) TokenLiteral() string { return de.Token.Literal } +func (de *DeleteExpression) String() string { + var out bytes.Buffer + out.WriteString("delete ") + out.WriteString(de.Target.String()) + return out.String() +} diff --git a/src/ast/destructuring.go b/src/ast/destructuring.go new file mode 100644 index 0000000..7f9b346 --- /dev/null +++ b/src/ast/destructuring.go @@ -0,0 +1,84 @@ +package ast + +import ( + "BanglaCode/src/lexer" + "bytes" + "strings" +) + +// ArrayDestructuringDeclaration represents: dhoro [a, b] = expr; +type ArrayDestructuringDeclaration struct { + Token lexer.Token // DHORO/STHIR/BISHWO token + Names []*Identifier + Source Expression + IsConstant bool + IsGlobal bool +} + +func (ad *ArrayDestructuringDeclaration) statementNode() {} +func (ad *ArrayDestructuringDeclaration) TokenLiteral() string { return ad.Token.Literal } +func (ad *ArrayDestructuringDeclaration) String() string { + var out bytes.Buffer + if ad.IsConstant { + out.WriteString("sthir ") + } else if ad.IsGlobal { + out.WriteString("bishwo ") + } else { + out.WriteString("dhoro ") + } + names := make([]string, 0, len(ad.Names)) + for _, n := range ad.Names { + names = append(names, n.Value) + } + out.WriteString("[") + out.WriteString(strings.Join(names, ", ")) + out.WriteString("] = ") + out.WriteString(ad.Source.String()) + out.WriteString(";") + return out.String() +} + +// ObjectDestructuringDeclaration represents: dhoro {x, y} = expr; +type ObjectDestructuringDeclaration struct { + Token lexer.Token // DHORO/STHIR/BISHWO token + Keys []string + Names []*Identifier + Source Expression + IsConstant bool + IsGlobal bool +} + +func (od *ObjectDestructuringDeclaration) statementNode() {} +func (od *ObjectDestructuringDeclaration) TokenLiteral() string { return od.Token.Literal } +func (od *ObjectDestructuringDeclaration) String() string { + var out bytes.Buffer + if od.IsConstant { + out.WriteString("sthir ") + } else if od.IsGlobal { + out.WriteString("bishwo ") + } else { + out.WriteString("dhoro ") + } + out.WriteString("{") + out.WriteString(strings.Join(od.Keys, ", ")) + out.WriteString("} = ") + out.WriteString(od.Source.String()) + out.WriteString(";") + return out.String() +} + +// ArrowParamList is an internal expression node used to parse (a, b) => ... +type ArrowParamList struct { + Token lexer.Token // LPAREN token + Params []*Identifier +} + +func (ap *ArrowParamList) expressionNode() {} +func (ap *ArrowParamList) TokenLiteral() string { return ap.Token.Literal } +func (ap *ArrowParamList) String() string { + names := make([]string, 0, len(ap.Params)) + for _, p := range ap.Params { + names = append(names, p.Value) + } + return "(" + strings.Join(names, ", ") + ")" +} diff --git a/src/ast/loops_advanced.go b/src/ast/loops_advanced.go new file mode 100644 index 0000000..c5f9e28 --- /dev/null +++ b/src/ast/loops_advanced.go @@ -0,0 +1,48 @@ +package ast + +import ( + "BanglaCode/src/lexer" + "bytes" +) + +// ForOfStatement represents: ghuriye (item of iterable) { ... } +type ForOfStatement struct { + Token lexer.Token // GHURIYE token + VarName *Identifier + Iterable Expression + Body *BlockStatement +} + +func (fs *ForOfStatement) statementNode() {} +func (fs *ForOfStatement) TokenLiteral() string { return fs.Token.Literal } +func (fs *ForOfStatement) String() string { + var out bytes.Buffer + out.WriteString("ghuriye (") + out.WriteString(fs.VarName.String()) + out.WriteString(" of ") + out.WriteString(fs.Iterable.String()) + out.WriteString(") ") + out.WriteString(fs.Body.String()) + return out.String() +} + +// ForInStatement represents: ghuriye (key in object) { ... } +type ForInStatement struct { + Token lexer.Token // GHURIYE token + VarName *Identifier + Object Expression + Body *BlockStatement +} + +func (fs *ForInStatement) statementNode() {} +func (fs *ForInStatement) TokenLiteral() string { return fs.Token.Literal } +func (fs *ForInStatement) String() string { + var out bytes.Buffer + out.WriteString("ghuriye (") + out.WriteString(fs.VarName.String()) + out.WriteString(" in ") + out.WriteString(fs.Object.String()) + out.WriteString(") ") + out.WriteString(fs.Body.String()) + return out.String() +} diff --git a/src/evaluator/advanced.go b/src/evaluator/advanced.go new file mode 100644 index 0000000..10be0e3 --- /dev/null +++ b/src/evaluator/advanced.go @@ -0,0 +1,140 @@ +package evaluator + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/object" +) + +// evalDoWhileStatement evaluates: do { ... } jotokkhon (condition); +func evalDoWhileStatement(stmt *ast.DoWhileStatement, env *object.Environment) object.Object { + for { + result := Eval(stmt.Body, env) + if result != nil { + switch result.Type() { + case object.RETURN_OBJ, object.ERROR_OBJ, object.EXCEPTION_OBJ: + return result + case object.BREAK_OBJ: + return object.NULL + case object.CONTINUE_OBJ: + // continue to condition check + } + } + + condition := Eval(stmt.Condition, env) + if isError(condition) { + return condition + } + if !isTruthy(condition) { + break + } + } + + return object.NULL +} + +func evalDeleteExpression(node *ast.DeleteExpression, env *object.Environment) object.Object { + member, ok := node.Target.(*ast.MemberExpression) + if !ok { + return object.FALSE + } + + obj := Eval(member.Object, env) + if isError(obj) { + return obj + } + + switch o := obj.(type) { + case *object.Map: + key, ok := resolveMemberKey(member, env) + if !ok { + return object.FALSE + } + if _, exists := o.Pairs[key]; exists { + delete(o.Pairs, key) + return object.TRUE + } + return object.TRUE + + case *object.Instance: + key, ok := resolveMemberKey(member, env) + if !ok { + return object.FALSE + } + delete(o.Properties, key) + return object.TRUE + + case *object.Array: + if !member.Computed { + return object.FALSE + } + idxObj := Eval(member.Property, env) + if isError(idxObj) || idxObj.Type() != object.NUMBER_OBJ { + return object.FALSE + } + idx := int(idxObj.(*object.Number).Value) + if idx < 0 || idx >= len(o.Elements) { + return object.TRUE + } + o.Elements[idx] = object.NULL + return object.TRUE + } + + return object.FALSE +} + +func evalInOperator(left, right object.Object) object.Object { + switch r := right.(type) { + case *object.Map: + key := getMapKey(left) + if key == "" && left.Type() != object.STRING_OBJ && left.Type() != object.NUMBER_OBJ { + return object.FALSE + } + _, exists := r.Pairs[key] + return object.NativeBoolToBooleanObject(exists) + + case *object.Array: + if left.Type() != object.NUMBER_OBJ { + return object.FALSE + } + idx := int(left.(*object.Number).Value) + return object.NativeBoolToBooleanObject(idx >= 0 && idx < len(r.Elements)) + + case *object.String: + if left.Type() != object.NUMBER_OBJ { + return object.FALSE + } + idx := int(left.(*object.Number).Value) + return object.NativeBoolToBooleanObject(idx >= 0 && idx < len([]rune(r.Value))) + } + + return object.FALSE +} + +func evalInstanceofOperator(left, right object.Object) object.Object { + instance, isInstance := left.(*object.Instance) + classObj, isClass := right.(*object.Class) + if !isInstance || !isClass { + return object.FALSE + } + return object.NativeBoolToBooleanObject(instance.Class == classObj) +} + +func resolveMemberKey(member *ast.MemberExpression, env *object.Environment) (string, bool) { + if member.Computed { + keyObj := Eval(member.Property, env) + if isError(keyObj) { + return "", false + } + key := getMapKey(keyObj) + if key == "" && keyObj.Type() != object.STRING_OBJ && keyObj.Type() != object.NUMBER_OBJ { + return "", false + } + return key, true + } + + ident, ok := member.Property.(*ast.Identifier) + if !ok { + return "", false + } + return ident.Value, true +} diff --git a/src/evaluator/builtins/builtins_array_advanced.go b/src/evaluator/builtins/builtins_array_advanced.go new file mode 100644 index 0000000..71deb1d --- /dev/null +++ b/src/evaluator/builtins/builtins_array_advanced.go @@ -0,0 +1,312 @@ +package builtins + +import "BanglaCode/src/object" + +func init() { + registerArrayConcat() + registerArrayFlat() + registerArrayReduceRight() + registerArrayFind() + registerArrayFindIndex() + registerArrayFindLast() + registerArrayFindLastIndex() + registerArrayEvery() + registerArraySome() + registerArrayFlatMap() + registerArrayAt() + registerArrayLastIndexOf() +} + +func registerArrayConcat() { + Builtins["joro_array"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 1 { + return newError("wrong number of arguments. got=%d, want>=1", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("first argument to `joro_array` must be ARRAY, got %s", args[0].Type()) + } + + base := args[0].(*object.Array) + result := make([]object.Object, 0, len(base.Elements)) + result = append(result, base.Elements...) + for i := 1; i < len(args); i++ { + if args[i].Type() == object.ARRAY_OBJ { + result = append(result, args[i].(*object.Array).Elements...) + } else { + result = append(result, args[i]) + } + } + return &object.Array{Elements: result} + }} +} + +func registerArrayFlat() { + Builtins["somtol"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 1 || len(args) > 2 { + return newError("wrong number of arguments. got=%d, want=1 or 2", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("first argument to `somtol` must be ARRAY, got %s", args[0].Type()) + } + + depth := 1 + if len(args) == 2 { + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `somtol` must be NUMBER, got %s", args[1].Type()) + } + depth = int(args[1].(*object.Number).Value) + if depth < 0 { + depth = 0 + } + } + return &object.Array{Elements: flattenArray(args[0].(*object.Array).Elements, depth)} + }} +} + +func registerArrayReduceRight() { + Builtins["sonkuchito_dan"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 2 || len(args) > 3 { + return newError("wrong number of arguments. got=%d, want=2 or 3", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("first argument to `sonkuchito_dan` must be ARRAY, got %s", args[0].Type()) + } + if args[1].Type() != object.FUNCTION_OBJ { + return newError("second argument to `sonkuchito_dan` must be FUNCTION, got %s", args[1].Type()) + } + + arr := args[0].(*object.Array) + handler := args[1].(*object.Function) + if len(arr.Elements) == 0 && len(args) == 2 { + return newError("reduceRight of empty array with no initial value") + } + + var accumulator object.Object + start := len(arr.Elements) - 1 + if len(args) == 3 { + accumulator = args[2] + } else { + accumulator = arr.Elements[start] + start-- + } + for i := start; i >= 0; i-- { + next := EvalFunc(handler, []object.Object{accumulator, arr.Elements[i], &object.Number{Value: float64(i)}, arr}) + if next.Type() == object.ERROR_OBJ { + return next + } + accumulator = next + } + return accumulator + }} +} + +func registerArrayFind() { + Builtins["khojo_prothom"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("khojo_prothom", args) + if err != nil { + return err + } + for i, el := range arr.Elements { + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if isTruthy(keep) { + return el + } + } + return object.NULL + }} +} + +func registerArrayFindIndex() { + Builtins["khojo_index"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("khojo_index", args) + if err != nil { + return err + } + for i, el := range arr.Elements { + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if isTruthy(keep) { + return &object.Number{Value: float64(i)} + } + } + return &object.Number{Value: -1} + }} +} + +func registerArrayFindLast() { + Builtins["khojo_shesh"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("khojo_shesh", args) + if err != nil { + return err + } + for i := len(arr.Elements) - 1; i >= 0; i-- { + el := arr.Elements[i] + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if isTruthy(keep) { + return el + } + } + return object.NULL + }} +} + +func registerArrayFindLastIndex() { + Builtins["khojo_shesh_index"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("khojo_shesh_index", args) + if err != nil { + return err + } + for i := len(arr.Elements) - 1; i >= 0; i-- { + el := arr.Elements[i] + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if isTruthy(keep) { + return &object.Number{Value: float64(i)} + } + } + return &object.Number{Value: -1} + }} +} + +func registerArrayEvery() { + Builtins["prottek"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("prottek", args) + if err != nil { + return err + } + for i, el := range arr.Elements { + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if !isTruthy(keep) { + return object.FALSE + } + } + return object.TRUE + }} +} + +func registerArraySome() { + Builtins["kono"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("kono", args) + if err != nil { + return err + } + for i, el := range arr.Elements { + keep := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if keep.Type() == object.ERROR_OBJ { + return keep + } + if isTruthy(keep) { + return object.TRUE + } + } + return object.FALSE + }} +} + +func registerArrayFlatMap() { + Builtins["somtol_manchitro"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + arr, callback, err := requireArrayAndCallback("somtol_manchitro", args) + if err != nil { + return err + } + result := make([]object.Object, 0, len(arr.Elements)) + for i, el := range arr.Elements { + mapped := EvalFunc(callback, []object.Object{el, &object.Number{Value: float64(i)}, arr}) + if mapped.Type() == object.ERROR_OBJ { + return mapped + } + if mappedArr, ok := mapped.(*object.Array); ok { + result = append(result, mappedArr.Elements...) + } else { + result = append(result, mapped) + } + } + return &object.Array{Elements: result} + }} +} + +func registerArrayAt() { + Builtins["array_at"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("first argument to `array_at` must be ARRAY, got %s", args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `array_at` must be NUMBER, got %s", args[1].Type()) + } + arr := args[0].(*object.Array) + idx := int(args[1].(*object.Number).Value) + if idx < 0 { + idx = len(arr.Elements) + idx + } + if idx < 0 || idx >= len(arr.Elements) { + return object.NULL + } + return arr.Elements[idx] + }} +} + +func registerArrayLastIndexOf() { + Builtins["shesh_index_of"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("first argument to `shesh_index_of` must be ARRAY, got %s", args[0].Type()) + } + arr := args[0].(*object.Array) + target := args[1] + for i := len(arr.Elements) - 1; i >= 0; i-- { + if objectsEqual(arr.Elements[i], target) { + return &object.Number{Value: float64(i)} + } + } + return &object.Number{Value: -1} + }} +} + +func flattenArray(elements []object.Object, depth int) []object.Object { + if depth == 0 { + out := make([]object.Object, len(elements)) + copy(out, elements) + return out + } + + result := make([]object.Object, 0, len(elements)) + for _, el := range elements { + if arr, ok := el.(*object.Array); ok { + result = append(result, flattenArray(arr.Elements, depth-1)...) + } else { + result = append(result, el) + } + } + return result +} + +func requireArrayAndCallback(name string, args []object.Object) (*object.Array, *object.Function, *object.Error) { + if len(args) != 2 { + return nil, nil, newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return nil, nil, newError("first argument to `%s` must be ARRAY, got %s", name, args[0].Type()) + } + if args[1].Type() != object.FUNCTION_OBJ { + return nil, nil, newError("second argument to `%s` must be FUNCTION, got %s", name, args[1].Type()) + } + return args[0].(*object.Array), args[1].(*object.Function), nil +} diff --git a/src/evaluator/builtins/builtins_date_regex.go b/src/evaluator/builtins/builtins_date_regex.go new file mode 100644 index 0000000..dd2c53b --- /dev/null +++ b/src/evaluator/builtins/builtins_date_regex.go @@ -0,0 +1,229 @@ +package builtins + +import ( + "BanglaCode/src/object" + "regexp" + "time" +) + +func init() { + registerDateNow() + registerDateParse() + registerDateFormat() + registerRegexTest() + registerRegexMatch() + registerRegexMatchAll() + registerRegexSearch() + registerRegexReplace() + registerMatchWrappers() +} + +func registerDateNow() { + Builtins["tarikh_ekhon"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 0 { + return newError("wrong number of arguments. got=%d, want=0", len(args)) + } + return &object.Number{Value: float64(time.Now().UnixMilli())} + }} +} + +func registerDateParse() { + Builtins["tarikh_parse"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("argument to `tarikh_parse` must be STRING, got %s", args[0].Type()) + } + text := args[0].(*object.String).Value + layouts := []string{time.RFC3339, time.RFC1123, time.RFC822, "2006-01-02 15:04:05", "2006-01-02"} + for _, layout := range layouts { + if parsed, err := time.Parse(layout, text); err == nil { + return &object.Number{Value: float64(parsed.UnixMilli())} + } + } + return nanNumber() + }} +} + +func registerDateFormat() { + Builtins["tarikh_format"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 1 || len(args) > 2 { + return newError("wrong number of arguments. got=%d, want=1 or 2", len(args)) + } + if args[0].Type() != object.NUMBER_OBJ { + return newError("first argument to `tarikh_format` must be NUMBER, got %s", args[0].Type()) + } + layout := time.RFC3339 + if len(args) == 2 { + if args[1].Type() != object.STRING_OBJ { + return newError("second argument to `tarikh_format` must be STRING, got %s", args[1].Type()) + } + layout = args[1].(*object.String).Value + } + ts := int64(args[0].(*object.Number).Value) + return &object.String{Value: time.UnixMilli(ts).UTC().Format(layout)} + }} +} + +func registerRegexTest() { + Builtins["regex_test"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + re, text, errObj := regexPatternText("regex_test", args) + if errObj != nil { + return errObj + } + return object.NativeBoolToBooleanObject(re.MatchString(text)) + }} +} + +func registerRegexMatch() { + Builtins["regex_match"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + re, text, errObj := regexPatternText("regex_match", args) + if errObj != nil { + return errObj + } + match := re.FindStringSubmatch(text) + if match == nil { + return object.NULL + } + out := make([]object.Object, 0, len(match)) + for _, m := range match { + out = append(out, &object.String{Value: m}) + } + return &object.Array{Elements: out} + }} +} + +func registerRegexMatchAll() { + Builtins["regex_match_all"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + re, text, errObj := regexPatternText("regex_match_all", args) + if errObj != nil { + return errObj + } + matches := re.FindAllString(text, -1) + out := make([]object.Object, 0, len(matches)) + for _, m := range matches { + out = append(out, &object.String{Value: m}) + } + return &object.Array{Elements: out} + }} +} + +func registerRegexSearch() { + Builtins["regex_search"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + re, text, errObj := regexPatternText("regex_search", args) + if errObj != nil { + return errObj + } + loc := re.FindStringIndex(text) + if loc == nil { + return &object.Number{Value: -1} + } + return &object.Number{Value: float64(loc[0])} + }} +} + +func registerRegexReplace() { + Builtins["regex_replace"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 3 || len(args) > 4 { + return newError("wrong number of arguments. got=%d, want=3 or 4", len(args)) + } + reArgs := []object.Object{args[0], args[1]} + if len(args) == 4 { + reArgs = append(reArgs, args[3]) + } + re, text, errObj := regexPatternText("regex_replace", reArgs) + if errObj != nil { + return errObj + } + if args[2].Type() != object.STRING_OBJ { + return newError("third argument to `regex_replace` must be STRING, got %s", args[2].Type()) + } + replacement := args[2].(*object.String).Value + return &object.String{Value: re.ReplaceAllString(text, replacement)} + }} +} + +func registerMatchWrappers() { + Builtins["match"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + regexArgs, errObj := validateMatchLikeArgs(args) + if errObj != nil { + return errObj + } + return Builtins["regex_match"].Fn(regexArgs...) + }} + + Builtins["matchAll"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + regexArgs, errObj := validateMatchLikeArgs(args) + if errObj != nil { + return errObj + } + return Builtins["regex_match_all"].Fn(regexArgs...) + }} + + Builtins["search"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + regexArgs, errObj := validateMatchLikeArgs(args) + if errObj != nil { + return errObj + } + return Builtins["regex_search"].Fn(regexArgs...) + }} +} + +func validateMatchLikeArgs(args []object.Object) ([]object.Object, *object.Error) { + if len(args) < 2 || len(args) > 3 { + return nil, newError("wrong number of arguments. got=%d, want=2 or 3", len(args)) + } + regexArgs := []object.Object{args[1], args[0]} + if len(args) == 3 { + regexArgs = append(regexArgs, args[2]) + } + return regexArgs, nil +} + +func regexPatternText(name string, args []object.Object) (*regexp.Regexp, string, *object.Error) { + if len(args) < 2 || len(args) > 3 { + return nil, "", newError("wrong number of arguments. got=%d, want=2 or 3", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return nil, "", newError("first argument to `%s` must be STRING pattern, got %s", name, args[0].Type()) + } + if args[1].Type() != object.STRING_OBJ { + return nil, "", newError("second argument to `%s` must be STRING text, got %s", name, args[1].Type()) + } + pattern := args[0].(*object.String).Value + if len(args) == 3 { + if args[2].Type() != object.STRING_OBJ { + return nil, "", newError("third argument to `%s` must be STRING flags, got %s", name, args[2].Type()) + } + pattern = applyRegexFlags(pattern, args[2].(*object.String).Value) + } + + re, err := regexp.Compile(pattern) + if err != nil { + return nil, "", newError("invalid regex pattern: %s", err.Error()) + } + return re, args[1].(*object.String).Value, nil +} + +func applyRegexFlags(pattern, flags string) string { + withFlags := pattern + needsPrefix := false + for _, ch := range flags { + switch ch { + case 'i': + needsPrefix = true + withFlags = "(?i)" + withFlags + case 'm': + needsPrefix = true + withFlags = "(?m)" + withFlags + case 's': + needsPrefix = true + withFlags = "(?s)" + withFlags + } + } + if !needsPrefix { + return pattern + } + return withFlags +} diff --git a/src/evaluator/builtins/builtins_global_numeric_uri.go b/src/evaluator/builtins/builtins_global_numeric_uri.go new file mode 100644 index 0000000..50c4b33 --- /dev/null +++ b/src/evaluator/builtins/builtins_global_numeric_uri.go @@ -0,0 +1,192 @@ +package builtins + +import ( + "BanglaCode/src/object" + "math" + "net/url" + "strings" +) + +func init() { + registerParseInt() + registerParseFloat() + registerIsNaN() + registerIsFinite() + registerEncodeURI() + registerDecodeURI() + registerEncodeURIComponent() + registerDecodeURIComponent() +} + +func registerParseInt() { + Builtins["purno_sonkhya"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 1 || len(args) > 2 { + return newError("wrong number of arguments. got=%d, want=1 or 2", len(args)) + } + if args[0].Type() != object.STRING_OBJ && args[0].Type() != object.NUMBER_OBJ { + return nanNumber() + } + input := args[0].Inspect() + radix := 0 + if len(args) == 2 { + if args[1].Type() != object.NUMBER_OBJ { + return nanNumber() + } + radix = int(args[1].(*object.Number).Value) + } + if v, ok := parseIntLikeJS(input, radix); ok { + return &object.Number{Value: v} + } + return nanNumber() + }} +} + +func registerParseFloat() { + Builtins["doshomik_sonkhya"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() == object.NUMBER_OBJ { + return args[0] + } + if args[0].Type() != object.STRING_OBJ { + return nanNumber() + } + if v, ok := parseLeadingFloat(args[0].(*object.String).Value); ok { + return &object.Number{Value: v} + } + return nanNumber() + }} +} + +func registerIsNaN() { + Builtins["sonkhya_na"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + n := coerceNumber(args[0]) + return object.NativeBoolToBooleanObject(math.IsNaN(n)) + }} +} + +func registerIsFinite() { + Builtins["sonkhya_shimito"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + n := coerceNumber(args[0]) + return object.NativeBoolToBooleanObject(!math.IsNaN(n) && !math.IsInf(n, 0)) + }} +} + +func registerEncodeURI() { + Builtins["uri_encode"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + s, err := requireSingleString("uri_encode", args) + if err != nil { + return err + } + return &object.String{Value: encodeURI(s)} + }} +} + +func registerDecodeURI() { + Builtins["uri_decode"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + s, err := requireSingleString("uri_decode", args) + if err != nil { + return err + } + decoded, decErr := url.PathUnescape(s) + if decErr != nil { + return newError("invalid URI format") + } + return &object.String{Value: decoded} + }} +} + +func registerEncodeURIComponent() { + Builtins["uri_ongsho_encode"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + s, err := requireSingleString("uri_ongsho_encode", args) + if err != nil { + return err + } + encoded := url.QueryEscape(s) + encoded = strings.ReplaceAll(encoded, "+", "%20") + return &object.String{Value: encoded} + }} +} + +func registerDecodeURIComponent() { + Builtins["uri_ongsho_decode"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + s, err := requireSingleString("uri_ongsho_decode", args) + if err != nil { + return err + } + decoded, decErr := url.QueryUnescape(s) + if decErr != nil { + return newError("invalid URI component format") + } + return &object.String{Value: decoded} + }} +} + +func requireSingleString(name string, args []object.Object) (string, *object.Error) { + if len(args) != 1 { + return "", newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return "", newError("argument to `%s` must be STRING, got %s", name, args[0].Type()) + } + return args[0].(*object.String).Value, nil +} + +func coerceNumber(obj object.Object) float64 { + switch v := obj.(type) { + case *object.Number: + return v.Value + case *object.Boolean: + if v.Value { + return 1 + } + return 0 + case *object.Null: + return 0 + case *object.String: + if parsed, ok := parseLeadingFloat(v.Value); ok { + return parsed + } + return math.NaN() + default: + return math.NaN() + } +} + +func encodeURI(s string) string { + var b strings.Builder + for i := 0; i < len(s); i++ { + c := s[i] + if isAllowedInURI(c) { + b.WriteByte(c) + continue + } + b.WriteString("%") + b.WriteString(strings.ToUpper(hexByte(c))) + } + return b.String() +} + +func isAllowedInURI(c byte) bool { + if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { + return true + } + switch c { + case ';', ',', '/', '?', ':', '@', '&', '=', '+', '$', '#', '-', '_', '.', '!', '~', '*', '\'', '(', ')': + return true + default: + return false + } +} + +func hexByte(c byte) string { + const digits = "0123456789ABCDEF" + return string([]byte{digits[c>>4], digits[c&0x0F]}) +} diff --git a/src/evaluator/builtins/builtins_object.go b/src/evaluator/builtins/builtins_object.go index 289a010..c30b22c 100644 --- a/src/evaluator/builtins/builtins_object.go +++ b/src/evaluator/builtins/builtins_object.go @@ -2,84 +2,170 @@ package builtins import ( "BanglaCode/src/object" + "math" ) func init() { - // Values - maan (মান - values) - Builtins["maan"] = &object.Builtin{ - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("wrong number of arguments. got=%d, want=1", len(args)) - } - if args[0].Type() != object.MAP_OBJ { - return newError("argument to `maan` must be MAP, got %s", args[0].Type()) - } - - mapObj := args[0].(*object.Map) - values := make([]object.Object, 0, len(mapObj.Pairs)) + registerObjectValues() + registerObjectEntries() + registerObjectAssign() + registerObjectHasOwn() + registerObjectFromEntries() + registerObjectIs() + registerObjectCreate() + registerObjectFreeze() +} - for _, value := range mapObj.Pairs { - values = append(values, value) - } +func registerObjectValues() { + Builtins["maan"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.MAP_OBJ { + return newError("argument to `maan` must be MAP, got %s", args[0].Type()) + } + mapObj := args[0].(*object.Map) + values := make([]object.Object, 0, len(mapObj.Pairs)) + for _, value := range mapObj.Pairs { + values = append(values, value) + } + return &object.Array{Elements: values} + }} +} - return &object.Array{Elements: values} - }, - } +func registerObjectEntries() { + Builtins["jora"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.MAP_OBJ { + return newError("argument to `jora` must be MAP, got %s", args[0].Type()) + } + mapObj := args[0].(*object.Map) + entries := make([]object.Object, 0, len(mapObj.Pairs)) + for key, value := range mapObj.Pairs { + entry := &object.Array{Elements: []object.Object{&object.String{Value: key}, value}} + entries = append(entries, entry) + } + return &object.Array{Elements: entries} + }} +} - // Entries - jora (জোড়া - pairs) - Builtins["jora"] = &object.Builtin{ - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("wrong number of arguments. got=%d, want=1", len(args)) +func registerObjectAssign() { + Builtins["mishra"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 2 { + return newError("wrong number of arguments. got=%d, want at least 2", len(args)) + } + if args[0].Type() != object.MAP_OBJ { + return newError("first argument to `mishra` must be MAP, got %s", args[0].Type()) + } + target := args[0].(*object.Map) + for i := 1; i < len(args); i++ { + if args[i].Type() != object.MAP_OBJ { + return newError("argument %d to `mishra` must be MAP, got %s", i+1, args[i].Type()) } - if args[0].Type() != object.MAP_OBJ { - return newError("argument to `jora` must be MAP, got %s", args[0].Type()) + source := args[i].(*object.Map) + for key, value := range source.Pairs { + target.Pairs[key] = value } + } + return target + }} +} - mapObj := args[0].(*object.Map) - entries := make([]object.Object, 0, len(mapObj.Pairs)) +func registerObjectHasOwn() { + Builtins["nijer_ache"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.MAP_OBJ { + return newError("first argument to `nijer_ache` must be MAP, got %s", args[0].Type()) + } + key := mapKeyFromObject(args[1]) + if key == "" && args[1].Type() != object.STRING_OBJ && args[1].Type() != object.NUMBER_OBJ { + return object.FALSE + } + _, exists := args[0].(*object.Map).Pairs[key] + return object.NativeBoolToBooleanObject(exists) + }} +} - for key, value := range mapObj.Pairs { - entry := &object.Array{ - Elements: []object.Object{ - &object.String{Value: key}, - value, - }, - } - entries = append(entries, entry) +func registerObjectFromEntries() { + Builtins["jora_theke"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.ARRAY_OBJ { + return newError("argument to `jora_theke` must be ARRAY, got %s", args[0].Type()) + } + entries := args[0].(*object.Array) + result := make(map[string]object.Object, len(entries.Elements)) + for i, entryObj := range entries.Elements { + entry, ok := entryObj.(*object.Array) + if !ok || len(entry.Elements) < 2 { + return newError("entry at index %d must be [key, value]", i) } - - return &object.Array{Elements: entries} - }, - } - - // Assign - mishra (মিশ্র - mix/merge) - Builtins["mishra"] = &object.Builtin{ - Fn: func(args ...object.Object) object.Object { - if len(args) < 2 { - return newError("wrong number of arguments. got=%d, want at least 2", len(args)) + key := mapKeyFromObject(entry.Elements[0]) + if key == "" && entry.Elements[0].Type() != object.STRING_OBJ && entry.Elements[0].Type() != object.NUMBER_OBJ { + return newError("entry key at index %d must be STRING or NUMBER", i) } + result[key] = entry.Elements[1] + } + return &object.Map{Pairs: result} + }} +} - // First argument is target - if args[0].Type() != object.MAP_OBJ { - return newError("first argument to `mishra` must be MAP, got %s", args[0].Type()) +func registerObjectIs() { + Builtins["ekoi_ki"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() == object.NUMBER_OBJ && args[1].Type() == object.NUMBER_OBJ { + a := args[0].(*object.Number).Value + b := args[1].(*object.Number).Value + if math.IsNaN(a) && math.IsNaN(b) { + return object.TRUE } + return object.NativeBoolToBooleanObject(a == b) + } + return object.NativeBoolToBooleanObject(objectsEqual(args[0], args[1])) + }} +} - target := args[0].(*object.Map) - - // Merge all source objects into target - for i := 1; i < len(args); i++ { - if args[i].Type() != object.MAP_OBJ { - return newError("argument %d to `mishra` must be MAP, got %s", i+1, args[i].Type()) - } - - source := args[i].(*object.Map) - for key, value := range source.Pairs { - target.Pairs[key] = value - } +func registerObjectCreate() { + Builtins["notun_map"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) < 1 || len(args) > 2 { + return newError("wrong number of arguments. got=%d, want=1 or 2", len(args)) + } + if args[0].Type() != object.MAP_OBJ && args[0].Type() != object.NULL_OBJ { + return newError("first argument to `notun_map` must be MAP or NULL, got %s", args[0].Type()) + } + out := &object.Map{Pairs: make(map[string]object.Object)} + if args[0].Type() == object.MAP_OBJ { + for k, v := range args[0].(*object.Map).Pairs { + out.Pairs[k] = v + } + } + if len(args) == 2 { + if args[1].Type() != object.MAP_OBJ { + return newError("second argument to `notun_map` must be MAP, got %s", args[1].Type()) } + for k, v := range args[1].(*object.Map).Pairs { + out.Pairs[k] = v + } + } + return out + }} +} - return target - }, - } +func registerObjectFreeze() { + Builtins["joma"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.MAP_OBJ { + return newError("argument to `joma` must be MAP, got %s", args[0].Type()) + } + return args[0] + }} } diff --git a/src/evaluator/builtins/builtins_string_extended.go b/src/evaluator/builtins/builtins_string_extended.go new file mode 100644 index 0000000..bb4ddf4 --- /dev/null +++ b/src/evaluator/builtins/builtins_string_extended.go @@ -0,0 +1,268 @@ +package builtins + +import ( + "BanglaCode/src/object" + "strings" + "unicode/utf8" +) + +func init() { + registerStringIncludes() + registerStringStartsWith() + registerStringEndsWith() + registerStringRepeat() + registerStringPad() + registerStringAt() + registerStringCharCodeAt() + registerStringTrim() + registerStringLastIndexOf() + registerStringCodePointAt() + registerStringLocaleCompare() + registerStringNormalize() +} + +func registerStringIncludes() { + Builtins["ache_text"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + str, part, err := requireTwoStrings("ache_text", args) + if err != nil { + return err + } + return object.NativeBoolToBooleanObject(strings.Contains(str, part)) + }} +} + +func registerStringStartsWith() { + Builtins["shuru_diye"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + str, prefix, err := requireTwoStrings("shuru_diye", args) + if err != nil { + return err + } + return object.NativeBoolToBooleanObject(strings.HasPrefix(str, prefix)) + }} +} + +func registerStringEndsWith() { + Builtins["shesh_diye"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + str, suffix, err := requireTwoStrings("shesh_diye", args) + if err != nil { + return err + } + return object.NativeBoolToBooleanObject(strings.HasSuffix(str, suffix)) + }} +} + +func registerStringRepeat() { + Builtins["baro"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("first argument to `baro` must be STRING, got %s", args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `baro` must be NUMBER, got %s", args[1].Type()) + } + n := int(args[1].(*object.Number).Value) + if n < 0 { + return newError("repeat count must be >= 0") + } + return &object.String{Value: strings.Repeat(args[0].(*object.String).Value, n)} + }} +} + +func registerStringPad() { + Builtins["agey_bhoro"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return padString("agey_bhoro", true, args) + }} + Builtins["pichoney_bhoro"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return padString("pichoney_bhoro", false, args) + }} +} + +func registerStringAt() { + Builtins["okkhor"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return textAt("okkhor", false, args) + }} + Builtins["text_at"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return textAt("text_at", true, args) + }} +} + +func registerStringCharCodeAt() { + Builtins["okkhor_code"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("first argument to `okkhor_code` must be STRING, got %s", args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `okkhor_code` must be NUMBER, got %s", args[1].Type()) + } + runes := []rune(args[0].(*object.String).Value) + idx := int(args[1].(*object.Number).Value) + if idx < 0 || idx >= len(runes) { + return nanNumber() + } + return &object.Number{Value: float64(runes[idx])} + }} +} + +func registerStringTrim() { + Builtins["chhanto_shuru"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("argument to `chhanto_shuru` must be STRING, got %s", args[0].Type()) + } + return &object.String{Value: strings.TrimLeftFunc(args[0].(*object.String).Value, unicodeSpace)} + }} + + Builtins["chhanto_shesh"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("argument to `chhanto_shesh` must be STRING, got %s", args[0].Type()) + } + return &object.String{Value: strings.TrimRightFunc(args[0].(*object.String).Value, unicodeSpace)} + }} +} + +func registerStringLastIndexOf() { + Builtins["shesh_khojo"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + str, part, err := requireTwoStrings("shesh_khojo", args) + if err != nil { + return err + } + return &object.Number{Value: float64(strings.LastIndex(str, part))} + }} +} + +func registerStringCodePointAt() { + Builtins["codepoint_at"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("first argument to `codepoint_at` must be STRING, got %s", args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `codepoint_at` must be NUMBER, got %s", args[1].Type()) + } + runes := []rune(args[0].(*object.String).Value) + idx := int(args[1].(*object.Number).Value) + if idx < 0 || idx >= len(runes) { + return nanNumber() + } + return &object.Number{Value: float64(runes[idx])} + }} +} + +func registerStringLocaleCompare() { + Builtins["tulona_text"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + left, right, err := requireTwoStrings("tulona_text", args) + if err != nil { + return err + } + if left < right { + return &object.Number{Value: -1} + } + if left > right { + return &object.Number{Value: 1} + } + return &object.Number{Value: 0} + }} +} + +func registerStringNormalize() { + Builtins["shadharon_text"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("argument to `shadharon_text` must be STRING, got %s", args[0].Type()) + } + return &object.String{Value: args[0].(*object.String).Value} + }} +} + +func requireTwoStrings(name string, args []object.Object) (string, string, *object.Error) { + if len(args) != 2 { + return "", "", newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return "", "", newError("first argument to `%s` must be STRING, got %s", name, args[0].Type()) + } + if args[1].Type() != object.STRING_OBJ { + return "", "", newError("second argument to `%s` must be STRING, got %s", name, args[1].Type()) + } + return args[0].(*object.String).Value, args[1].(*object.String).Value, nil +} + +func padString(name string, left bool, args []object.Object) object.Object { + if len(args) < 2 || len(args) > 3 { + return newError("wrong number of arguments. got=%d, want=2 or 3", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("first argument to `%s` must be STRING, got %s", name, args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `%s` must be NUMBER, got %s", name, args[1].Type()) + } + + str := args[0].(*object.String).Value + target := int(args[1].(*object.Number).Value) + if target <= utf8.RuneCountInString(str) { + return &object.String{Value: str} + } + + pad := " " + if len(args) == 3 { + if args[2].Type() != object.STRING_OBJ { + return newError("third argument to `%s` must be STRING, got %s", name, args[2].Type()) + } + pad = args[2].(*object.String).Value + if pad == "" { + pad = " " + } + } + + need := target - utf8.RuneCountInString(str) + repeatCount := need/utf8.RuneCountInString(pad) + 2 + filler := []rune(strings.Repeat(pad, repeatCount)) + if len(filler) > need { + filler = filler[:need] + } + if left { + return &object.String{Value: string(filler) + str} + } + return &object.String{Value: str + string(filler)} +} + +func textAt(name string, allowNegative bool, args []object.Object) object.Object { + if len(args) != 2 { + return newError("wrong number of arguments. got=%d, want=2", len(args)) + } + if args[0].Type() != object.STRING_OBJ { + return newError("first argument to `%s` must be STRING, got %s", name, args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return newError("second argument to `%s` must be NUMBER, got %s", name, args[1].Type()) + } + + runes := []rune(args[0].(*object.String).Value) + idx := int(args[1].(*object.Number).Value) + if allowNegative && idx < 0 { + idx = len(runes) + idx + } + if idx < 0 || idx >= len(runes) { + return &object.String{Value: ""} + } + return &object.String{Value: string(runes[idx])} +} + +func unicodeSpace(r rune) bool { + return r == ' ' || r == '\n' || r == '\r' || r == '\t' || r == '\f' || r == '\v' +} diff --git a/src/evaluator/builtins/builtins_timers.go b/src/evaluator/builtins/builtins_timers.go new file mode 100644 index 0000000..0655037 --- /dev/null +++ b/src/evaluator/builtins/builtins_timers.go @@ -0,0 +1,195 @@ +package builtins + +import ( + "BanglaCode/src/object" + "sync" + "time" +) + +var ( + timerMu sync.Mutex + nextTimerID float64 = 1 + timeouts = map[int]chan struct{}{} + intervals = map[int]*intervalControl{} +) + +type intervalControl struct { + stop chan struct{} + done chan struct{} +} + +func init() { + registerSetTimeout() + registerSetInterval() + registerClearTimeout() + registerClearInterval() +} + +func registerSetTimeout() { + Builtins["setTimeout"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + cb, cbArgs, ms, errObj := parseTimerArgs("setTimeout", args) + if errObj != nil { + return errObj + } + + id, stopCh := newTimerID(true) + go func() { + select { + case <-time.After(time.Duration(ms) * time.Millisecond): + EvalFunc(cb, cbArgs) + case <-stopCh: + } + timerMu.Lock() + delete(timeouts, id) + timerMu.Unlock() + }() + return &object.Number{Value: float64(id)} + }} +} + +func registerSetInterval() { + Builtins["setInterval"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + cb, cbArgs, ms, errObj := parseTimerArgs("setInterval", args) + if errObj != nil { + return errObj + } + if ms <= 0 { + ms = 1 + } + + id, ctrl := newIntervalID() + go func() { + ticker := time.NewTicker(time.Duration(ms) * time.Millisecond) + defer ticker.Stop() + defer close(ctrl.done) + for { + select { + case <-ticker.C: + select { + case <-ctrl.stop: + removeInterval(id) + return + default: + } + EvalFunc(cb, cbArgs) + case <-ctrl.stop: + removeInterval(id) + return + } + } + }() + return &object.Number{Value: float64(id)} + }} +} + +func registerClearTimeout() { + Builtins["clearTimeout"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return clearTimeoutTimer(args) + }} +} + +func registerClearInterval() { + Builtins["clearInterval"] = &object.Builtin{Fn: func(args ...object.Object) object.Object { + return clearIntervalTimer(args) + }} +} + +func parseTimerArgs(name string, args []object.Object) (*object.Function, []object.Object, int64, *object.Error) { + if len(args) < 2 { + return nil, nil, 0, newError("wrong number of arguments. got=%d, want>=2", len(args)) + } + if args[0].Type() != object.FUNCTION_OBJ { + return nil, nil, 0, newError("first argument to `%s` must be FUNCTION, got %s", name, args[0].Type()) + } + if args[1].Type() != object.NUMBER_OBJ { + return nil, nil, 0, newError("second argument to `%s` must be NUMBER delay(ms), got %s", name, args[1].Type()) + } + cb := args[0].(*object.Function) + ms := int64(args[1].(*object.Number).Value) + cbArgs := []object.Object{} + if len(args) > 2 { + cbArgs = append(cbArgs, args[2:]...) + } + return cb, cbArgs, ms, nil +} + +func newTimerID(timeout bool) (int, chan struct{}) { + timerMu.Lock() + defer timerMu.Unlock() + + id := int(nextTimerID) + nextTimerID++ + stopCh := make(chan struct{}, 1) + if timeout { + timeouts[id] = stopCh + } + return id, stopCh +} + +func newIntervalID() (int, *intervalControl) { + timerMu.Lock() + defer timerMu.Unlock() + + id := int(nextTimerID) + nextTimerID++ + ctrl := &intervalControl{ + stop: make(chan struct{}), + done: make(chan struct{}), + } + intervals[id] = ctrl + return id, ctrl +} + +func clearTimeoutTimer(args []object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.NUMBER_OBJ { + return newError("argument must be NUMBER timer id, got %s", args[0].Type()) + } + id := int(args[0].(*object.Number).Value) + + timerMu.Lock() + ch, ok := timeouts[id] + if ok { + select { + case ch <- struct{}{}: + default: + } + delete(timeouts, id) + } + timerMu.Unlock() + return object.NULL +} + +func clearIntervalTimer(args []object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + if args[0].Type() != object.NUMBER_OBJ { + return newError("argument must be NUMBER timer id, got %s", args[0].Type()) + } + id := int(args[0].(*object.Number).Value) + + timerMu.Lock() + ctrl, ok := intervals[id] + timerMu.Unlock() + if ok { + close(ctrl.stop) + <-ctrl.done + } + return object.NULL +} + +func removeInterval(id int) { + timerMu.Lock() + delete(intervals, id) + timerMu.Unlock() +} + +func clearTimer(args []object.Object, timeout bool) object.Object { + if timeout { + return clearTimeoutTimer(args) + } + return clearIntervalTimer(args) +} diff --git a/src/evaluator/builtins/helpers.go b/src/evaluator/builtins/helpers.go index 228ee12..648d5af 100644 --- a/src/evaluator/builtins/helpers.go +++ b/src/evaluator/builtins/helpers.go @@ -3,6 +3,10 @@ package builtins import ( "BanglaCode/src/object" "fmt" + "math" + "strconv" + "strings" + "unicode" ) // newError creates a new error object without position info @@ -29,3 +33,117 @@ func objectsEqual(left, right object.Object) bool { return left == right } } + +// isTruthy matches evaluator truthiness rules for builtin callbacks +func isTruthy(obj object.Object) bool { + if obj == nil { + return false + } + if obj == object.NULL || obj == object.FALSE { + return false + } + if b, ok := obj.(*object.Boolean); ok { + return b.Value + } + return true +} + +// parseLeadingFloat parses leading numeric content similar to JS parseFloat +func parseLeadingFloat(input string) (float64, bool) { + s := strings.TrimSpace(input) + if s == "" { + return 0, false + } + + maxLen := 0 + for i := 1; i <= len(s); i++ { + if _, err := strconv.ParseFloat(s[:i], 64); err == nil { + maxLen = i + } + } + if maxLen == 0 { + return 0, false + } + v, err := strconv.ParseFloat(s[:maxLen], 64) + if err != nil { + return 0, false + } + return v, true +} + +// parseIntLikeJS parses input like JS parseInt with optional radix +func parseIntLikeJS(input string, radix int) (float64, bool) { + s := strings.TrimSpace(input) + if s == "" { + return 0, false + } + + sign := 1.0 + if s[0] == '+' || s[0] == '-' { + if s[0] == '-' { + sign = -1 + } + s = s[1:] + if s == "" { + return 0, false + } + } + + base := radix + if base == 0 { + base = 10 + if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + base = 16 + s = s[2:] + } + } + if base < 2 || base > 36 { + return 0, false + } + + if base == 16 && len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { + s = s[2:] + } + + i := 0 + for i < len(s) && isValidDigitForBase(rune(s[i]), base) { + i++ + } + if i == 0 { + return 0, false + } + + val, err := strconv.ParseInt(s[:i], base, 64) + if err != nil { + return 0, false + } + return sign * float64(val), true +} + +func isValidDigitForBase(ch rune, base int) bool { + if unicode.IsDigit(ch) { + return int(ch-'0') < base + } + if ch >= 'a' && ch <= 'z' { + return int(ch-'a')+10 < base + } + if ch >= 'A' && ch <= 'Z' { + return int(ch-'A')+10 < base + } + return false +} + +func nanNumber() *object.Number { + return &object.Number{Value: math.NaN()} +} + +func mapKeyFromObject(key object.Object) string { + switch k := key.(type) { + case *object.String: + return k.Value + case *object.Number: + return k.Inspect() + default: + return "" + } +} diff --git a/src/evaluator/destructuring.go b/src/evaluator/destructuring.go new file mode 100644 index 0000000..c40794f --- /dev/null +++ b/src/evaluator/destructuring.go @@ -0,0 +1,60 @@ +package evaluator + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/object" +) + +func evalArrayDestructuringDeclaration(node *ast.ArrayDestructuringDeclaration, env *object.Environment) object.Object { + source := Eval(node.Source, env) + if isError(source) { + return source + } + arr, ok := source.(*object.Array) + if !ok { + return newError("array destructuring source must be ARRAY, got %s", source.Type()) + } + + for i, name := range node.Names { + var val object.Object = object.NULL + if i < len(arr.Elements) { + val = arr.Elements[i] + } + bindValue(env, name.Value, val, node.IsConstant, node.IsGlobal) + } + + return source +} + +func evalObjectDestructuringDeclaration(node *ast.ObjectDestructuringDeclaration, env *object.Environment) object.Object { + source := Eval(node.Source, env) + if isError(source) { + return source + } + m, ok := source.(*object.Map) + if !ok { + return newError("object destructuring source must be MAP, got %s", source.Type()) + } + + for i, key := range node.Keys { + val, exists := m.Pairs[key] + if !exists { + val = object.NULL + } + bindValue(env, node.Names[i].Value, val, node.IsConstant, node.IsGlobal) + } + + return source +} + +func bindValue(env *object.Environment, name string, val object.Object, isConstant, isGlobal bool) { + if isConstant { + env.SetConstant(name, val) + return + } + if isGlobal { + env.SetGlobal(name, val) + return + } + env.Set(name, val) +} diff --git a/src/evaluator/evaluator.go b/src/evaluator/evaluator.go index 4a30ad3..7700f11 100644 --- a/src/evaluator/evaluator.go +++ b/src/evaluator/evaluator.go @@ -28,23 +28,33 @@ func evalFunctionCall(handler *object.Function, args []object.Object) object.Obj // Eval evaluates an AST node and returns the resulting object func Eval(node ast.Node, env *object.Environment) object.Object { - switch node := node.(type) { - - // ==================== Statements ==================== + if out, ok := evalStatementNode(node, env); ok { + return out + } + if out, ok := evalControlNode(node, env); ok { + return out + } + if out, ok := evalLiteralNode(node, env); ok { + return out + } + if out, ok := evalExpressionNode(node, env); ok { + return out + } + return nil +} +func evalStatementNode(node ast.Node, env *object.Environment) (object.Object, bool) { + switch node := node.(type) { case *ast.Program: - return evalProgram(node.Statements, env) - + return evalProgram(node.Statements, env), true case *ast.ExpressionStatement: - return Eval(node.Expression, env) - + return Eval(node.Expression, env), true case *ast.BlockStatement: - return evalBlockStatement(node, env) - + return evalBlockStatement(node, env), true case *ast.VariableDeclaration: val := Eval(node.Value, env) if isError(val) { - return val + return val, true } if node.IsConstant { env.SetConstant(node.Name.Value, val) @@ -53,146 +63,157 @@ func Eval(node ast.Node, env *object.Environment) object.Object { } else { env.Set(node.Name.Value, val) } - return val + return val, true + case *ast.ArrayDestructuringDeclaration: + return evalArrayDestructuringDeclaration(node, env), true + case *ast.ObjectDestructuringDeclaration: + return evalObjectDestructuringDeclaration(node, env), true + } + return evalFlowStatementNode(node, env) +} +func evalFlowStatementNode(node ast.Node, env *object.Environment) (object.Object, bool) { + switch node := node.(type) { case *ast.IfStatement: - return evalIfStatement(node, env) - + return evalIfStatement(node, env), true case *ast.WhileStatement: - return evalWhileStatement(node, env) - + return evalWhileStatement(node, env), true + case *ast.DoWhileStatement: + return evalDoWhileStatement(node, env), true case *ast.ForStatement: - return evalForStatement(node, env) - + return evalForStatement(node, env), true + case *ast.ForOfStatement: + return evalForOfStatement(node, env), true + case *ast.ForInStatement: + return evalForInStatement(node, env), true case *ast.ReturnStatement: val := Eval(node.ReturnValue, env) if isError(val) { - return val + return val, true } - return &object.ReturnValue{Value: val} - + return &object.ReturnValue{Value: val}, true case *ast.BreakStatement: - return object.BREAK - + return object.BREAK, true case *ast.ContinueStatement: - return object.CONTINUE - + return object.CONTINUE, true case *ast.SwitchStatement: - return evalSwitchStatement(node, env) - - // ==================== Classes & Modules ==================== + return evalSwitchStatement(node, env), true + } + return nil, false +} +func evalControlNode(node ast.Node, env *object.Environment) (object.Object, bool) { + switch node := node.(type) { case *ast.ClassDeclaration: - return evalClassDeclaration(node, env) - + return evalClassDeclaration(node, env), true case *ast.ImportStatement: - return evalImportStatement(node, env) - + return evalImportStatement(node, env), true case *ast.ExportStatement: - return evalExportStatement(node, env) - - // ==================== Error Handling ==================== - + return evalExportStatement(node, env), true case *ast.TryCatchStatement: - return evalTryCatchStatement(node, env) - + return evalTryCatchStatement(node, env), true case *ast.ThrowStatement: - return evalThrowStatement(node, env) - - // ==================== Literals ==================== + return evalThrowStatement(node, env), true + } + return nil, false +} +func evalLiteralNode(node ast.Node, env *object.Environment) (object.Object, bool) { + switch node := node.(type) { case *ast.NumberLiteral: - return &object.Number{Value: node.Value} - + return &object.Number{Value: node.Value}, true case *ast.StringLiteral: - return &object.String{Value: node.Value} - + return &object.String{Value: node.Value}, true case *ast.TemplateLiteral: - return evalTemplateLiteral(node, env) - + return evalTemplateLiteral(node, env), true case *ast.BooleanLiteral: - return object.NativeBoolToBooleanObject(node.Value) - + return object.NativeBoolToBooleanObject(node.Value), true case *ast.NullLiteral: - return object.NULL - + return object.NULL, true case *ast.ArrayLiteral: elements := evalExpressions(node.Elements, env) if len(elements) == 1 && isError(elements[0]) { - return elements[0] + return elements[0], true } - return &object.Array{Elements: elements} - + return &object.Array{Elements: elements}, true case *ast.MapLiteral: - return evalMapLiteral(node, env) - - // ==================== Expressions ==================== + return evalMapLiteral(node, env), true + } + return nil, false +} +func evalExpressionNode(node ast.Node, env *object.Environment) (object.Object, bool) { + switch node := node.(type) { case *ast.Identifier: - return evalIdentifier(node, env) - + return evalIdentifier(node, env), true case *ast.UnaryExpression: right := Eval(node.Right, env) if isError(right) { - return right + return right, true } - return evalUnaryExpression(node.Operator, right) - + return evalUnaryExpression(node.Operator, right), true case *ast.BinaryExpression: - left := Eval(node.Left, env) - if isError(left) { - return left - } - right := Eval(node.Right, env) - if isError(right) { - return right - } - return evalBinaryExpression(node.Operator, left, right) - + return evalBinaryNode(node, env), true + case *ast.DeleteExpression: + return evalDeleteExpression(node, env), true case *ast.AssignmentExpression: - return evalAssignmentExpression(node, env) - + return evalAssignmentExpression(node, env), true case *ast.CallExpression: - function := Eval(node.Function, env) - if isError(function) { - return function - } - args := evalExpressions(node.Arguments, env) - if len(args) == 1 && isError(args[0]) { - return args[0] - } - return applyFunctionWithPosition(function, args, env, node.Token.Line, node.Token.Column, node.Function) - + return evalCallExpression(node, env), true case *ast.MemberExpression: - return evalMemberExpression(node, env) - + return evalMemberExpression(node, env), true case *ast.FunctionLiteral: - params := node.Parameters - body := node.Body - name := "" - if node.Name != nil { - name = node.Name.Value - } - fn := &object.Function{Parameters: params, RestParameter: node.RestParameter, Env: env, Body: body, Name: name} - if name != "" { - env.Set(name, fn) - } - return fn - + return buildFunctionLiteral(node, env), true case *ast.NewExpression: - return evalNewExpression(node, env) - + return evalNewExpression(node, env), true case *ast.SpreadElement: - return evalSpreadElement(node, env) - - // ==================== Async/Await ==================== - + return evalSpreadElement(node, env), true case *ast.AsyncFunctionLiteral: - return evalAsyncFunctionLiteral(node, env) - + return evalAsyncFunctionLiteral(node, env), true case *ast.AwaitExpression: - return evalAwaitExpression(node, env) + return evalAwaitExpression(node, env), true } + return nil, false +} - return nil +func evalBinaryNode(node *ast.BinaryExpression, env *object.Environment) object.Object { + left := Eval(node.Left, env) + if isError(left) { + return left + } + right := Eval(node.Right, env) + if isError(right) { + return right + } + return evalBinaryExpression(node.Operator, left, right) +} + +func evalCallExpression(node *ast.CallExpression, env *object.Environment) object.Object { + function := Eval(node.Function, env) + if isError(function) { + return function + } + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + return applyFunctionWithPosition(function, args, env, node.Token.Line, node.Token.Column, node.Function) +} + +func buildFunctionLiteral(node *ast.FunctionLiteral, env *object.Environment) object.Object { + name := "" + if node.Name != nil { + name = node.Name.Value + } + fn := &object.Function{ + Parameters: node.Parameters, + RestParameter: node.RestParameter, + Env: env, + Body: node.Body, + Name: name, + } + if name != "" { + env.Set(name, fn) + } + return fn } diff --git a/src/evaluator/expressions.go b/src/evaluator/expressions.go index 5ad7854..4d2f942 100644 --- a/src/evaluator/expressions.go +++ b/src/evaluator/expressions.go @@ -54,6 +54,10 @@ func evalBinaryExpression(operator string, left, right object.Object) object.Obj return evalStringBinaryExpression(operator, left, right) case left.Type() == object.STRING_OBJ && right.Type() == object.NUMBER_OBJ: return evalStringNumberBinaryExpression(operator, left, right) + case operator == "in": + return evalInOperator(left, right) + case operator == "instanceof": + return evalInstanceofOperator(left, right) case operator == "==" || operator == "soman": return boolToObject(left == right) case operator == "!=" || operator == "osoman": @@ -190,180 +194,6 @@ func evalAssignmentExpression(ae *ast.AssignmentExpression, env *object.Environm } } -// evalMemberAssignment handles assignment to object properties or array elements -func evalMemberAssignment(member *ast.MemberExpression, operator string, value ast.Expression, env *object.Environment) object.Object { - obj := Eval(member.Object, env) - if isError(obj) { - return obj - } - - val := Eval(value, env) - if isError(val) { - return val - } - - switch o := obj.(type) { - case *object.Array: - index := Eval(member.Property, env) - if isError(index) { - return index - } - if index.Type() != object.NUMBER_OBJ { - return newError("array index must be a number, got %s", index.Type()) - } - idx := int(index.(*object.Number).Value) - if idx < 0 || idx >= len(o.Elements) { - return newError("array index out of bounds: %d", idx) - } - - // Handle compound operators - if operator != "=" { - current := o.Elements[idx] - op := string(operator[0]) - val = evalBinaryExpression(op, current, val) - if isError(val) { - return val - } - } - - o.Elements[idx] = val - return val - - case *object.Map: - var key string - if member.Computed { - keyObj := Eval(member.Property, env) - if isError(keyObj) { - return keyObj - } - key = getMapKey(keyObj) - } else { - if ident, ok := member.Property.(*ast.Identifier); ok { - key = ident.Value - } else { - return newError("invalid map key") - } - } - - // Handle compound operators - if operator != "=" { - current, ok := o.Pairs[key] - if !ok { - return newError("key '%s' not found in map", key) - } - op := string(operator[0]) - val = evalBinaryExpression(op, current, val) - if isError(val) { - return val - } - } - - o.Pairs[key] = val - return val - - case *object.Instance: - if ident, ok := member.Property.(*ast.Identifier); ok { - // Handle compound operators - if operator != "=" { - current, ok := o.Properties[ident.Value] - if !ok { - return newError("property '%s' not found", ident.Value) - } - op := string(operator[0]) - val = evalBinaryExpression(op, current, val) - if isError(val) { - return val - } - } - - o.Properties[ident.Value] = val - return val - } - return newError("invalid property name") - - default: - return newError("cannot assign to %s", obj.Type()) - } -} - -// evalMemberExpression evaluates member access (obj.prop or arr[idx]) -func evalMemberExpression(me *ast.MemberExpression, env *object.Environment) object.Object { - obj := Eval(me.Object, env) - if isError(obj) { - return obj - } - - switch o := obj.(type) { - case *object.Array: - index := Eval(me.Property, env) - if isError(index) { - return index - } - return evalArrayIndex(o, index) - - case *object.Map: - var key string - if me.Computed { - keyObj := Eval(me.Property, env) - if isError(keyObj) { - return keyObj - } - key = getMapKey(keyObj) - } else { - if ident, ok := me.Property.(*ast.Identifier); ok { - key = ident.Value - } else { - return newError("invalid map key") - } - } - if val, ok := o.Pairs[key]; ok { - return val - } - return object.NULL - - case *object.Instance: - if ident, ok := me.Property.(*ast.Identifier); ok { - // Check properties first - if val, ok := o.Properties[ident.Value]; ok { - return val - } - // Check methods - if method, ok := o.Class.Methods[ident.Value]; ok { - // Bind 'ei' (this) to instance by creating a new environment - boundEnv := object.NewEnclosedEnvironment(method.Env) - boundEnv.Set("ei", o) - return &object.Function{ - Parameters: method.Parameters, - Body: method.Body, - Env: boundEnv, - Name: method.Name, - } - } - return object.NULL - } - return newError("invalid property name") - - default: - return newError("member access not supported on %s", obj.Type()) - } -} - -// evalArrayIndex evaluates array indexing -func evalArrayIndex(array *object.Array, index object.Object) object.Object { - if index.Type() != object.NUMBER_OBJ { - return newError("array index must be a number, got %s", index.Type()) - } - - idx := int(index.(*object.Number).Value) - max := len(array.Elements) - 1 - - if idx < 0 || idx > max { - return object.NULL - } - - return array.Elements[idx] -} - // evalMapLiteral evaluates map/object literals func evalMapLiteral(node *ast.MapLiteral, env *object.Environment) object.Object { pairs := make(map[string]object.Object) diff --git a/src/evaluator/expressions_member.go b/src/evaluator/expressions_member.go new file mode 100644 index 0000000..eb26452 --- /dev/null +++ b/src/evaluator/expressions_member.go @@ -0,0 +1,199 @@ +package evaluator + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/object" +) + +// evalMemberAssignment handles assignment to object properties or array elements +func evalMemberAssignment(member *ast.MemberExpression, operator string, value ast.Expression, env *object.Environment) object.Object { + obj := Eval(member.Object, env) + if isError(obj) { + return obj + } + + val := Eval(value, env) + if isError(val) { + return val + } + + switch o := obj.(type) { + case *object.Array: + return assignArrayMember(o, member, operator, val, env) + + case *object.Map: + return assignMapMember(o, member, operator, val, env) + + case *object.Instance: + return assignInstanceMember(o, member, operator, val) + + default: + return newError("cannot assign to %s", obj.Type()) + } +} + +// evalMemberExpression evaluates member access (obj.prop or arr[idx]) +func evalMemberExpression(me *ast.MemberExpression, env *object.Environment) object.Object { + obj := Eval(me.Object, env) + if isError(obj) { + return obj + } + + switch o := obj.(type) { + case *object.Array: + return accessArrayMember(o, me, env) + + case *object.Map: + return accessMapMember(o, me, env) + + case *object.Instance: + return accessInstanceMember(o, me) + + default: + return newError("member access not supported on %s", obj.Type()) + } +} + +func assignArrayMember(arr *object.Array, member *ast.MemberExpression, operator string, val object.Object, env *object.Environment) object.Object { + index := Eval(member.Property, env) + if isError(index) { + return index + } + if index.Type() != object.NUMBER_OBJ { + return newError("array index must be a number, got %s", index.Type()) + } + idx := int(index.(*object.Number).Value) + if idx < 0 || idx >= len(arr.Elements) { + return newError("array index out of bounds: %d", idx) + } + + if operator != "=" { + current := arr.Elements[idx] + op := string(operator[0]) + val = evalBinaryExpression(op, current, val) + if isError(val) { + return val + } + } + + arr.Elements[idx] = val + return val +} + +func assignMapMember(m *object.Map, member *ast.MemberExpression, operator string, val object.Object, env *object.Environment) object.Object { + key, errObj := resolveMapMemberKey(member, env) + if errObj != nil { + return errObj + } + + if operator != "=" { + current, ok := m.Pairs[key] + if !ok { + return newError("key '%s' not found in map", key) + } + op := string(operator[0]) + val = evalBinaryExpression(op, current, val) + if isError(val) { + return val + } + } + + m.Pairs[key] = val + return val +} + +func assignInstanceMember(inst *object.Instance, member *ast.MemberExpression, operator string, val object.Object) object.Object { + ident, ok := member.Property.(*ast.Identifier) + if !ok { + return newError("invalid property name") + } + + if operator != "=" { + current, ok := inst.Properties[ident.Value] + if !ok { + return newError("property '%s' not found", ident.Value) + } + op := string(operator[0]) + val = evalBinaryExpression(op, current, val) + if isError(val) { + return val + } + } + + inst.Properties[ident.Value] = val + return val +} + +func accessArrayMember(arr *object.Array, me *ast.MemberExpression, env *object.Environment) object.Object { + index := Eval(me.Property, env) + if isError(index) { + return index + } + return evalArrayIndex(arr, index) +} + +func accessMapMember(m *object.Map, me *ast.MemberExpression, env *object.Environment) object.Object { + key, errObj := resolveMapMemberKey(me, env) + if errObj != nil { + return errObj + } + if val, ok := m.Pairs[key]; ok { + return val + } + return object.NULL +} + +func accessInstanceMember(inst *object.Instance, me *ast.MemberExpression) object.Object { + ident, ok := me.Property.(*ast.Identifier) + if !ok { + return newError("invalid property name") + } + + if val, ok := inst.Properties[ident.Value]; ok { + return val + } + + if method, ok := inst.Class.Methods[ident.Value]; ok { + boundEnv := object.NewEnclosedEnvironment(method.Env) + boundEnv.Set("ei", inst) + return &object.Function{ + Parameters: method.Parameters, + Body: method.Body, + Env: boundEnv, + Name: method.Name, + } + } + + return object.NULL +} + +func resolveMapMemberKey(member *ast.MemberExpression, env *object.Environment) (string, *object.Error) { + if member.Computed { + keyObj := Eval(member.Property, env) + if isError(keyObj) { + return "", keyObj.(*object.Error) + } + return getMapKey(keyObj), nil + } + ident, ok := member.Property.(*ast.Identifier) + if !ok { + return "", newError("invalid map key") + } + return ident.Value, nil +} + +// evalArrayIndex evaluates array indexing +func evalArrayIndex(array *object.Array, index object.Object) object.Object { + if index.Type() != object.NUMBER_OBJ { + return newError("array index must be a number, got %s", index.Type()) + } + + idx := int(index.(*object.Number).Value) + max := len(array.Elements) - 1 + + if idx < 0 || idx > max { + return object.NULL + } + + return array.Elements[idx] +} diff --git a/src/evaluator/loops_advanced.go b/src/evaluator/loops_advanced.go new file mode 100644 index 0000000..992147f --- /dev/null +++ b/src/evaluator/loops_advanced.go @@ -0,0 +1,131 @@ +package evaluator + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/object" + "sort" +) + +func evalForOfStatement(stmt *ast.ForOfStatement, env *object.Environment) object.Object { + iterable := Eval(stmt.Iterable, env) + if isError(iterable) { + return iterable + } + + loopEnv := object.NewEnclosedEnvironment(env) + elements, err := toForOfElements(iterable) + if err != nil { + return err + } + + for _, el := range elements { + loopEnv.Update(stmt.VarName.Value, el) + result := Eval(stmt.Body, loopEnv) + if result != nil { + switch result.Type() { + case object.RETURN_OBJ, object.ERROR_OBJ, object.EXCEPTION_OBJ: + return result + case object.BREAK_OBJ: + return object.NULL + case object.CONTINUE_OBJ: + continue + } + } + } + + return object.NULL +} + +func evalForInStatement(stmt *ast.ForInStatement, env *object.Environment) object.Object { + target := Eval(stmt.Object, env) + if isError(target) { + return target + } + + loopEnv := object.NewEnclosedEnvironment(env) + keys, err := toForInKeys(target) + if err != nil { + return err + } + + for _, key := range keys { + loopEnv.Update(stmt.VarName.Value, key) + result := Eval(stmt.Body, loopEnv) + if result != nil { + switch result.Type() { + case object.RETURN_OBJ, object.ERROR_OBJ, object.EXCEPTION_OBJ: + return result + case object.BREAK_OBJ: + return object.NULL + case object.CONTINUE_OBJ: + continue + } + } + } + + return object.NULL +} + +func toForOfElements(iterable object.Object) ([]object.Object, *object.Error) { + switch it := iterable.(type) { + case *object.Array: + elements := make([]object.Object, len(it.Elements)) + copy(elements, it.Elements) + return elements, nil + + case *object.String: + runes := []rune(it.Value) + elements := make([]object.Object, 0, len(runes)) + for _, r := range runes { + elements = append(elements, &object.String{Value: string(r)}) + } + return elements, nil + + case *object.Map: + keys := make([]string, 0, len(it.Pairs)) + for k := range it.Pairs { + keys = append(keys, k) + } + sort.Strings(keys) + elements := make([]object.Object, 0, len(keys)) + for _, k := range keys { + elements = append(elements, it.Pairs[k]) + } + return elements, nil + } + + return nil, newError("for...of target must be ARRAY, STRING, or MAP, got %s", iterable.Type()) +} + +func toForInKeys(target object.Object) ([]object.Object, *object.Error) { + switch it := target.(type) { + case *object.Map: + keys := make([]string, 0, len(it.Pairs)) + for k := range it.Pairs { + keys = append(keys, k) + } + sort.Strings(keys) + out := make([]object.Object, 0, len(keys)) + for _, k := range keys { + out = append(out, &object.String{Value: k}) + } + return out, nil + + case *object.Array: + out := make([]object.Object, 0, len(it.Elements)) + for i := range it.Elements { + out = append(out, &object.Number{Value: float64(i)}) + } + return out, nil + + case *object.String: + runes := []rune(it.Value) + out := make([]object.Object, 0, len(runes)) + for i := range runes { + out = append(out, &object.Number{Value: float64(i)}) + } + return out, nil + } + + return nil, newError("for...in target must be MAP, ARRAY, or STRING, got %s", target.Type()) +} diff --git a/src/lexer/lexer.go b/src/lexer/lexer.go index 9288261..2f529a6 100644 --- a/src/lexer/lexer.go +++ b/src/lexer/lexer.go @@ -53,170 +53,181 @@ func (l *Lexer) peekChar() byte { // NextToken returns the next token from the input func (l *Lexer) NextToken() Token { - var tok Token - l.skipWhitespace() - - // Handle comments - if l.ch == '/' && l.peekChar() == '/' { - l.skipComment() + if l.consumeComment() { return l.NextToken() } + if tok, ok := l.readStringOrTemplateToken(); ok { + return tok + } + if tok, ok := l.readIdentifierOrNumberToken(); ok { + return tok + } + tok, advance := l.readSymbolToken() + if advance { + l.readChar() + } + return tok +} + +func (l *Lexer) consumeComment() bool { + if l.ch != '/' || l.peekChar() != '/' { + return false + } + l.skipComment() + return true +} + +func (l *Lexer) readStringOrTemplateToken() (Token, bool) { + switch l.ch { + case '"': + return l.readQuotedToken('"'), true + case '\'': + return l.readQuotedToken('\''), true + case '`': + return l.readTemplateToken(), true + default: + return Token{}, false + } +} + +func (l *Lexer) readQuotedToken(quote byte) Token { + tok := Token{Type: STRING, Line: l.line, Column: l.column} + tok.Literal = l.readString(quote) + l.readChar() + return tok +} + +func (l *Lexer) readTemplateToken() Token { + tok := Token{Type: TEMPLATE, Line: l.line, Column: l.column} + tok.Literal = l.readTemplate() + l.readChar() + return tok +} +func (l *Lexer) readIdentifierOrNumberToken() (Token, bool) { + if isLetter(l.ch) { + tok := Token{Line: l.line, Column: l.column} + tok.Literal = l.readIdentifier() + tok.Type = LookupIdent(tok.Literal) + return tok, true + } + if isDigit(l.ch) { + tok := Token{Type: NUMBER, Line: l.line, Column: l.column} + tok.Literal = l.readNumber() + return tok, true + } + return Token{}, false +} + +func (l *Lexer) readSymbolToken() (Token, bool) { + if tok, ok := l.readTwoCharOperator(); ok { + return tok, true + } + switch l.ch { + case '.': + return l.readDotToken() + case 0: + return NewToken(EOF, "", l.line, l.column), false + case ',', ';', ':', '(', ')', '{', '}', '[', ']', '%': + return NewToken(singleCharTokenType(l.ch), string(l.ch), l.line, l.column), true + default: + return NewToken(ILLEGAL, string(l.ch), l.line, l.column), true + } +} + +func (l *Lexer) readTwoCharOperator() (Token, bool) { switch l.ch { case '=': + if l.peekChar() == '>' { + return l.makeTwoCharToken(ARROW), true + } if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(EQ, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(ASSIGN, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(EQ), true } + return NewToken(ASSIGN, string(l.ch), l.line, l.column), true case '+': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(PLUS_ASSIGN, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(PLUS, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(PLUS_ASSIGN), true } + return NewToken(PLUS, string(l.ch), l.line, l.column), true case '-': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(MINUS_ASSIGN, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(MINUS, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(MINUS_ASSIGN), true } + return NewToken(MINUS, string(l.ch), l.line, l.column), true case '*': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(ASTERISK_ASSIGN, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(ASTERISK, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(ASTERISK_ASSIGN), true } + return NewToken(ASTERISK, string(l.ch), l.line, l.column), true case '/': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(SLASH_ASSIGN, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(SLASH, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(SLASH_ASSIGN), true } - case '%': - tok = NewToken(PERCENT, string(l.ch), l.line, l.column) + return NewToken(SLASH, string(l.ch), l.line, l.column), true case '!': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(NOT_EQ, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(BANG, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(NOT_EQ), true } + return NewToken(BANG, string(l.ch), l.line, l.column), true case '<': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(LTE, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(LT, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(LTE), true } + return NewToken(LT, string(l.ch), l.line, l.column), true case '>': if l.peekChar() == '=' { - ch := l.ch - line := l.line - column := l.column - l.readChar() - tok = NewToken(GTE, string(ch)+string(l.ch), line, column) - } else { - tok = NewToken(GT, string(l.ch), l.line, l.column) + return l.makeTwoCharToken(GTE), true } + return NewToken(GT, string(l.ch), l.line, l.column), true + default: + return Token{}, false + } +} + +func (l *Lexer) makeTwoCharToken(tokenType TokenType) Token { + ch := l.ch + line := l.line + column := l.column + l.readChar() + return NewToken(tokenType, string(ch)+string(l.ch), line, column) +} + +func (l *Lexer) readDotToken() (Token, bool) { + if l.peekChar() == '.' && l.readPosition+1 < len(l.input) && l.input[l.readPosition+1] == '.' { + line := l.line + column := l.column + l.readChar() + l.readChar() + return NewToken(DOTDOTDOT, "...", line, column), true + } + return NewToken(DOT, string(l.ch), l.line, l.column), true +} + +func singleCharTokenType(ch byte) TokenType { + switch ch { case ',': - tok = NewToken(COMMA, string(l.ch), l.line, l.column) + return COMMA case ';': - tok = NewToken(SEMICOLON, string(l.ch), l.line, l.column) + return SEMICOLON case ':': - tok = NewToken(COLON, string(l.ch), l.line, l.column) - case '.': - if l.peekChar() == '.' && l.readPosition+1 < len(l.input) && l.input[l.readPosition+1] == '.' { - line := l.line - column := l.column - l.readChar() // consume second '.' - l.readChar() // consume third '.' - tok = NewToken(DOTDOTDOT, "...", line, column) - } else { - tok = NewToken(DOT, string(l.ch), l.line, l.column) - } + return COLON case '(': - tok = NewToken(LPAREN, string(l.ch), l.line, l.column) + return LPAREN case ')': - tok = NewToken(RPAREN, string(l.ch), l.line, l.column) + return RPAREN case '{': - tok = NewToken(LBRACE, string(l.ch), l.line, l.column) + return LBRACE case '}': - tok = NewToken(RBRACE, string(l.ch), l.line, l.column) + return RBRACE case '[': - tok = NewToken(LBRACKET, string(l.ch), l.line, l.column) + return LBRACKET case ']': - tok = NewToken(RBRACKET, string(l.ch), l.line, l.column) - case '"': - tok.Type = STRING - tok.Literal = l.readString('"') - tok.Line = l.line - tok.Column = l.column - l.readChar() // advance past closing quote - return tok - case '\'': - tok.Type = STRING - tok.Literal = l.readString('\'') - tok.Line = l.line - tok.Column = l.column - l.readChar() // advance past closing quote - return tok - case '`': - tok.Type = TEMPLATE - tok.Literal = l.readTemplate() - tok.Line = l.line - tok.Column = l.column - l.readChar() // advance past closing backtick - return tok - case 0: - tok = NewToken(EOF, "", l.line, l.column) + return RBRACKET default: - if isLetter(l.ch) { - tok.Line = l.line - tok.Column = l.column - tok.Literal = l.readIdentifier() - tok.Type = LookupIdent(tok.Literal) - return tok - } else if isDigit(l.ch) { - tok.Line = l.line - tok.Column = l.column - tok.Literal = l.readNumber() - tok.Type = NUMBER - return tok - } else { - tok = NewToken(ILLEGAL, string(l.ch), l.line, l.column) - } + return PERCENT } - - l.readChar() - return tok } // readIdentifier reads an identifier (variable name, keyword, etc.) diff --git a/src/lexer/token.go b/src/lexer/token.go index d3cb0a9..332e8ef 100644 --- a/src/lexer/token.go +++ b/src/lexer/token.go @@ -41,6 +41,7 @@ const ( // Logical operators BANG = "!" + IN = "IN" // Compound assignment PLUS_ASSIGN = "+=" @@ -53,6 +54,7 @@ const ( SEMICOLON = ";" COLON = ":" DOT = "." + ARROW = "=>" LPAREN = "(" RPAREN = ")" @@ -95,6 +97,10 @@ const ( BIKOLPO = "BIKOLPO" // switch (বিকল্প - alternative) KHETRE = "KHETRE" // case (ক্ষেত্রে - in case of) MANCHITO = "MANCHITO" // default (মানচিত্র - default) + DO = "DO" // do + INSTANCEOF = "INSTANCEOF" // instanceof + DELETE = "DELETE" // delete + OF = "OF" // of ) // keywords maps Banglish keywords to their token types @@ -131,6 +137,11 @@ var keywords = map[string]TokenType{ "bikolpo": BIKOLPO, "khetre": KHETRE, "manchito": MANCHITO, + "do": DO, + "in": IN, + "instanceof": INSTANCEOF, + "delete": DELETE, + "of": OF, } // LookupIdent checks if an identifier is a keyword diff --git a/src/object/environment.go b/src/object/environment.go index 9f7776b..1dd5972 100644 --- a/src/object/environment.go +++ b/src/object/environment.go @@ -1,11 +1,14 @@ package object +import "sync" + // Environment represents a scope for variable bindings type Environment struct { store map[string]Object constants map[string]bool // tracks which variables are constants outer *Environment // parent scope global *Environment // reference to global (root) environment + mu sync.RWMutex } // NewEnvironment creates a new environment @@ -38,60 +41,87 @@ func (e *Environment) GetGlobal() *Environment { // Get retrieves a variable from the environment func (e *Environment) Get(name string) (Object, bool) { + e.mu.RLock() obj, ok := e.store[name] - if !ok && e.outer != nil { - obj, ok = e.outer.Get(name) + outer := e.outer + e.mu.RUnlock() + if ok { + return obj, true } - return obj, ok + if outer != nil { + return outer.Get(name) + } + return nil, false } // Set assigns a variable in the environment func (e *Environment) Set(name string, val Object) Object { + e.mu.Lock() e.store[name] = val + e.mu.Unlock() return val } // SetConstant assigns a constant in the environment func (e *Environment) SetConstant(name string, val Object) Object { + e.mu.Lock() e.store[name] = val e.constants[name] = true + e.mu.Unlock() return val } // SetGlobal assigns a variable in the global environment func (e *Environment) SetGlobal(name string, val Object) Object { global := e.GetGlobal() + global.mu.Lock() global.store[name] = val + global.mu.Unlock() return val } // IsConstant checks if a variable is a constant func (e *Environment) IsConstant(name string) bool { - if constant, ok := e.constants[name]; ok && constant { + e.mu.RLock() + constant, ok := e.constants[name] + outer := e.outer + e.mu.RUnlock() + if ok && constant { return true } - if e.outer != nil { - return e.outer.IsConstant(name) + if outer != nil { + return outer.IsConstant(name) } return false } // Update updates a variable in the environment (searches outer scopes) func (e *Environment) Update(name string, val Object) Object { + e.mu.Lock() _, ok := e.store[name] if ok { e.store[name] = val + e.mu.Unlock() return val } - if e.outer != nil { - return e.outer.Update(name, val) + outer := e.outer + e.mu.Unlock() + if outer != nil { + return outer.Update(name, val) } - // If variable doesn't exist, create it in current scope + e.mu.Lock() e.store[name] = val + e.mu.Unlock() return val } // All returns all variables in the current scope (not including outer scopes) func (e *Environment) All() map[string]Object { - return e.store + e.mu.RLock() + defer e.mu.RUnlock() + out := make(map[string]Object, len(e.store)) + for k, v := range e.store { + out[k] = v + } + return out } diff --git a/src/parser/advanced.go b/src/parser/advanced.go new file mode 100644 index 0000000..7ee80c7 --- /dev/null +++ b/src/parser/advanced.go @@ -0,0 +1,43 @@ +package parser + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/lexer" +) + +// parseDoWhileStatement parses: do { ... } jotokkhon (condition); +func (p *Parser) parseDoWhileStatement() *ast.DoWhileStatement { + stmt := &ast.DoWhileStatement{Token: p.curToken} + + if !p.expectPeek(lexer.LBRACE) { + return nil + } + stmt.Body = p.parseBlockStatement() + + if !p.expectPeek(lexer.JOTOKKHON) { + return nil + } + if !p.expectPeek(lexer.LPAREN) { + return nil + } + + p.nextToken() + stmt.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(lexer.RPAREN) { + return nil + } + if p.peekTokenIs(lexer.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +// parseDeleteExpression parses: delete targetExpression +func (p *Parser) parseDeleteExpression() ast.Expression { + exp := &ast.DeleteExpression{Token: p.curToken} + p.nextToken() + exp.Target = p.parseExpression(PREFIX) + return exp +} diff --git a/src/parser/arrow.go b/src/parser/arrow.go new file mode 100644 index 0000000..4254192 --- /dev/null +++ b/src/parser/arrow.go @@ -0,0 +1,47 @@ +package parser + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/lexer" +) + +// parseArrowFunctionExpression parses: x => expr OR x => { ... } +func (p *Parser) parseArrowFunctionExpression(left ast.Expression) ast.Expression { + params := []*ast.Identifier{} + + switch l := left.(type) { + case *ast.Identifier: + params = append(params, l) + case *ast.ArrowParamList: + params = append(params, l.Params...) + default: + p.errors = append(p.errors, "invalid arrow function parameters") + return nil + } + + fn := &ast.FunctionLiteral{ + Token: p.curToken, // ARROW token + Parameters: params, + } + + p.nextToken() + + if p.curTokenIs(lexer.LBRACE) { + fn.Body = p.parseBlockStatement() + return fn + } + + // Expression body has implicit return + bodyExpr := p.parseExpression(LOWEST) + fn.Body = &ast.BlockStatement{ + Token: lexer.NewToken(lexer.LBRACE, "{", p.curToken.Line, p.curToken.Column), + Statements: []ast.Statement{ + &ast.ReturnStatement{ + Token: lexer.NewToken(lexer.FERAO, "ferao", p.curToken.Line, p.curToken.Column), + ReturnValue: bodyExpr, + }, + }, + } + + return fn +} diff --git a/src/parser/destructuring.go b/src/parser/destructuring.go new file mode 100644 index 0000000..8133c76 --- /dev/null +++ b/src/parser/destructuring.go @@ -0,0 +1,124 @@ +package parser + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/lexer" +) + +func (p *Parser) parseArrayDestructuringDeclaration(token lexer.Token, isConstant, isGlobal bool) *ast.ArrayDestructuringDeclaration { + stmt := &ast.ArrayDestructuringDeclaration{ + Token: token, + IsConstant: isConstant, + IsGlobal: isGlobal, + Names: []*ast.Identifier{}, + } + + // current token is '[' + for { + p.nextToken() + if p.curTokenIs(lexer.RBRACKET) { + break + } + if !p.curTokenIs(lexer.IDENT) { + p.errors = append(p.errors, "array destructuring expects identifiers") + return nil + } + stmt.Names = append(stmt.Names, &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}) + if p.peekTokenIs(lexer.COMMA) { + p.nextToken() + continue + } + if p.peekTokenIs(lexer.RBRACKET) { + p.nextToken() + break + } + p.errors = append(p.errors, "array destructuring expects ',' or ']'") + return nil + } + + if !p.expectPeek(lexer.ASSIGN) { + return nil + } + p.nextToken() + stmt.Source = p.parseExpression(LOWEST) + if p.peekTokenIs(lexer.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseObjectDestructuringDeclaration(token lexer.Token, isConstant, isGlobal bool) *ast.ObjectDestructuringDeclaration { + stmt := &ast.ObjectDestructuringDeclaration{ + Token: token, + IsConstant: isConstant, + IsGlobal: isGlobal, + Keys: []string{}, + Names: []*ast.Identifier{}, + } + + if !p.parseObjectDestructuringBindings(stmt) { + return nil + } + + if !p.expectPeek(lexer.ASSIGN) { + return nil + } + p.nextToken() + stmt.Source = p.parseExpression(LOWEST) + if p.peekTokenIs(lexer.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseObjectDestructuringBindings(stmt *ast.ObjectDestructuringDeclaration) bool { + // current token is '{' + for { + p.nextToken() + if p.curTokenIs(lexer.RBRACE) { + return true + } + + key, name, ok := p.parseObjectDestructuringPair() + if !ok { + return false + } + stmt.Keys = append(stmt.Keys, key) + stmt.Names = append(stmt.Names, name) + + if p.peekTokenIs(lexer.COMMA) { + p.nextToken() + continue + } + if p.peekTokenIs(lexer.RBRACE) { + p.nextToken() + return true + } + p.errors = append(p.errors, "object destructuring expects ',' or '}'") + return false + } +} + +func (p *Parser) parseObjectDestructuringPair() (string, *ast.Identifier, bool) { + if !p.curTokenIs(lexer.IDENT) { + p.errors = append(p.errors, "object destructuring expects identifier keys") + return "", nil, false + } + + key := p.curToken.Literal + name := key + token := p.curToken + + if p.peekTokenIs(lexer.COLON) { + p.nextToken() + if !p.expectPeek(lexer.IDENT) { + return "", nil, false + } + name = p.curToken.Literal + token = p.curToken + } + + return key, &ast.Identifier{Token: token, Value: name}, true +} diff --git a/src/parser/expressions.go b/src/parser/expressions.go index e256aed..5d9d900 100644 --- a/src/parser/expressions.go +++ b/src/parser/expressions.go @@ -95,15 +95,52 @@ func (p *Parser) parseUnaryExpression() ast.Expression { // parseGroupedExpression parses (expression) func (p *Parser) parseGroupedExpression() ast.Expression { + if p.peekTokenIs(lexer.RPAREN) { + return p.parseEmptyArrowParams() + } p.nextToken() + first := p.parseExpression(LOWEST) + if first == nil { + return nil + } + if ident, ok := first.(*ast.Identifier); ok { + return p.parseGroupedIdentifierOrArrow(first, ident) + } + if !p.expectPeek(lexer.RPAREN) { + return nil + } + return first +} - exp := p.parseExpression(LOWEST) +func (p *Parser) parseEmptyArrowParams() ast.Expression { + lp := p.curToken + p.nextToken() + if p.peekTokenIs(lexer.ARROW) { + return &ast.ArrowParamList{Token: lp, Params: []*ast.Identifier{}} + } + return nil +} +func (p *Parser) parseGroupedIdentifierOrArrow(first ast.Expression, ident *ast.Identifier) ast.Expression { + params := []*ast.Identifier{ident} + for p.peekTokenIs(lexer.COMMA) { + p.nextToken() + if !p.expectPeek(lexer.IDENT) { + return nil + } + params = append(params, &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}) + } if !p.expectPeek(lexer.RPAREN) { return nil } - - return exp + if p.peekTokenIs(lexer.ARROW) { + return &ast.ArrowParamList{Token: p.curToken, Params: params} + } + if len(params) == 1 { + return first + } + p.errors = append(p.errors, "grouped identifier list is only valid for arrow functions") + return nil } // parseArrayLiteral parses [elements] @@ -223,54 +260,40 @@ func (p *Parser) parseFunctionParameters() []*ast.Identifier { // parseFunctionParametersWithRest parses function parameters including rest parameter func (p *Parser) parseFunctionParametersWithRest() ([]*ast.Identifier, *ast.Identifier) { identifiers := []*ast.Identifier{} - var restParam *ast.Identifier if p.peekTokenIs(lexer.RPAREN) { p.nextToken() return identifiers, nil } - p.nextToken() - - // Check for rest parameter if p.curTokenIs(lexer.DOTDOTDOT) { - if !p.expectPeek(lexer.IDENT) { - return nil, nil - } - restParam = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - if !p.expectPeek(lexer.RPAREN) { - return nil, nil - } - return identifiers, restParam + return p.parseRestOnlyParameters(identifiers) } - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} identifiers = append(identifiers, ident) - for p.peekTokenIs(lexer.COMMA) { p.nextToken() p.nextToken() - - // Check for rest parameter if p.curTokenIs(lexer.DOTDOTDOT) { - if !p.expectPeek(lexer.IDENT) { - return nil, nil - } - restParam = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - if !p.expectPeek(lexer.RPAREN) { - return nil, nil - } - return identifiers, restParam + return p.parseRestOnlyParameters(identifiers) } - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} identifiers = append(identifiers, ident) } - if !p.expectPeek(lexer.RPAREN) { return nil, nil } + return identifiers, nil +} +func (p *Parser) parseRestOnlyParameters(identifiers []*ast.Identifier) ([]*ast.Identifier, *ast.Identifier) { + if !p.expectPeek(lexer.IDENT) { + return nil, nil + } + restParam := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + if !p.expectPeek(lexer.RPAREN) { + return nil, nil + } return identifiers, restParam } diff --git a/src/parser/loops_advanced.go b/src/parser/loops_advanced.go new file mode 100644 index 0000000..e65a700 --- /dev/null +++ b/src/parser/loops_advanced.go @@ -0,0 +1,52 @@ +package parser + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/lexer" +) + +func (p *Parser) parseForInOrForOf(forToken lexer.Token) ast.Statement { + // Support: ghuriye (item of iterable) { ... } + // Support: ghuriye (key in object) { ... } + if !p.curTokenIs(lexer.IDENT) { + return nil + } + if !(p.peekTokenIs(lexer.OF) || p.peekTokenIs(lexer.IN)) { + return nil + } + varName := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + p.nextToken() + loopKind := p.curToken.Type + + p.nextToken() + iterableOrObject := p.parseExpression(LOWEST) + if iterableOrObject == nil { + return nil + } + + if !p.expectPeek(lexer.RPAREN) { + return nil + } + if !p.expectPeek(lexer.LBRACE) { + return nil + } + + body := p.parseBlockStatement() + + if loopKind == lexer.OF { + return &ast.ForOfStatement{ + Token: forToken, + VarName: varName, + Iterable: iterableOrObject, + Body: body, + } + } + + return &ast.ForInStatement{ + Token: forToken, + VarName: varName, + Object: iterableOrObject, + Body: body, + } +} diff --git a/src/parser/parser.go b/src/parser/parser.go index a168f9f..967de1e 100644 --- a/src/parser/parser.go +++ b/src/parser/parser.go @@ -27,15 +27,17 @@ type Parser struct { // New creates a new parser from a lexer func New(l *lexer.Lexer) *Parser { - p := &Parser{ - l: l, - errors: []string{}, - } - + p := &Parser{l: l, errors: []string{}} p.prefixParseFns = make(map[lexer.TokenType]prefixParseFn) p.infixParseFns = make(map[lexer.TokenType]infixParseFn) + p.registerPrefixParsers() + p.registerInfixParsers() + p.nextToken() + p.nextToken() + return p +} - // Register prefix parse functions +func (p *Parser) registerPrefixParsers() { p.registerPrefix(lexer.IDENT, p.parseIdentifier) p.registerPrefix(lexer.NUMBER, p.parseNumberLiteral) p.registerPrefix(lexer.STRING, p.parseStringLiteral) @@ -54,8 +56,10 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(lexer.OPEKHA, p.parseAwaitExpression) p.registerPrefix(lexer.NOTUN, p.parseNewExpression) p.registerPrefix(lexer.DOTDOTDOT, p.parseSpreadElement) + p.registerPrefix(lexer.DELETE, p.parseDeleteExpression) +} - // Register infix parse functions +func (p *Parser) registerInfixParsers() { p.registerInfix(lexer.PLUS, p.parseBinaryExpression) p.registerInfix(lexer.MINUS, p.parseBinaryExpression) p.registerInfix(lexer.ASTERISK, p.parseBinaryExpression) @@ -69,6 +73,8 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(lexer.GTE, p.parseBinaryExpression) p.registerInfix(lexer.EBONG, p.parseBinaryExpression) p.registerInfix(lexer.BA, p.parseBinaryExpression) + p.registerInfix(lexer.IN, p.parseBinaryExpression) + p.registerInfix(lexer.INSTANCEOF, p.parseBinaryExpression) p.registerInfix(lexer.ASSIGN, p.parseAssignmentExpression) p.registerInfix(lexer.PLUS_ASSIGN, p.parseAssignmentExpression) p.registerInfix(lexer.MINUS_ASSIGN, p.parseAssignmentExpression) @@ -77,12 +83,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(lexer.LPAREN, p.parseCallExpression) p.registerInfix(lexer.LBRACKET, p.parseMemberExpression) p.registerInfix(lexer.DOT, p.parseMemberExpression) - - // Read two tokens to initialize curToken and peekToken - p.nextToken() - p.nextToken() - - return p + p.registerInfix(lexer.ARROW, p.parseArrowFunctionExpression) } // Errors returns the list of parsing errors diff --git a/src/parser/precedence.go b/src/parser/precedence.go index c0ab326..d9c6493 100644 --- a/src/parser/precedence.go +++ b/src/parser/precedence.go @@ -7,8 +7,10 @@ const ( _ int = iota LOWEST ASSIGN // =, +=, -=, *=, /= + ARROWP // => OR // ba (||) AND // ebong (&&) + INOP // in, instanceof EQUALS // ==, != LESSGREATER // <, >, <=, >= SUM // +, - @@ -25,8 +27,11 @@ var precedences = map[lexer.TokenType]int{ lexer.MINUS_ASSIGN: ASSIGN, lexer.ASTERISK_ASSIGN: ASSIGN, lexer.SLASH_ASSIGN: ASSIGN, + lexer.ARROW: ARROWP, lexer.BA: OR, lexer.EBONG: AND, + lexer.IN: INOP, + lexer.INSTANCEOF: INOP, lexer.EQ: EQUALS, lexer.NOT_EQ: EQUALS, lexer.LT: LESSGREATER, diff --git a/src/parser/statements.go b/src/parser/statements.go index dc53bd6..cf7532b 100644 --- a/src/parser/statements.go +++ b/src/parser/statements.go @@ -20,6 +20,8 @@ func (p *Parser) parseStatement() ast.Statement { return p.parseWhileStatement() case lexer.GHURIYE: return p.parseForStatement() + case lexer.DO: + return p.parseDoWhileStatement() case lexer.FERAO: return p.parseReturnStatement() case lexer.SRENI: @@ -44,7 +46,19 @@ func (p *Parser) parseStatement() ast.Statement { } // parseVariableDeclaration parses "dhoro x = value", "sthir x = value", or "bishwo x = value" -func (p *Parser) parseVariableDeclaration(isConstant bool, isGlobal bool) *ast.VariableDeclaration { +func (p *Parser) parseVariableDeclaration(isConstant bool, isGlobal bool) ast.Statement { + declToken := p.curToken + + if p.peekTokenIs(lexer.LBRACKET) { + p.nextToken() + return p.parseArrayDestructuringDeclaration(declToken, isConstant, isGlobal) + } + + if p.peekTokenIs(lexer.LBRACE) { + p.nextToken() + return p.parseObjectDestructuringDeclaration(declToken, isConstant, isGlobal) + } + stmt := &ast.VariableDeclaration{ Token: p.curToken, IsConstant: isConstant, @@ -140,9 +154,12 @@ func (p *Parser) parseWhileStatement() *ast.WhileStatement { return stmt } -// parseForStatement parses "ghuriye (init; condition; update) { }" -func (p *Parser) parseForStatement() *ast.ForStatement { - stmt := &ast.ForStatement{Token: p.curToken} +// parseForStatement parses: +// - classic for: ghuriye (init; condition; update) { } +// - for...of: ghuriye (dhoro item of iterable) { } +// - for...in: ghuriye (dhoro key in object) { } +func (p *Parser) parseForStatement() ast.Statement { + forToken := p.curToken if !p.expectPeek(lexer.LPAREN) { return nil @@ -150,6 +167,16 @@ func (p *Parser) parseForStatement() *ast.ForStatement { p.nextToken() + if iterStmt := p.parseForInOrForOf(forToken); iterStmt != nil { + return iterStmt + } + + return p.parseClassicForStatement(forToken) +} + +func (p *Parser) parseClassicForStatement(forToken lexer.Token) ast.Statement { + stmt := &ast.ForStatement{Token: forToken} + // Parse init statement if !p.curTokenIs(lexer.SEMICOLON) { stmt.Init = p.parseStatement() @@ -413,72 +440,3 @@ func (p *Parser) parseThrowStatement() *ast.ThrowStatement { return stmt } - -// parseSwitchStatement parses "bikolpo (expression) { khetre value: ... manchito: ... }" -func (p *Parser) parseSwitchStatement() *ast.SwitchStatement { - stmt := &ast.SwitchStatement{Token: p.curToken} - - // Expect opening parenthesis - if !p.expectPeek(lexer.LPAREN) { - return nil - } - - // Parse the expression to match against - p.nextToken() - stmt.Expr = p.parseExpression(LOWEST) - - // Expect closing parenthesis - if !p.expectPeek(lexer.RPAREN) { - return nil - } - - // Expect opening brace - if !p.expectPeek(lexer.LBRACE) { - return nil - } - - // Parse cases and default - stmt.Cases = []*ast.CaseClause{} - - p.nextToken() - for !p.curTokenIs(lexer.RBRACE) && !p.curTokenIs(lexer.EOF) { - if p.curTokenIs(lexer.KHETRE) { - caseClause := p.parseCaseClause() - if caseClause != nil { - stmt.Cases = append(stmt.Cases, caseClause) - } - p.nextToken() - } else if p.curTokenIs(lexer.MANCHITO) { - // Parse default case (no colon in simplified syntax) - // Expect opening brace - if !p.expectPeek(lexer.LBRACE) { - return nil - } - - stmt.Default = p.parseBlockStatement() - p.nextToken() - } else { - return nil - } - } - - return stmt -} - -// parseCaseClause parses a single "khetre value { ... }" clause -func (p *Parser) parseCaseClause() *ast.CaseClause { - clause := &ast.CaseClause{Token: p.curToken} - - // Move to the value expression - p.nextToken() - clause.Value = p.parseExpression(LOWEST) - - // Expect opening brace (no colon in simplified syntax) - if !p.expectPeek(lexer.LBRACE) { - return nil - } - - clause.Body = p.parseBlockStatement() - - return clause -} diff --git a/src/parser/switch.go b/src/parser/switch.go new file mode 100644 index 0000000..0795acf --- /dev/null +++ b/src/parser/switch.go @@ -0,0 +1,67 @@ +package parser + +import ( + "BanglaCode/src/ast" + "BanglaCode/src/lexer" +) + +// parseSwitchStatement parses "bikolpo (expression) { khetre value { ... } manchito { ... } }" +func (p *Parser) parseSwitchStatement() *ast.SwitchStatement { + stmt := &ast.SwitchStatement{Token: p.curToken} + if !p.parseSwitchHeader(stmt) { + return nil + } + stmt.Cases = []*ast.CaseClause{} + p.nextToken() + for !p.curTokenIs(lexer.RBRACE) && !p.curTokenIs(lexer.EOF) { + if p.curTokenIs(lexer.KHETRE) { + caseClause := p.parseCaseClause() + if caseClause != nil { + stmt.Cases = append(stmt.Cases, caseClause) + } + p.nextToken() + continue + } + if p.curTokenIs(lexer.MANCHITO) { + if !p.parseDefaultClause(stmt) { + return nil + } + continue + } + return nil + } + return stmt +} + +func (p *Parser) parseSwitchHeader(stmt *ast.SwitchStatement) bool { + if !p.expectPeek(lexer.LPAREN) { + return false + } + p.nextToken() + stmt.Expr = p.parseExpression(LOWEST) + if !p.expectPeek(lexer.RPAREN) { + return false + } + return p.expectPeek(lexer.LBRACE) +} + +func (p *Parser) parseDefaultClause(stmt *ast.SwitchStatement) bool { + if !p.expectPeek(lexer.LBRACE) { + return false + } + stmt.Default = p.parseBlockStatement() + p.nextToken() + return true +} + +// parseCaseClause parses a single "khetre value { ... }" clause +func (p *Parser) parseCaseClause() *ast.CaseClause { + clause := &ast.CaseClause{Token: p.curToken} + p.nextToken() + clause.Value = p.parseExpression(LOWEST) + if !p.expectPeek(lexer.LBRACE) { + return nil + } + clause.Body = p.parseBlockStatement() + return clause +} diff --git a/src/repl/repl.go b/src/repl/repl.go index 1984f77..19ec36c 100644 --- a/src/repl/repl.go +++ b/src/repl/repl.go @@ -11,7 +11,7 @@ import ( "strings" ) -const Version = "7.1.0" +const Version = "8.0.0" const PROMPT = "\033[1;33m>> \033[0m" diff --git a/test/missing_features_batch1_test.go b/test/missing_features_batch1_test.go new file mode 100644 index 0000000..86c14cd --- /dev/null +++ b/test/missing_features_batch1_test.go @@ -0,0 +1,119 @@ +package test + +import ( + "BanglaCode/src/object" + "math" + "testing" +) + +func TestMissingBatchArrayFunctions(t *testing.T) { + tests := []struct { + input string + expected float64 + }{ + {`khojo_prothom([2,4,7,8], kaj(x) { ferao x > 5; })`, 7}, + {`khojo_index([2,4,7,8], kaj(x) { ferao x > 5; })`, 2}, + {`khojo_shesh([2,4,7,8], kaj(x) { ferao x % 2 == 0; })`, 8}, + {`khojo_shesh_index([2,4,7,8], kaj(x) { ferao x % 2 == 0; })`, 3}, + {`array_at([10,20,30], -1)`, 30}, + {`shesh_index_of([1,2,3,2,1], 2)`, 3}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testNumberObject(t, evaluated, tt.expected) + } +} + +func TestMissingBatchArrayBooleanFunctions(t *testing.T) { + testBooleanObject(t, testEval(`prottek([2,4,6], kaj(x) { ferao x % 2 == 0; })`), true) + testBooleanObject(t, testEval(`prottek([2,3,6], kaj(x) { ferao x % 2 == 0; })`), false) + testBooleanObject(t, testEval(`kono([1,3,5], kaj(x) { ferao x % 2 == 0; })`), false) + testBooleanObject(t, testEval(`kono([1,4,5], kaj(x) { ferao x % 2 == 0; })`), true) +} + +func TestMissingBatchArrayFlatMap(t *testing.T) { + input := ` + dhoro out = somtol_manchitro([1, 2, 3], kaj(x) { + ferao [x, x * 10]; + }); + out; + ` + evaluated := testEval(input) + testArrayObject(t, evaluated, []float64{1, 10, 2, 20, 3, 30}, 0) +} + +func TestMissingBatchArrayConcatFlatReduceRight(t *testing.T) { + testArrayObject(t, testEval(`joro_array([1,2], [3], 4)`), []float64{1, 2, 3, 4}, 0) + + flatInput := ` + dhoro a = [1, [2, [3]]]; + somtol(a, 2); + ` + testArrayObject(t, testEval(flatInput), []float64{1, 2, 3}, 0) + + testNumberObject(t, testEval(`sonkuchito_dan([1,2,3], kaj(acc, x) { ferao acc - x; }, 0)`), -6) +} + +func TestMissingBatchStringFunctions(t *testing.T) { + testBooleanObject(t, testEval(`ache_text("banglacode", "code")`), true) + testBooleanObject(t, testEval(`shuru_diye("banglacode", "bang")`), true) + testBooleanObject(t, testEval(`shesh_diye("banglacode", "code")`), true) + + s1 := testEval(`baro("ha", 3)`) + testStringObject(t, s1, "hahaha") + + s2 := testEval(`agey_bhoro("7", 3, "0")`) + testStringObject(t, s2, "007") + + s3 := testEval(`pichoney_bhoro("7", 3, "0")`) + testStringObject(t, s3, "700") + + s4 := testEval(`okkhor("bangla", 2)`) + testStringObject(t, s4, "n") + + s5 := testEval(`text_at("bangla", -1)`) + testStringObject(t, s5, "a") + + s6 := testEval(`chhanto_shuru(" hi")`) + testStringObject(t, s6, "hi") + + s7 := testEval(`chhanto_shesh("hi ")`) + testStringObject(t, s7, "hi") + + testNumberObject(t, testEval(`okkhor_code("A", 0)`), 65) + testNumberObject(t, testEval(`shesh_khojo("banana", "na")`), 4) + testNumberObject(t, testEval(`codepoint_at("A", 0)`), 65) + testNumberObject(t, testEval(`tulona_text("a", "b")`), -1) + testStringObject(t, testEval(`shadharon_text("text")`), "text") +} + +func TestMissingBatchGlobalNumericFunctions(t *testing.T) { + testNumberObject(t, testEval(`purno_sonkhya("42")`), 42) + testNumberObject(t, testEval(`purno_sonkhya("0x10")`), 16) + testNumberObject(t, testEval(`purno_sonkhya("111", 2)`), 7) + testNumberObject(t, testEval(`doshomik_sonkhya("3.14")`), 3.14) + + testBooleanObject(t, testEval(`sonkhya_na("abc")`), true) + testBooleanObject(t, testEval(`sonkhya_na("12.5")`), false) + testBooleanObject(t, testEval(`sonkhya_shimito("12.5")`), true) + testBooleanObject(t, testEval(`sonkhya_shimito("abc")`), false) +} + +func TestMissingBatchNaNBehavior(t *testing.T) { + out := testEval(`purno_sonkhya("abc")`) + num, ok := out.(*object.Number) + if !ok { + t.Fatalf("expected Number for NaN case, got=%T", out) + } + if !math.IsNaN(num.Value) { + t.Fatalf("expected NaN, got=%v", num.Value) + } +} + +func TestMissingBatchURIFunctions(t *testing.T) { + testStringObject(t, testEval(`uri_ongsho_encode("hello world")`), "hello%20world") + testStringObject(t, testEval(`uri_ongsho_decode("hello%20world")`), "hello world") + testStringObject(t, testEval(`uri_encode("https://a.com/q=a b")`), "https://a.com/q=a%20b") + testStringObject(t, testEval(`uri_decode("https://a.com/q=a%20b")`), "https://a.com/q=a b") +} diff --git a/test/missing_features_batch2_core_syntax_test.go b/test/missing_features_batch2_core_syntax_test.go new file mode 100644 index 0000000..59419b0 --- /dev/null +++ b/test/missing_features_batch2_core_syntax_test.go @@ -0,0 +1,62 @@ +package test + +import "testing" + +func TestDoWhileLoop(t *testing.T) { + input := ` + dhoro x = 0; + do { + x = x + 1; + } jotokkhon (x < 3); + x; + ` + testNumberObject(t, testEval(input), 3) +} + +func TestDoWhileRunsAtLeastOnce(t *testing.T) { + input := ` + dhoro x = 0; + do { + x = x + 1; + } jotokkhon (mittha); + x; + ` + testNumberObject(t, testEval(input), 1) +} + +func TestInOperator(t *testing.T) { + testBooleanObject(t, testEval(`"a" in {a: 1, b: 2}`), true) + testBooleanObject(t, testEval(`"z" in {a: 1, b: 2}`), false) + testBooleanObject(t, testEval(`1 in [10, 20, 30]`), true) + testBooleanObject(t, testEval(`3 in [10, 20, 30]`), false) + testBooleanObject(t, testEval(`2 in "bangla"`), true) +} + +func TestInstanceofOperator(t *testing.T) { + input := ` + sreni Manush { + shuru(naam) { ei.naam = naam; } + } + dhoro p = notun Manush("Ankan"); + p instanceof Manush; + ` + testBooleanObject(t, testEval(input), true) +} + +func TestDeleteOperator(t *testing.T) { + input := ` + dhoro obj = {a: 1, b: 2}; + delete obj.a; + "a" in obj; + ` + testBooleanObject(t, testEval(input), false) +} + +func TestDeleteArrayIndex(t *testing.T) { + input := ` + dhoro arr = [1, 2, 3]; + delete arr[1]; + arr[1] == khali; + ` + testBooleanObject(t, testEval(input), true) +} diff --git a/test/missing_features_batch3_date_regex_test.go b/test/missing_features_batch3_date_regex_test.go new file mode 100644 index 0000000..2d61e87 --- /dev/null +++ b/test/missing_features_batch3_date_regex_test.go @@ -0,0 +1,32 @@ +package test + +import "BanglaCode/src/object" +import "testing" + +func TestDateBuiltins(t *testing.T) { + now := testEval(`tarikh_ekhon()`) + if _, ok := now.(*object.Number); !ok { + t.Fatalf("tarikh_ekhon should return NUMBER, got=%T", now) + } + + testBooleanObject(t, testEval(`sonkhya_na(tarikh_parse("invalid date"))`), true) + testBooleanObject(t, testEval(`sonkhya_na(tarikh_parse("2026-02-21"))`), false) + + out := testEval(`dhoro t = tarikh_parse("2026-02-21"); tarikh_format(t, "2006-01-02")`) + testStringObject(t, out, "2026-02-21") +} + +func TestRegexBuiltins(t *testing.T) { + testBooleanObject(t, testEval(`regex_test("[a-z]+", "bangla")`), true) + testNumberObject(t, testEval(`regex_search("la", "bangla")`), 4) + testStringObject(t, testEval(`regex_replace("a", "banana", "x")`), "bxnxnx") + + match := testEval(`regex_match("b(ang)", "bangla")`) + arr, ok := match.(*object.Array) + if !ok { + t.Fatalf("regex_match should return ARRAY, got=%T", match) + } + if len(arr.Elements) != 2 { + t.Fatalf("regex_match expected 2 captures, got=%d", len(arr.Elements)) + } +} diff --git a/test/missing_features_batch3_maturity_syntax_test.go b/test/missing_features_batch3_maturity_syntax_test.go new file mode 100644 index 0000000..4fca163 --- /dev/null +++ b/test/missing_features_batch3_maturity_syntax_test.go @@ -0,0 +1,42 @@ +package test + +import "testing" + +func TestArrowFunctionSingleParam(t *testing.T) { + input := ` + dhoro double = x => x * 2; + double(5); + ` + testNumberObject(t, testEval(input), 10) +} + +func TestArrowFunctionBlockBody(t *testing.T) { + input := ` + dhoro inc = x => { ferao x + 1; }; + inc(9); + ` + testNumberObject(t, testEval(input), 10) +} + +func TestForOfLoopArray(t *testing.T) { + input := ` + dhoro sum = 0; + ghuriye (x of [1, 2, 3, 4]) { + sum = sum + x; + } + sum; + ` + testNumberObject(t, testEval(input), 10) +} + +func TestForInLoopMap(t *testing.T) { + input := ` + dhoro obj = {a: 1, b: 2}; + dhoro count = 0; + ghuriye (k in obj) { + count = count + 1; + } + count; + ` + testNumberObject(t, testEval(input), 2) +} diff --git a/test/missing_features_batch3_object_test.go b/test/missing_features_batch3_object_test.go new file mode 100644 index 0000000..baf1e66 --- /dev/null +++ b/test/missing_features_batch3_object_test.go @@ -0,0 +1,20 @@ +package test + +import "testing" + +func TestObjectMaturityBuiltins(t *testing.T) { + testBooleanObject(t, testEval(`nijer_ache({a: 1}, "a")`), true) + testBooleanObject(t, testEval(`nijer_ache({a: 1}, "b")`), false) + + out1 := testEval(`jora_theke([["a", 1], ["b", 2]])["b"]`) + testNumberObject(t, out1, 2) + + testBooleanObject(t, testEval(`ekoi_ki(1, 1)`), true) + testBooleanObject(t, testEval(`ekoi_ki(1, 2)`), false) + + out2 := testEval(`notun_map({a: 1}, {b: 2})["b"]`) + testNumberObject(t, out2, 2) + + out3 := testEval(`joma({x: 1})["x"]`) + testNumberObject(t, out3, 1) +} diff --git a/test/missing_features_batch4_maturity_test.go b/test/missing_features_batch4_maturity_test.go new file mode 100644 index 0000000..ec4f5a9 --- /dev/null +++ b/test/missing_features_batch4_maturity_test.go @@ -0,0 +1,56 @@ +package test + +import "BanglaCode/src/object" +import "testing" + +func TestDestructuringDeclarations(t *testing.T) { + inputArray := ` + dhoro [a, b, c] = [10, 20]; + a + b; + ` + testNumberObject(t, testEval(inputArray), 30) + + testBooleanObject(t, testEval(`dhoro [x, y] = [1]; y == khali;`), true) + testNumberObject(t, testEval(`dhoro {a, b} = {a: 7, b: 9}; a + b;`), 16) + testBooleanObject(t, testEval(`dhoro {a, b} = {a: 1}; b == khali;`), true) +} + +func TestMultiParamArrowFunctions(t *testing.T) { + testNumberObject(t, testEval(`dhoro add = (a, b) => a + b; add(3, 4);`), 7) + testNumberObject(t, testEval(`dhoro z = () => 11; z();`), 11) +} + +func TestTimerBuiltins(t *testing.T) { + inputTimeout := ` + dhoro x = 0; + setTimeout(kaj() { x = 5; }, 10); + ghum(40); + x; + ` + testNumberObject(t, testEval(inputTimeout), 5) + + inputInterval := ` + dhoro c = 0; + dhoro id = setInterval(kaj() { c = c + 1; }, 5); + ghum(35); + clearInterval(id); + dhoro prev = c; + ghum(20); + c == prev; + ` + testBooleanObject(t, testEval(inputInterval), true) +} + +func TestRegexFlagsAndWrappers(t *testing.T) { + testBooleanObject(t, testEval(`regex_test("bangla", "BANGLA", "i")`), true) + testNumberObject(t, testEval(`search("BANGLA CODE", "code", "i")`), 7) + + out := testEval(`matchAll("a1 b2", "[a-z][0-9]")`) + arr, ok := out.(*object.Array) + if !ok { + t.Fatalf("matchAll expected ARRAY, got=%T", out) + } + if len(arr.Elements) != 2 { + t.Fatalf("matchAll expected 2 matches, got=%d", len(arr.Elements)) + } +}