Audit of plan vs implementation. Last updated: 2026-04-08.
As of 2026-02-26, the any keyword has been replaced with let/const to align with JavaScript:
// Old (deprecated)
any x = 5
// New
let x = 5 // mutable binding
const y = 10 // immutable binding (error on reassignment)Benefits:
- Familiar to JS/TS developers
- Enables compiler optimizations for const bindings
- Better native code generation (
letvslet mutin Rust)
| Feature | Status |
|---|---|
| Numbers, strings, booleans, null | ✓ |
any x = expr (block-scoped) |
✓ |
fn name(a, b) { } and fn name(a) = expr |
✓ |
| if/else, while, for C-style | ✓ |
for (any x of arr) |
✓ |
| Nested blocks and loops | ✓ |
Arrays [], indexing a[i] |
✓ |
Plain objects {}, dot/index access |
✓ |
=== / !==, && || !, ??, ?. |
✓ |
| Optional braces (indentation) | ✓ |
| Item | Decision | Status |
|---|---|---|
| block-scope | Follow | ✓ |
| comments | Follow | ✓ |
| computed-property-names | Follow | |
| addition, array, assignment, call | Follow | ✓ |
coalesce (??) |
Follow | ✓ |
conditional (? :) |
Follow | ✓ |
| division, multiplication, modulus, exponentiation | Follow | ✓ |
| bitwise | Follow | ✓ |
| logical-and/or/not | Follow | ✓ |
| member, optional-chaining | Follow | ✓ |
| object | Follow | ✓ |
| strict-equals | Follow | ✓ |
| increment/decrement (postfix & prefix) | Follow | ✓ |
| typeof | Follow | ✓ |
| void | Follow | ✓ |
| block, break, continue, for, if, return, while | Follow | ✓ |
| switch, do-while | Follow | ✓ |
| throw, try/catch | Follow | ✓ |
| Array (simplify), Math, String, Object | Follow | ✓ |
| parseInt, parseFloat, isFinite, isNaN | Follow | ✓ |
| Infinity, NaN | Follow | ✓ |
| Math.abs, sqrt, min, max, floor, ceil, round | Follow | ✓ |
| array.length, string.length | Follow | ✓ |
compound assignment (+=, -=, *=, /=, %=) |
Not in plan | ✓ Added |
| type annotations (optional, parsed not enforced) | Not in plan | ✓ Added |
| Test | .tish | .js |
|---|---|---|
| Nested loops | nested_loops.tish | nested_loops.js |
| Variable scopes | scopes.tish | scopes.js |
| Optional braces | optional_braces.tish, optional_braces_braced.tish | ✓ |
| Tab vs space | tab_indent.tish, space_indent.tish | ✓ |
| fn and let/const | fn_any.tish | fn_any.js |
| Strict equality | strict_equality.tish | strict_equality.js |
| Objects (comprehensive) | objects.tish, objects_perf.tish | objects.js, objects_perf.js |
| Compound assignment | compound_assign.tish | compound_assign.js |
| Type annotations | types.tish | types.js |
| Mutation | mutation.tish | mutation.js |
| Array methods | array_methods.tish | array_methods.js |
| String methods | string_methods.tish | string_methods.js |
| Higher-order methods | higher_order_methods.tish | higher_order_methods.js |
| Object methods | object_methods.tish | object_methods.js |
| Arrow functions | arrow_functions.tish | arrow_functions.js |
| Template literals | template_literals.tish | template_literals.js |
Total: 40 .tish / 42 .js tests
| Feature | Plan ref | Effort | Notes |
|---|---|---|---|
| Rest parameters | 3.1.2 rest-parameters Follow | ✓ Implemented | |
| Static import/export | 3.1.2, §4 "Simple modules" | Large | §7 says "no import in MVP"; deferred |
| decodeURI/encodeURI | 3.1.5 Omit or Follow | ✓ Implemented | |
| JSON | 3.1.5 Optional | ✓ Implemented | JSON.parse, JSON.stringify |
| Feature | Current | Gap | Effort |
|---|---|---|---|
| Property assignment | ✓ Implemented | obj.x = val and arr[i] = val now work |
— |
| Mutable arrays/objects | ✓ Implemented | Rc<RefCell<...>> enables mutation |
— |
| Computed property names | Dynamic access only | { [expr]: val } in literals not supported |
Small |
| Feature | Plan ref | Notes |
|---|---|---|
| in operator | 3.1.3 in/instanceof | ✓ Implemented — "x" in obj |
| instanceof | 3.1.3 | Omit (no classes) |
| delete | 3.1.3 | Omit or Simplify |
| destructuring | 3.1.2 | Simplify or defer |
| Feature | Plan ref | Current | Gap |
|---|---|---|---|
| Boolean | 3.1.5 | bool literals | No Boolean(x) constructor |
| String | 3.1.5 | strings, .length, 17 instance methods | ✓ indexOf, lastIndexOf, includes, slice, substring, split, trim, toUpperCase, toLowerCase, startsWith, endsWith, replace, replaceAll, charAt, charCodeAt, repeat, padStart, padEnd |
| Array | 3.1.5 | arrays, .length, 18 methods | ✓ push, pop, shift, unshift, indexOf, includes, join, reverse, slice, concat + map, filter, reduce, find, findIndex, forEach, some, every, flat |
| Object | 3.1.5 | objects, dot/bracket access | ✓ Object.keys(), Object.values(), Object.entries() |
| Error/NativeErrors | 3.1.5 | throw/catch work | No Error constructor, no .message |
| Feature | Status | Notes |
|---|---|---|
| Prefix ++/-- | ✓ Implemented | |
| Compound assignment | ✓ Implemented | +=, -=, *=, /=, %= |
| Logical assignment | Not implemented | &&=, ||=, ??= |
| Spread operator | Not implemented | ...arr |
Gaps discovered when porting JS patterns (e.g. mdx-docs). See tish-docs JS Compatibility Gaps and TODO-GAPS.md.
| Gap | Impact | Resolution task |
|---|---|---|
String.indexOf no fromIndex |
Can't search from offset | ✓ Optional 2nd param (char index) |
indexOf returns byte offset; slice/length use char indices |
Corrupt output with UTF-8 multi-byte (em dash, etc.) | ✓ Char-based indices |
String.lastIndexOf missing |
JS parity for reverse search | ✓ lastIndexOf(search, position?); native compile omits position via Infinity sentinel |
| String vs JS (astral symbols) | Emoji etc.: one Tish char index vs two UTF-16 units in JS | Documented in LANGUAGE.md; use BMP in cross-engine golden tests |
RegExp.exec() no index property |
Can't get match start for replacement | ✓ Added |
String.replace no function replacer |
Manual iteration for custom replace | ✓ Added (interpreter) |
| Behavior | JavaScript | Tish | Rationale |
|---|---|---|---|
| No undefined | undefined type exists |
null only |
Simplification |
| Optional chaining on null | Returns undefined |
Returns null |
Follows from above |
| Loose equality | == with coercion |
Not supported (error) | By design |
| Type coercion | Implicit in many ops | No implicit coercion | By design |
- Full-stack parse: all 31 .tish ✓
- Interpreter run: all 31 .tish ✓
- Interpreter vs native: Most files pass (some differences in compiled output)
- Performance Tish vs JS: 31 pairs in run_performance_manual.sh ✓
- ✓ Rest parameters — Implemented
- ✓ JSON.parse / JSON.stringify — Implemented
- ✓ decodeURI / encodeURI — Implemented
- ✓ Prefix ++/-- — Implemented
- ✓ in operator — Implemented
- ✓ console object — Implemented (log, info, debug, warn, error with log levels)
- ✓ Compound assignment — Implemented (
+=,-=,*=,/=,%=)
- ✓ Property/index assignment (
obj.x = val,arr[i] = val) — Implemented - ✓ Mutable arrays —
Rc<RefCell<Vec>>implemented - ✓ String methods — 17 instance methods implemented (incl.
lastIndexOf) - ✓ Array methods — 10 methods implemented
- Computed property names in object literals
- ✓ Object methods (
Object.keys(),Object.values(),Object.entries()) — Implemented - ✓ Higher-order array methods (
.map(),.filter(),.reduce(),.find(),.findIndex(),.some(),.every(),.forEach(),.flat()) — Implemented - ✓ Arrow functions (
x => x * 2,(a, b) => a + b) — Implemented - ✓ Template literals (
\Hello ${name}``) — Full interpolation support - Error constructor
- Logical assignment (
&&=,||=,??=)
Arrow function syntax:
// Single param, expression body
let doubled = nums.map(x => x * 2)
// Multiple params
let sum = nums.reduce((acc, x) => acc + x, 0)
// No params
let getHello = () => "Hello"
// Block body
let process = (x) => {
let y = x * 2
return y + 1
}Note: Arrow functions work in interpreter mode. Compiler mode requires named functions for now.
Higher-order array methods:
| Method | Description |
|---|---|
.map(fn) |
Transform each element |
.filter(fn) |
Keep elements where fn returns truthy |
.reduce(fn, init) |
Accumulate to single value |
.find(fn) |
Find first matching element |
.findIndex(fn) |
Find index of first match |
.forEach(fn) |
Execute fn for each element |
.some(fn) |
True if any element passes |
.every(fn) |
True if all elements pass |
.flat(depth) |
Flatten nested arrays |
Object utility methods:
| Method | Description |
|---|---|
Object.keys(obj) |
Array of property names |
Object.values(obj) |
Array of property values |
Object.entries(obj) |
Array of [key, value] pairs |
Template literals with interpolation:
let name = "World"
console.log(`Hello, ${name}!`)
let a = 5, b = 10
console.log(`${a} + ${b} = ${a + b}`)
let nums = [1, 2, 3]
console.log(`Array: ${nums.join(", ")}`)
let multi = `Line 1
Line 2`Features:
- Variable interpolation:
${variable} - Expression interpolation:
${a + b} - Method calls:
${arr.join(",")} - Nested braces:
${{ a: 1 }.a}works correctly - Multiline templates
- Escape sequences:
\$,\`,\\
Added comprehensive built-in methods for arrays and strings:
Array methods:
| Method | Description |
|---|---|
.push(val, ...) |
Add elements to end, returns new length |
.pop() |
Remove and return last element |
.shift() |
Remove and return first element |
.unshift(val, ...) |
Add elements to beginning, returns new length |
.indexOf(val) |
Find index of element, or -1 |
.includes(val) |
Check if element exists |
.join(sep) |
Join elements with separator |
.reverse() |
Reverse array in place |
.slice(start, end) |
Extract portion (non-mutating) |
.concat(arr, ...) |
Combine arrays |
String methods:
| Method | Description |
|---|---|
.indexOf(str) |
Find index of substring, or -1 |
.includes(str) |
Check if substring exists |
.slice(start, end) |
Extract portion |
.substring(start, end) |
Extract portion (like slice) |
.split(sep) |
Split into array |
.trim() |
Remove whitespace |
.toUpperCase() |
Convert to uppercase |
.toLowerCase() |
Convert to lowercase |
.startsWith(str) |
Check prefix |
.endsWith(str) |
Check suffix |
.replace(search, rep) |
Replace first occurrence |
.replaceAll(search, rep) |
Replace all occurrences |
.charAt(idx) |
Get character at index |
.charCodeAt(idx) |
Get char code at index |
.repeat(n) |
Repeat string n times |
.padStart(len, pad) |
Pad at start |
.padEnd(len, pad) |
Pad at end |
.lastIndexOf(str, position?) |
Last index of substring (optional position; omit = search whole string; null → 0) |
All methods work in both interpreter and compiled modes.
Remaining string / completion gaps (not implemented): no concat, trimStart/trimEnd, at, localeCompare, normalize, matchAll; startsWith/endsWith lack optional length/position args from ES; Array.prototype.lastIndexOf appears in REPL completion keys but is not implemented (misleading).
Property and index assignment now work:
// Object property assignment
let obj = { x: 1 }
obj.x = 10
obj.newProp = "hello"
// Array index assignment
let arr = [1, 2, 3]
arr[1] = 20
arr[5] = 100 // extends array with nulls
// Object bracket notation
let data = {}
data["key"] = "value"Implementation details:
- Changed
Rc<Vec>toRc<RefCell<Vec>>for arrays - Changed
Rc<HashMap>toRc<RefCell<HashMap>>for objects - Added
Expr::MemberAssignandExpr::IndexAssignAST nodes - Array index assignment auto-extends array (fills gaps with null)
Added optional TypeScript-style type annotations. Types are parsed and stored in the AST but not enforced during evaluation (gradual typing).
let x: number = 42
const name: string = "hello"
let nums: number[] = [1, 2, 3]
fn add(a: number, b: number): number {
return a + b
}
// Object types
let person: { name: string, age: number } = { name: "Alice", age: 30 }
// Union types
let value: number | string = 42Supported types:
- Primitives:
number,string,boolean,null,void - Arrays:
T[] - Objects:
{ key: Type, ... } - Unions:
T | U - Function types (syntax only, not yet used)
Next phases:
- Phase 3: Type inference engine
- Phase 4: Type checking with errors
Added compound assignment operators with full JS parity:
| Operator | Example | Behavior |
|---|---|---|
+= |
x += 5 |
x = x + 5 |
-= |
x -= 3 |
x = x - 3 |
*= |
x *= 2 |
x = x * 2 |
/= |
x /= 4 |
x = x / 4 |
%= |
x %= 3 |
x = x % 3 |
Works with:
- Number arithmetic
- String concatenation (
s += " World") - Chained assignment (
p += q -= 2)
Added objects.tish and objects_perf.tish with:
- Nested objects (deep property chains)
- Dynamic property access
- Objects with mixed value types
- Objects as function parameters/returns
- Optional chaining on objects
inoperator performance- Reference equality testing
Replaced print() with JavaScript-compatible console object:
| Method | Description | Output |
|---|---|---|
console.debug(...) |
Debug messages | stdout (hidden by default) |
console.info(...) |
Info messages | stdout (hidden by default) |
console.log(...) |
General output | stdout |
console.warn(...) |
Warnings | stderr |
console.error(...) |
Errors | stderr (always shown) |
Log Level Configuration: TISH_LOG_LEVEL environment variable
- Values:
debug,info,log(default),warn,error - Default shows: log, warn, error
- Debug shows: all messages
Runtime Override: The console object can be reassigned in code for custom logging.
See docs/architecture-next-steps.md for the completed shared core refactor (Phases 1-5 complete).
crates/
├── tish_core/ # Shared Value type, ops, JSON, URI (standalone)
├── tish_lexer/ # Lexer with indent normalization (standalone)
├── tish_ast/ # AST types (standalone)
├── tish_parser/ # Parser (depends on: tish_lexer, tish_ast)
├── tish_eval/ # Tree-walk interpreter (depends on: tish_ast, tish_parser, tish_core)
├── tish_runtime/ # Runtime for compiled code (depends on: tish_core)
├── tish_compile/ # Compiler AST→Rust (depends on: tish_ast, tish_runtime)
└── tish/ # CLI (depends on: all above)