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
+
${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/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..49d71bf
--- /dev/null
+++ b/src/evaluator/builtins/builtins_timers.go
@@ -0,0 +1,151 @@
+package builtins
+
+import (
+ "BanglaCode/src/object"
+ "sync"
+ "time"
+)
+
+var (
+ timerMu sync.Mutex
+ nextTimerID float64 = 1
+ timeouts = map[int]chan struct{}{}
+ intervals = map[int]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, stopCh := newTimerID(false)
+ go func() {
+ ticker := time.NewTicker(time.Duration(ms) * time.Millisecond)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ EvalFunc(cb, cbArgs)
+ case <-stopCh:
+ timerMu.Lock()
+ delete(intervals, id)
+ timerMu.Unlock()
+ return
+ }
+ }
+ }()
+ return &object.Number{Value: float64(id)}
+ }}
+}
+
+func registerClearTimeout() {
+ Builtins["clearTimeout"] = &object.Builtin{Fn: func(args ...object.Object) object.Object {
+ return clearTimer(args, true)
+ }}
+}
+
+func registerClearInterval() {
+ Builtins["clearInterval"] = &object.Builtin{Fn: func(args ...object.Object) object.Object {
+ return clearTimer(args, false)
+ }}
+}
+
+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
+ } else {
+ intervals[id] = stopCh
+ }
+ return id, stopCh
+}
+
+func clearTimer(args []object.Object, timeout bool) 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()
+ defer timerMu.Unlock()
+
+ if timeout {
+ if ch, ok := timeouts[id]; ok {
+ select {
+ case ch <- struct{}{}:
+ default:
+ }
+ delete(timeouts, id)
+ }
+ } else {
+ if ch, ok := intervals[id]; ok {
+ select {
+ case ch <- struct{}{}:
+ default:
+ }
+ delete(intervals, id)
+ }
+ }
+ return object.NULL
+}
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/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/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))
+ }
+}
From 1794a541e68dd1633a10391ff3bbf92a84727b54 Mon Sep 17 00:00:00 2001
From: Ankan Saha
Date: Sun, 22 Feb 2026 03:53:06 +0530
Subject: [PATCH 2/3] chore: Update project version to 8.0.0 across
documentation, extension, and main version file.
---
Documentation/package.json | 2 +-
Extension/package.json | 2 +-
VERSION | 2 +-
main.go | 4 ++--
src/repl/repl.go | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/Documentation/package.json b/Documentation/package.json
index abd62b2..04560d9 100644
--- a/Documentation/package.json
+++ b/Documentation/package.json
@@ -1,6 +1,6 @@
{
"name": "documentation",
- "version": "7.1.0",
+ "version": "8.0.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
diff --git a/Extension/package.json b/Extension/package.json
index fc408c9..2acf0c9 100644
--- a/Extension/package.json
+++ b/Extension/package.json
@@ -2,7 +2,7 @@
"name": "banglacode",
"displayName": "BanglaCode",
"description": "Language support for BanglaCode (.bang, .bangla, .bong) - Bengali Programming Language created by Ankan from West Bengal, India",
- "version": "7.1.0",
+ "version": "8.0.0",
"publisher": "AnkanSaha",
"author": {
"name": "AnkanSaha"
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/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"
From d587f228178eba476c9b89baa427e2a5bb591591 Mon Sep 17 00:00:00 2001
From: Ankan Saha
Date: Sun, 22 Feb 2026 03:57:48 +0530
Subject: [PATCH 3/3] refactor: Implement thread-safe environment operations
and enhance interval timer control for reliable termination.
---
src/evaluator/builtins/builtins_timers.go | 100 ++++++++++++++++------
src/object/environment.go | 50 ++++++++---
2 files changed, 112 insertions(+), 38 deletions(-)
diff --git a/src/evaluator/builtins/builtins_timers.go b/src/evaluator/builtins/builtins_timers.go
index 49d71bf..0655037 100644
--- a/src/evaluator/builtins/builtins_timers.go
+++ b/src/evaluator/builtins/builtins_timers.go
@@ -10,9 +10,14 @@ var (
timerMu sync.Mutex
nextTimerID float64 = 1
timeouts = map[int]chan struct{}{}
- intervals = map[int]chan struct{}{}
+ intervals = map[int]*intervalControl{}
)
+type intervalControl struct {
+ stop chan struct{}
+ done chan struct{}
+}
+
func init() {
registerSetTimeout()
registerSetInterval()
@@ -52,18 +57,23 @@ func registerSetInterval() {
ms = 1
}
- id, stopCh := newTimerID(false)
+ 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 <-stopCh:
- timerMu.Lock()
- delete(intervals, id)
- timerMu.Unlock()
+ case <-ctrl.stop:
+ removeInterval(id)
return
}
}
@@ -74,13 +84,13 @@ func registerSetInterval() {
func registerClearTimeout() {
Builtins["clearTimeout"] = &object.Builtin{Fn: func(args ...object.Object) object.Object {
- return clearTimer(args, true)
+ return clearTimeoutTimer(args)
}}
}
func registerClearInterval() {
Builtins["clearInterval"] = &object.Builtin{Fn: func(args ...object.Object) object.Object {
- return clearTimer(args, false)
+ return clearIntervalTimer(args)
}}
}
@@ -112,13 +122,25 @@ func newTimerID(timeout bool) (int, chan struct{}) {
stopCh := make(chan struct{}, 1)
if timeout {
timeouts[id] = stopCh
- } else {
- intervals[id] = stopCh
}
return id, stopCh
}
-func clearTimer(args []object.Object, timeout bool) object.Object {
+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))
}
@@ -128,24 +150,46 @@ func clearTimer(args []object.Object, timeout bool) object.Object {
id := int(args[0].(*object.Number).Value)
timerMu.Lock()
- defer timerMu.Unlock()
-
- if timeout {
- if ch, ok := timeouts[id]; ok {
- select {
- case ch <- struct{}{}:
- default:
- }
- delete(timeouts, id)
- }
- } else {
- if ch, ok := intervals[id]; ok {
- select {
- case ch <- struct{}{}:
- default:
- }
- delete(intervals, id)
+ 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/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
}