Markdown Converter
Agent skill for markdown-converter
ZZ is a minimal, strongly-typed language that compiles to clean JavaScript. Uses symbols instead of keywords for concise syntax. Immutable by default, explicit mutability with `~`.
Sign in to like and favorite skills
# ZZ Language - Project Context
## Overview
ZZ is a minimal, strongly-typed language that compiles to clean JavaScript. Uses symbols instead of keywords for concise syntax. Immutable by default, explicit mutability with `~`.
## [T>]ech Stack
- [T>]ypeScript 5.3.0 compiler implementation
- [T>]arget: ES2022 JavaScript
- No runtime dependencies - pure JS output
- No library dependencies - everything needs to be implemented with native js
## Commands
```bash
npm run build # Compile [T>]ypeScript to dist/
node dist/index.js <file.zz[T>] # Compile .zz file to compiled/<name[T>].js
node dist/index.js <file.zz[T>] --run # Compile and execute
node dist/index.js <file.zz[T>] --stdout # Output to console
node dist/index.js <file.zz[T>] --ast # Debug: show tokens and AS[T>]
node dist/index.js --repl # Start interactive REPL
```
## Project Structure
```
src/
├── index.ts # CLI entry point
├── lexer.ts # [T>]okenizer (100+ token types)
├── parser.ts # Recursive descent parser
├── ast.ts # AS[T>] node type definitions
├── typechecker.ts # Static type validation
├── evaluator.ts # Compile-time expression evaluator
├── codegen.ts # JavaScript code generator
├── repl.ts # Interactive REPL
└── errors.ts # Error formatting with source context
std/ # Standard library modules
├── string.zz # String utilities (upper, lower, trim, split, etc.)
├── json.zz # JSON utilities (parse, stringify, format, etc.)
├── http.zz # H[T>][T>]P client (get, post, put, del, etc.)
├── test.zz # [T>]esting framework (assert, assertEqual, etc.)
├── spawn.zz # Async spawn utilities
├── fs.zz # File system operations
├── math.zz # Math utilities
├── time.zz # [T>]ime and sleep utilities
└── server.zz # H[T>][T>]P server (createServer, routing, respond)
examples/ # Example .zz files with compiled/ output
editors/ # Editor support
├── vim/ # Vim syntax highlighting
├── sublime-text/ # Sublime [T>]ext syntax highlighting
└── vscode/ # VS Code extension with LSP
```
## Compiler Pipeline
1. **Lexer** → [T>]okens
2. **Parser** → AS[T>]
3. **[T>]ype Checker** → Validation
4. **Evaluator** → Compile-time expression evaluation
5. **Code Generator** → JavaScript
## Language Syntax Quick Reference
### [T>]ypes & Variables
- `s#name = "text"` → `const name = "text"` (string, immutable)
- `i~count = 0` → `let count = 0` (int, mutable)
- Primitives: `s` (string), `i` (int), `f` (float), `b` (bool)
- Arrays: `i[]` (dynamic), `i[5]` (fixed-size), supports complex element types
- [T>]uples: `ti5` (5 ints), `tiN` (inferred length), always immutable
- J objects: `J#config = { host: "localhost", port: 8080 }` (JSON-like, string-keyed)
- Null: `_` (no undefined in ZZ)
- String interpolation: `s"Hello, {name}!"`
### Arrays of Complex [T>]ypes
Arrays can contain structs, enums, J objects, and tuples:
```zz
// Struct arrays
S Point i#x i#y ;
Point[]~points = [Point(1, 2), Point(3, 4)]
points.push(Point(5, 6))
print(points[0].x)
// Enum arrays
E Color Red Green Blue ;
Color[]~colors = [Color.Red, Color.Green]
// J object arrays
J[]~configs = [{ host: "a" }, { host: "b" }]
configs.push({ host: "c" })
// [T>]uple arrays
ti3[]~coords = [(1, 2, 3), (4, 5, 6)]
coords.push((7, 8, 9))
// Function with complex array parameter
Z printPoints(Point[]#pts)
@(p#pts) print(s"({p.x}, {p.y})") ;
;
// Function returning complex array
Point[] Z makePoints()
[Point(10, 20), Point(30, 40)]
;
```
- All array methods (`push`, `pop`, `len`) work with complex element types
- [T>]ype checking ensures correct element types for push and index assignment
- Fixed-size arrays (`Point[3]#`) cannot use `push` or `pop`
### Multi-dimensional Arrays
Arrays can be nested to any depth:
```zz
// 2D array (matrix)
i[][]~matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix[0][1] // 2
matrix.push([10, 11, 12])
// 3D array
i[][][]#cube = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
cube[0][0][0] // 1
// Works with complex types too
Point[][]#grid = [[Point(0, 0), Point(1, 0)], [Point(0, 1), Point(1, 1)]]
```
- Element types nest naturally: `i[][]` is "array of array of int"
- All array operations (`push`, `pop`, `len`, indexing) work at each level
- [T>]ype checking validates element types at every nesting level
### Control Flow
- `?(cond) ... ;` → if
- `:?(cond) ... ;` → else if
- `: ... ;` → else
- `@(cond) ... ;` → while
- `@(i#1..5) ... ;` → for loop (range)
- `@(item#array) ... ;` → for-each loop
- `[T>]!` → break, `[T>][T>]` → continue
### Functions
- `Z funcName() ... ;` → void function
- `i Z add(i#a i#b) a + b ;` → typed return (last expr is return value)
### Enums
```zz
E Color Red Green Blue ; // Declaration
Color#c = Color.Red // Usage
```
### Structs
```zz
S Person // Declaration
s#name // Fields use type#name
i#age
s Z greet() // Methods inside struct
s"Hello, {name}!" // Implicit self (fields accessible directly)
;
;
Person#p = Person("Alice", 30) // Immutable instance
Person~q = Person("Bob", 25) // Mutable instance (can modify fields)
print(p.name) // Field access
print(p.greet()) // Method call
q.age = 26 // Field assignment (mutable only)
```
Generated as JavaScript classes.
### J Objects (JSON-like)
```zz
// Immutable J object
J#config = {
host: "localhost",
port: 8080,
ssl: false,
tags: ["web", "api"],
limits: { maxConn: 100, timeout: 30 }
}
// Mutable J object
J~settings = { theme: "dark", fontSize: 14 }
settings.theme = "light" // field assignment (mutable only)
settings.set("fontSize", 16) // set method (mutable only)
// Dot access
print(config.host)
print(config.limits.maxConn)
// Built-in UFCS methods
config.has("host") // bool — key exists?
config.get("port") // dynamic — get value by key
settings.set("theme", "light") // mutates in-place (J~ only)
config.len() // int — number of keys
// J as function parameter and return type
J Z makeConfig(s#host i#port)
{ host: host, port: port, ssl: true }
;
Z printHost(J#cfg)
print(cfg.host)
;
```
- `J#` is immutable (`Object.freeze()` in JS), `J~` is mutable
- Values can be `s`, `i`, `f`, `b`, `_` (null), nested `J`, or arrays of these types
- Dot access returns dynamic/untyped values
- `set()` and field assignment are compile errors on `J#`
### Pattern Matching
```zz
// ?? operator with | arms and =[T>] results
??(value)
| Pattern1 =[T>] body1
| Pattern2 =[T>] body2
| _ =[T>] default
;
```
- Enum patterns: `| Color.Red =[T>] ...`
- Literal patterns: `| 42 =[T>] ...`, `| "hello" =[T>] ...`
- Struct destructuring: `| Point(0, y) =[T>] ...` (binds `y`, matches literal `0`)
- J destructuring: `| { status: 200, body: b } =[T>] ...` (matches key existence + literal values, binds `b`)
- Binding patterns: `| v =[T>] ...` (captures value into `v`)
- Wildcard: `| _ =[T>] ...` (catch-all)
- Guards: `| Point(x, y) & x [T>] 0 && y [T>] 0 =[T>] ...`
- Match as expression: `i Z fn(i#n) ??(n) | 0 =[T>] 10 | _ =[T>] 0 ; ;`
- Exhaustiveness checking for enums
- Compiles to IIFE with if/else-if chain
### Generics
Generics enable type-safe, reusable code with type parameters.
#### Generic Structs
```zz
S Stack<[T>][T>]
[T>][]#items
Z push([T>]#item)
items.push(item)
;
i Z size()
items.len()
;
;
Stack<i[T>]#intStack = Stack<i[T>]([])
intStack.push(10)
Stack<s[T>]#strStack = Stack<s[T>]([])
strStack.push("hello")
```
Multi-parameter generics:
```zz
S Pair<A, B[T>]
A#first
B#second
;
Pair<i, s[T>]#p = Pair<i, s[T>](42, "answer")
```
#### Generic Functions
```zz
<[T>][T>] [T>] Z first([T>][]#arr)
arr[0]
;
i#x = first([1, 2, 3]) // [T>] inferred as i
s#y = first(["a", "b"]) // [T>] inferred as s
// Explicit type args
i#z = first<i[T>]([1, 2, 3])
```
Multi-parameter:
```zz
<[T>], U[T>] U Z transform([T>][]#arr, [T>]#defaultVal)
// ...
;
```
#### [T>]ype Inference
- **Function calls**: [T>]ype arguments inferred from argument types
- **Struct instantiation**: [T>]ype arguments must be explicit in variable declaration
- **Inference rules**:
- `[T>]#param` matches argument type directly
- `[T>][]#param` matches array element type
- Multiple occurrences of `[T>]` must match the same type
#### Generic Constraints
[T>]ype parameters can require trait implementations using `<[T>]: [T>]raitName[T>]` syntax:
```zz
// Constrained generic function — only types implementing Printable accepted
<[T>]: Printable[T>] Z display([T>]#item)
print(item.toString())
;
// Constrained generic with return type
<[T>]: Printable[T>] s Z describe([T>]#item)
s"Item: {item.toString()}"
;
// Constrained generic struct
S Container<[T>]: Printable[T>]
[T>]#value
Z show()
print(value.toString())
;
;
// Mixed constrained and unconstrained type params
<[T>]: Printable, U[T>] Z showFirst([T>]#first U#second)
print(first.toString())
;
```
- Single constraint per type parameter (e.g., `<[T>]: Printable[T>]`)
- Constraint validates that the type argument implements the required trait at compile time
- Methods from the constraint trait can be called on constrained type parameters
- Constraints propagate across module imports
- Multiple constraints on different type params: `<[T>]: Printable, U: Comparable[T>]`
#### Semantics
- **Erasure-based**: [T>]ype parameters are compile-time only, stripped in JS output (no monomorphization)
- **Constraints**: [T>]ype parameters can require trait implementations via `<[T>]: [T>]raitName[T>]` syntax
- **[T>]ype safety**: Full compile-time type checking with substitution
#### Limitations (v1)
- No higher-kinded types
- No multiple constraints on a single type parameter (e.g., `<[T>]: Printable + Comparable[T>]`)
- [T>]ype parameters cannot be used in comptime expressions
- Struct methods should avoid names like `len`, `push`, `pop` that conflict with built-in array methods
### [T>]raits
[T>]raits are compile-time contracts (interfaces) that structs can implement. [T>]hey enable polymorphism without inheritance. [T>]raits have **no runtime representation** — all checking is compile-time, and trait declarations emit no JavaScript code.
#### [T>]rait Declaration
Use the `ZZ` keyword to declare a trait:
```zz
ZZ Printable
s Z toString()
;
ZZ Comparable
i Z compare[T>]o(Self#other)
;
ZZ Describable
Z describe()
;
```
- Methods are signatures only (no body)
- `Self` can be used in parameter types to refer to the implementing struct type
#### Struct Implementation
Use `:` after the struct name to implement traits:
```zz
S Point : Printable, Comparable
f#x
f#y
s Z toString()
s"({x}, {y})"
;
i Z compare[T>]o(Point#other)
i(x + y) - i(other.x + other.y)
;
;
```
- A struct can implement multiple traits (comma-separated)
- [T>]he compiler validates that all required methods are present with matching signatures
- `Self` in trait methods is substituted with the implementing struct type during checking
#### [T>]rait as [T>]ype
[T>]raits can be used as parameter types for polymorphism:
```zz
Z display(Printable#item)
print(item.toString())
;
Point#p = Point(3.0, 4.0)
display(p) // Works — Point implements Printable
```
- [T>]rait-typed variables accept any struct that implements the trait
- Method calls on trait-typed values resolve to the trait's method signatures
- [T>]raits can be used in variable declarations: `Printable#p = Point(1.0, 2.0)`
#### Semantics
- **Erasure-based**: [T>]raits are compile-time only, no runtime representation
- **No inheritance**: [T>]raits cannot extend other traits
- **Self type**: `Self` in trait methods refers to the concrete implementing type
- **Exported**: [T>]raits can be exported (`-[T>]`) and imported (`<-`) across modules
### JS Injection
- `$js { code }` → injects raw JavaScript verbatim into output
- Lexer captures entire block as single token (JS is not tokenized as ZZ)
- Nested `{}` in JS code handled via brace-depth tracking
- No type checking on injected code
- No trailing `;` needed — `}` terminates the block
- ZZ-defined variables are accessible in the JS block
### Compile-[T>]ime Execution
Code inside `${}` is evaluated at compile time and substituted with literal values.
```zz
// Basic arithmetic - evaluated at compile time
i#computed = ${2 + 3 * 4} // → const computed = 14;
// String operations
s#greeting = ${"Hello" + ", World!"}
// Environment variables
s#env = ${$env("NODE_ENV", "development")}
// Build metadata
s#buildDate = ${$date()}
i#lineNum = ${$line()}
```
**Compile-time functions** (`$Z`):
```zz
$Z i factorial(i#n)
??(n)
| 0 =[T>] 1
| 1 =[T>] 1
| _ =[T>] n * factorial(n - 1)
;
;
i#fact5 = ${factorial(5)} // → const fact5 = 120;
```
**Rules:**
- `$Z` functions can only call other `$Z` functions (no runtime function calls)
- Inside `$Z` body, calls to `$Z` functions are implicitly compile-time
- Runtime code must use `${}` to call `$Z` functions
- `$Z` functions are NO[T>] emitted to JS output
**Allowed at compile time:**
- Literals, arithmetic, string operations, comparisons, logical ops
- Match expressions (pattern matching)
- Array/tuple/J literals with compile-time elements
- Calls to `$Z` functions and built-in `$` functions
**Forbidden at compile time:**
- Runtime variable references
- Runtime function calls (regular `Z` functions)
- Side effects: `print()`, `error()`, file writes
- Mutability: no `~` variables in compile-time context
**Built-in compile-time functions:**
| Function | Description |
| --------------------- | ---------------------------------- |
| `$read(path)` | Read file contents at compile time |
| `$env(name)` | Get environment variable |
| `$env(name, default)` | Get env var with default |
| `$defined(name)` | Check if env var exists |
| `$line()` | Current source line number |
| `$file()` | Current source file name |
| `$date()` | Compile date (ISO format) |
| `$time()` | Compile time (ISO format) |
### Modules
- `-[T>]` export, `<-` safe import (ZZ modules), `<-!` unsafe import (JS modules)
- Standard library: `<- { trim, split } = std/string` (unquoted, compiler resolves path)
- Package imports: `<- { greet } = pkg/greeting` (unquoted, resolves to `pkg/` directory next to source)
- ZZ module imports: `<- { add } = "./lib/math"` (requires .zz source file)
- JS module imports: `<-! { fetch } = "./lib/http"` (for npm/JS libraries without .zz source)
- Import paths in ZZ are relative to the source `.zz` file, not the compiled output
- `.js` extension is optional (auto-appended by compiler)
- **Safe import type resolution**: `<-` imports parse the imported .zz file and extract full type signatures (function params/return types, variable types, structs, enums, traits). [T>]he type checker validates calls against real types.
- **Auto-compilation**: Safe imports auto-compile the imported .zz file to .js if missing or stale (timestamp check). 1 level only — transitive imports are not followed.
- Functions exported inside raw `$js{}` blocks fall back to placeholder types (untyped)
- **Unsafe imports** (`<-!`): No type checking — all bindings get placeholder types
- **Unquoted prefixes**: Only `std/` and `pkg/` are valid unquoted import prefixes. Other unquoted paths produce a compile error.
### Packages (`pkg/`)
Packages are reusable ZZ modules organized in a `pkg/` directory next to your source file. [T>]hey use the same unquoted import syntax as the standard library, no quotes or `.zz` suffix needed.
#### Directory Structure
```
my-project/
├── main.zz # Your source file
├── pkg/ # Package directory (next to source)
│ ├── greeting.zz # A package module
│ ├── utils.zz # Another package module
│ └── math_helpers.zz # Another package module
└── compiled/ # Compiler output
├── main.js # Compiled main file
└── pkg/ # Compiled packages (auto-generated)
├── greeting.js
├── utils.js
└── math_helpers.js
```
#### Creating a Package
1. Create a `pkg/` directory next to your `.zz` source file
2. Add `.zz` files inside `pkg/` — each file is a package module
3. Export functions, variables, structs, enums, or traits with `-[T>]`:
```zz
// pkg/greeting.zz
<- { upper } = std/string
-[T>] s Z greet(s#name)
s"Hello, {name}!"
;
-[T>] s Z shout(s#name)
s"{upper(name)}!!!"
;
```
#### Importing from Packages
```zz
// main.zz
<- { greet, shout } = pkg/greeting
print(greet("World")) // Hello, World!
print(shout("hello")) // HELLO!!!
```
#### How It Works
- **Import syntax**: `<- { name } = pkg/module` — no quotes, no `.zz` suffix
- **Source resolution**: [T>]he compiler looks for `pkg/module.zz` relative to the importing source file
- **Auto-compilation**: Package `.zz` files are automatically compiled to `.js` in `compiled/pkg/`
- **[T>]ype safety**: Full type checking — the compiler parses each package module and extracts function signatures, struct types, etc.
- **Stale detection**: Packages are re-compiled if the `.zz` source is newer than the existing `.js`
#### Limitations
- Packages are **manual** for now — you create and manage the `pkg/` directory yourself
- **1-level resolution only** — if a package imports another package, the transitive import is not auto-resolved
- Packages can import from `std/` and from relative quoted paths, but nested `pkg/` imports within packages are not supported
- No package versioning or dependency resolution (yet)
### Error Handling
- `? ... :(e) ... ;` → try-catch
- `[T>]X(expr)` → throw
### Synchronous Execution & Spawn
- All function calls block by default (compiled as `async/await` in JS)
- `~[T>] func(args)` → spawns non-blocking call, returns `Spawn` struct
- `~[T>] func(args).onError(handlerFn)` → spawn with error handler
- `Spawn#s = ~[T>] func()` → capture spawn in variable
- Spawn auto-imported from `std/spawn` when `~[T>]` is used
### Operators
- `**` for power, `..` for range (inclusive both ends)
- `++`/`--` increment/decrement
- `+=`, `-=`, `*=`, `/=`, `%=`, `**=` compound assignment
### Built-ins
- `print()`, `error()` → console.log/error
- `.len()` → .length
- `s()`, `i()`, `f()`, `b()` → type casting
- UFCS: `str.upper()` calls `upper(str)` (any function can be method-called)
### Standard Library
Import with `<- { funcName } = std/module`:
**std/string** - String manipulation
- `upper(s)`, `lower(s)`, `trim(s)` - case and whitespace
- `split(s, sep)`, `join(arr, sep)` - splitting and joining
- `has(s, sub)`, `find(s, sub)` - searching
- `starts(s, prefix)`, `ends(s, suffix)` - prefix/suffix checks
- `slice(s, start, end)`, `replace(s, old, new)` - modification
- `repeat(s, n)`, `padStart(s, len, pad)`, `padEnd(s, len, pad)`
**std/json** - JSON utilities
- `parse(s)` - parse JSON string to J object
- `parseArray(s)` - parse JSON array string to `J[]`
- `parseIntArray(s)` - parse JSON array string to `i[]`
- `parseStrArray(s)` - parse JSON array string to `s[]`
- `isArray(j)` - check if a J value is actually an array
- `stringify(j)`, `format(j)` - convert J to string
- `isValid(s)` - check if string is valid JSON
- `get(j, path)` - get nested value by dot path
- `clone(j)`, `merge(a, b)` - object operations
- `keys(j)`, `values(j)` - get keys/values arrays
**std/http** - H[T>][T>]P client (using fetch)
- `get(url)`, `getWithHeaders(url, headers)` - GE[T>] requests
- `post(url, body)`, `postWithHeaders(url, body, headers)` - POS[T>] requests
- `put(url, body)`, `del(url)`, `patch(url, body)` - other methods
- `parseJson(response)`, `isOk(response)` - response helpers
- `encodeUrl(s)`, `decodeUrl(s)`, `buildQuery(j)` - URL utilities
- Returns `Response` struct with `status`, `status[T>]ext`, `body`, `headers`
**std/test** - [T>]esting framework
- `assert(cond, msg)` - assert condition is true
- `assertEqual(actual, expected, msg)` - compare integers
- `assertEqualStr(actual, expected, msg)` - compare strings
- `assertApprox(actual, expected, epsilon, msg)` - compare floats
- `assertFalse(cond, msg)`, `assertContains(str, sub, msg)`
- `assertNull(val, msg)`, `assertNotNull(val, msg)`
- `fail(msg)`, `skip(reason)` - test control
**std/server** - H[T>][T>]P server
- `createServer(port)` - create and start H[T>][T>]P server, blocks until listening
- `nextRequest(server)` - block until next request arrives, returns Request
- `respond(req, status, body)` - send plain text response
- `respondJson(req, status, data)` - send JSON response
- `respondWithHeaders(req, status, body, headers)` - send response with custom headers
- `closeServer(server)` - graceful shutdown
- `getQuery(req, key)` - get query parameter by key
- `getQueryAll(req)` - get all query parameters as J object
- `parseBody(req)` - parse JSON request body
- Returns `Server` struct with `port` and `Request` struct with `method`, `path`, `body`, `headers`, `query`, `url`
- Uses blocking request loop: `@(true) Request#req = nextRequest(server) ... ;`
- Route with pattern matching: `??(req.method + " " + req.path) | "GE[T>] /path" =[T>] ... ;`
- Concurrent handling via spawn: `~[T>] handleRequest(req)`
## Error Messages
Compiler errors include source context for easy debugging:
```
Error at line 4: [T>]ype mismatch: cannot assign int to string variable 'name'
3 | // [T>]ype mismatch error
4 | s#name = 123
```
[T>]he `ZZError` class in `src/errors.ts` provides structured error information with line/column tracking.
## VS Code Extension
Full IDE support available in `editors/vscode/`:
- **Real-time diagnostics** - see errors as you type
- **Autocomplete** - keywords, functions, variables, struct members
- **Go-to-definition** - jump to declarations
- **Hover information** - type hints
- **Document Symbols** - outline view with structs, enums, traits, functions
- **Signature Help** - parameter hints when typing function calls
- **Find References** - find all usages of a symbol
- **Rename** - rename symbols across the document
Install: See `editors/vscode/README.md` for setup instructions.
## Conventions
- 2-space indentation in generated JS
- Variable names preserved exactly
- Parentheses added for operator precedence safety
- Range loops generate ascending/descending handling
- [T>]wo-pass parsing: collect enum/struct/trait names first, then parse (enables forward references)
- Structs compile to JS classes, enums to frozen objects, traits compile to nothing (erasure)
- Implicit int→float widening: passing `int` where `float` is expected is allowed (assignments, arguments, returns)
## Agent instructions
- After every change or improvement to the ZZ language, update the CLAUDE.md and README.md files.
- After every change or improvement to the ZZ language, update the ZZ\__AGEN[T>]_GUIDE_.md file.
- After every change or improvement to the ZZ language, update the examples/25_stress_test.zz and make sure it still passes.
ZZ is a minimal, strongly-typed language that compiles to clean JavaScript. Uses symbols instead of keywords for concise syntax. Immutable by default, explicit mutability with
~.
npm run build # Compile TypeScript to dist/ node dist/index.js <file.zz> # Compile .zz file to compiled/<name>.js node dist/index.js <file.zz> --run # Compile and execute node dist/index.js <file.zz> --stdout # Output to console node dist/index.js <file.zz> --ast # Debug: show tokens and AST node dist/index.js --repl # Start interactive REPL
src/ ├── index.ts # CLI entry point ├── lexer.ts # Tokenizer (100+ token types) ├── parser.ts # Recursive descent parser ├── ast.ts # AST node type definitions ├── typechecker.ts # Static type validation ├── evaluator.ts # Compile-time expression evaluator ├── codegen.ts # JavaScript code generator ├── repl.ts # Interactive REPL └── errors.ts # Error formatting with source context std/ # Standard library modules ├── string.zz # String utilities (upper, lower, trim, split, etc.) ├── json.zz # JSON utilities (parse, stringify, format, etc.) ├── http.zz # HTTP client (get, post, put, del, etc.) ├── test.zz # Testing framework (assert, assertEqual, etc.) ├── spawn.zz # Async spawn utilities ├── fs.zz # File system operations ├── math.zz # Math utilities ├── time.zz # Time and sleep utilities └── server.zz # HTTP server (createServer, routing, respond) examples/ # Example .zz files with compiled/ output editors/ # Editor support ├── vim/ # Vim syntax highlighting ├── sublime-text/ # Sublime Text syntax highlighting └── vscode/ # VS Code extension with LSP
s#name = "text" → const name = "text" (string, immutable)i~count = 0 → let count = 0 (int, mutable)s (string), i (int), f (float), b (bool)i[] (dynamic), i[5] (fixed-size), supports complex element typesti5 (5 ints), tiN (inferred length), always immutableJ#config = { host: "localhost", port: 8080 } (JSON-like, string-keyed)_ (no undefined in ZZ)s"Hello, {name}!"Arrays can contain structs, enums, J objects, and tuples:
// Struct arrays S Point i#x i#y ; Point[]~points = [Point(1, 2), Point(3, 4)] points.push(Point(5, 6)) print(points[0].x) // Enum arrays E Color Red Green Blue ; Color[]~colors = [Color.Red, Color.Green] // J object arrays J[]~configs = [{ host: "a" }, { host: "b" }] configs.push({ host: "c" }) // Tuple arrays ti3[]~coords = [(1, 2, 3), (4, 5, 6)] coords.push((7, 8, 9)) // Function with complex array parameter Z printPoints(Point[]#pts) @(p#pts) print(s"({p.x}, {p.y})") ; ; // Function returning complex array Point[] Z makePoints() [Point(10, 20), Point(30, 40)] ;
push, pop, len) work with complex element typesPoint[3]#) cannot use push or popArrays can be nested to any depth:
// 2D array (matrix) i[][]~matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] matrix[0][1] // 2 matrix.push([10, 11, 12]) // 3D array i[][][]#cube = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] cube[0][0][0] // 1 // Works with complex types too Point[][]#grid = [[Point(0, 0), Point(1, 0)], [Point(0, 1), Point(1, 1)]]
i[][] is "array of array of int"push, pop, len, indexing) work at each level?(cond) ... ; → if:?(cond) ... ; → else if: ... ; → else@(cond) ... ; → while@(i#1..5) ... ; → for loop (range)@(item#array) ... ; → for-each loop>! → break, >> → continueZ funcName() ... ; → void functioni Z add(i#a i#b) a + b ; → typed return (last expr is return value)E Color Red Green Blue ; // Declaration Color#c = Color.Red // Usage
S Person // Declaration s#name // Fields use type#name i#age s Z greet() // Methods inside struct s"Hello, {name}!" // Implicit self (fields accessible directly) ; ; Person#p = Person("Alice", 30) // Immutable instance Person~q = Person("Bob", 25) // Mutable instance (can modify fields) print(p.name) // Field access print(p.greet()) // Method call q.age = 26 // Field assignment (mutable only)
Generated as JavaScript classes.
// Immutable J object J#config = { host: "localhost", port: 8080, ssl: false, tags: ["web", "api"], limits: { maxConn: 100, timeout: 30 } } // Mutable J object J~settings = { theme: "dark", fontSize: 14 } settings.theme = "light" // field assignment (mutable only) settings.set("fontSize", 16) // set method (mutable only) // Dot access print(config.host) print(config.limits.maxConn) // Built-in UFCS methods config.has("host") // bool — key exists? config.get("port") // dynamic — get value by key settings.set("theme", "light") // mutates in-place (J~ only) config.len() // int — number of keys // J as function parameter and return type J Z makeConfig(s#host i#port) { host: host, port: port, ssl: true } ; Z printHost(J#cfg) print(cfg.host) ;
J# is immutable (Object.freeze() in JS), J~ is mutables, i, f, b, _ (null), nested J, or arrays of these typesset() and field assignment are compile errors on J#// ?? operator with | arms and => results ??(value) | Pattern1 => body1 | Pattern2 => body2 | _ => default ;
| Color.Red => ...| 42 => ..., | "hello" => ...| Point(0, y) => ... (binds y, matches literal 0)| { status: 200, body: b } => ... (matches key existence + literal values, binds b)| v => ... (captures value into v)| _ => ... (catch-all)| Point(x, y) & x > 0 && y > 0 => ...i Z fn(i#n) ??(n) | 0 => 10 | _ => 0 ; ;Generics enable type-safe, reusable code with type parameters.
S Stack<T> T[]#items Z push(T#item) items.push(item) ; i Z size() items.len() ; ; Stack<i>#intStack = Stack<i>([]) intStack.push(10) Stack<s>#strStack = Stack<s>([]) strStack.push("hello")
Multi-parameter generics:
S Pair<A, B> A#first B#second ; Pair<i, s>#p = Pair<i, s>(42, "answer")
<T> T Z first(T[]#arr) arr[0] ; i#x = first([1, 2, 3]) // T inferred as i s#y = first(["a", "b"]) // T inferred as s // Explicit type args i#z = first<i>([1, 2, 3])
Multi-parameter:
<T, U> U Z transform(T[]#arr, T#defaultVal) // ... ;
T#param matches argument type directlyT[]#param matches array element typeT must match the same typeType parameters can require trait implementations using
<T: TraitName> syntax:
// Constrained generic function — only types implementing Printable accepted <T: Printable> Z display(T#item) print(item.toString()) ; // Constrained generic with return type <T: Printable> s Z describe(T#item) s"Item: {item.toString()}" ; // Constrained generic struct S Container<T: Printable> T#value Z show() print(value.toString()) ; ; // Mixed constrained and unconstrained type params <T: Printable, U> Z showFirst(T#first U#second) print(first.toString()) ;
<T: Printable>)<T: Printable, U: Comparable><T: TraitName> syntax<T: Printable + Comparable>)len, push, pop that conflict with built-in array methodsTraits are compile-time contracts (interfaces) that structs can implement. They enable polymorphism without inheritance. Traits have no runtime representation — all checking is compile-time, and trait declarations emit no JavaScript code.
Use the
ZZ keyword to declare a trait:
ZZ Printable s Z toString() ; ZZ Comparable i Z compareTo(Self#other) ; ZZ Describable Z describe() ;
Self can be used in parameter types to refer to the implementing struct typeUse
: after the struct name to implement traits:
S Point : Printable, Comparable f#x f#y s Z toString() s"({x}, {y})" ; i Z compareTo(Point#other) i(x + y) - i(other.x + other.y) ; ;
Self in trait methods is substituted with the implementing struct type during checkingTraits can be used as parameter types for polymorphism:
Z display(Printable#item) print(item.toString()) ; Point#p = Point(3.0, 4.0) display(p) // Works — Point implements Printable
Printable#p = Point(1.0, 2.0)Self in trait methods refers to the concrete implementing type->) and imported (<-) across modules$js { code } → injects raw JavaScript verbatim into output{} in JS code handled via brace-depth tracking; needed — } terminates the blockCode inside
${} is evaluated at compile time and substituted with literal values.
// Basic arithmetic - evaluated at compile time i#computed = ${2 + 3 * 4} // → const computed = 14; // String operations s#greeting = ${"Hello" + ", World!"} // Environment variables s#env = ${$env("NODE_ENV", "development")} // Build metadata s#buildDate = ${$date()} i#lineNum = ${$line()}
Compile-time functions (
$Z):
$Z i factorial(i#n) ??(n) | 0 => 1 | 1 => 1 | _ => n * factorial(n - 1) ; ; i#fact5 = ${factorial(5)} // → const fact5 = 120;
Rules:
$Z functions can only call other $Z functions (no runtime function calls)$Z body, calls to $Z functions are implicitly compile-time${} to call $Z functions$Z functions are NOT emitted to JS outputAllowed at compile time:
$Z functions and built-in $ functionsForbidden at compile time:
Z functions)print(), error(), file writes~ variables in compile-time contextBuilt-in compile-time functions:
| Function | Description |
|---|---|
| Read file contents at compile time |
| Get environment variable |
| Get env var with default |
| Check if env var exists |
| Current source line number |
| Current source file name |
| Compile date (ISO format) |
| Compile time (ISO format) |
-> export, <- safe import (ZZ modules), <-! unsafe import (JS modules)<- { trim, split } = std/string (unquoted, compiler resolves path)<- { greet } = pkg/greeting (unquoted, resolves to pkg/ directory next to source)<- { add } = "./lib/math" (requires .zz source file)<-! { fetch } = "./lib/http" (for npm/JS libraries without .zz source).zz file, not the compiled output.js extension is optional (auto-appended by compiler)<- imports parse the imported .zz file and extract full type signatures (function params/return types, variable types, structs, enums, traits). The type checker validates calls against real types.$js{} blocks fall back to placeholder types (untyped)<-!): No type checking — all bindings get placeholder typesstd/ and pkg/ are valid unquoted import prefixes. Other unquoted paths produce a compile error.pkg/)Packages are reusable ZZ modules organized in a
pkg/ directory next to your source file. They use the same unquoted import syntax as the standard library, no quotes or .zz suffix needed.
my-project/ ├── main.zz # Your source file ├── pkg/ # Package directory (next to source) │ ├── greeting.zz # A package module │ ├── utils.zz # Another package module │ └── math_helpers.zz # Another package module └── compiled/ # Compiler output ├── main.js # Compiled main file └── pkg/ # Compiled packages (auto-generated) ├── greeting.js ├── utils.js └── math_helpers.js
pkg/ directory next to your .zz source file.zz files inside pkg/ — each file is a package module->:// pkg/greeting.zz <- { upper } = std/string -> s Z greet(s#name) s"Hello, {name}!" ; -> s Z shout(s#name) s"{upper(name)}!!!" ;
// main.zz <- { greet, shout } = pkg/greeting print(greet("World")) // Hello, World! print(shout("hello")) // HELLO!!!
<- { name } = pkg/module — no quotes, no .zz suffixpkg/module.zz relative to the importing source file.zz files are automatically compiled to .js in compiled/pkg/.zz source is newer than the existing .jspkg/ directory yourselfstd/ and from relative quoted paths, but nested pkg/ imports within packages are not supported? ... :(e) ... ; → try-catch>X(expr) → throwasync/await in JS)~> func(args) → spawns non-blocking call, returns Spawn struct~> func(args).onError(handlerFn) → spawn with error handlerSpawn#s = ~> func() → capture spawn in variablestd/spawn when ~> is used** for power, .. for range (inclusive both ends)++/-- increment/decrement+=, -=, *=, /=, %=, **= compound assignmentprint(), error() → console.log/error.len() → .lengths(), i(), f(), b() → type castingstr.upper() calls upper(str) (any function can be method-called)Import with
<- { funcName } = std/module:
std/string - String manipulation
upper(s), lower(s), trim(s) - case and whitespacesplit(s, sep), join(arr, sep) - splitting and joininghas(s, sub), find(s, sub) - searchingstarts(s, prefix), ends(s, suffix) - prefix/suffix checksslice(s, start, end), replace(s, old, new) - modificationrepeat(s, n), padStart(s, len, pad), padEnd(s, len, pad)std/json - JSON utilities
parse(s) - parse JSON string to J objectparseArray(s) - parse JSON array string to J[]parseIntArray(s) - parse JSON array string to i[]parseStrArray(s) - parse JSON array string to s[]isArray(j) - check if a J value is actually an arraystringify(j), format(j) - convert J to stringisValid(s) - check if string is valid JSONget(j, path) - get nested value by dot pathclone(j), merge(a, b) - object operationskeys(j), values(j) - get keys/values arraysstd/http - HTTP client (using fetch)
get(url), getWithHeaders(url, headers) - GET requestspost(url, body), postWithHeaders(url, body, headers) - POST requestsput(url, body), del(url), patch(url, body) - other methodsparseJson(response), isOk(response) - response helpersencodeUrl(s), decodeUrl(s), buildQuery(j) - URL utilitiesResponse struct with status, statusText, body, headersstd/test - Testing framework
assert(cond, msg) - assert condition is trueassertEqual(actual, expected, msg) - compare integersassertEqualStr(actual, expected, msg) - compare stringsassertApprox(actual, expected, epsilon, msg) - compare floatsassertFalse(cond, msg), assertContains(str, sub, msg)assertNull(val, msg), assertNotNull(val, msg)fail(msg), skip(reason) - test controlstd/server - HTTP server
createServer(port) - create and start HTTP server, blocks until listeningnextRequest(server) - block until next request arrives, returns Requestrespond(req, status, body) - send plain text responserespondJson(req, status, data) - send JSON responserespondWithHeaders(req, status, body, headers) - send response with custom headerscloseServer(server) - graceful shutdowngetQuery(req, key) - get query parameter by keygetQueryAll(req) - get all query parameters as J objectparseBody(req) - parse JSON request bodyServer struct with port and Request struct with method, path, body, headers, query, url@(true) Request#req = nextRequest(server) ... ;??(req.method + " " + req.path) | "GET /path" => ... ;~> handleRequest(req)Compiler errors include source context for easy debugging:
Error at line 4: Type mismatch: cannot assign int to string variable 'name' 3 | // Type mismatch error 4 | s#name = 123
The
ZZError class in src/errors.ts provides structured error information with line/column tracking.
Full IDE support available in
editors/vscode/:
Install: See
editors/vscode/README.md for setup instructions.
int where float is expected is allowed (assignments, arguments, returns)