52 Commits

Author SHA1 Message Date
John Alanbrook
a4f3b025c5 update 2026-02-08 08:25:48 -06:00
John Alanbrook
bae4e957e9 hugo website for pit 2026-02-07 12:01:58 -06:00
John Alanbrook
83ea67c01b Merge branch 'mach' into mcode2 2026-02-07 00:10:01 -06:00
John Alanbrook
16059cca4e fix tests 2026-02-07 00:09:58 -06:00
John Alanbrook
9ffe60ebef vm suite 2026-02-07 00:09:41 -06:00
John Alanbrook
2beafec5d9 fix tests 2026-02-07 00:09:21 -06:00
John Alanbrook
aba8eb66bd crash fixes 2026-02-06 23:38:56 -06:00
John Alanbrook
1abcaa92c7 Merge branch 'mach' into mcode2 2026-02-06 23:20:55 -06:00
John Alanbrook
168f7c71d5 fix text header chasing 2026-02-06 23:20:48 -06:00
John Alanbrook
56ed895b6e Merge branch 'mach' into mcode2 2026-02-06 23:15:38 -06:00
John Alanbrook
1e4646999d fix mach crashes 2026-02-06 23:15:33 -06:00
John Alanbrook
68d6c907fe fix mcode compilation 2026-02-06 23:13:13 -06:00
John Alanbrook
8150c64c7d pitcode 2026-02-06 22:58:21 -06:00
John Alanbrook
024d796ca4 add asan error vm stacktrace 2026-02-06 21:49:53 -06:00
John Alanbrook
ea185dbffd rm typeof 2026-02-06 21:26:45 -06:00
John Alanbrook
6571262af0 mach disrupt support 2026-02-06 21:09:18 -06:00
John Alanbrook
77ae133747 Merge branch 'mcode2' into mach 2026-02-06 20:45:57 -06:00
John Alanbrook
142a2d518b Merge branch 'stacktrace' into mach 2026-02-06 20:44:43 -06:00
John Alanbrook
5b65c64fe5 stack traces 2026-02-06 20:44:38 -06:00
John Alanbrook
e985fa5fe1 disrupt/disruption; remove try/catch 2026-02-06 18:40:56 -06:00
John Alanbrook
160ade2410 smarter gc malloc for large allocations 2026-02-06 18:38:23 -06:00
John Alanbrook
e2bc5948c1 fix functions and closures in mach 2026-02-06 18:30:26 -06:00
John Alanbrook
8cf98d8a9e Merge branch 'mcode2' into mach 2026-02-06 15:14:40 -06:00
John Alanbrook
3c38e828e5 context free tokenizing, parsing, compiling 2026-02-06 15:14:18 -06:00
John Alanbrook
af2d296f40 use new parser info 2026-02-06 12:45:25 -06:00
John Alanbrook
0a45394689 fix crash related to allocating in context heap 2026-02-06 12:43:19 -06:00
John Alanbrook
32885a422f bring in mcode 2026-02-06 04:24:14 -06:00
John Alanbrook
8959e53303 Merge branch 'newsyn' into mcode2 2026-02-06 03:55:56 -06:00
John Alanbrook
8a9a02b131 Merge branch 'newsyn' into mach 2026-02-06 03:54:38 -06:00
John Alanbrook
f9d68b2990 fix if/else, chained assignment 2026-02-06 03:54:25 -06:00
John Alanbrook
017a57b1eb use new parser information 2026-02-06 03:44:44 -06:00
John Alanbrook
ff8c68d01c mcode and mcode interpreter 2026-02-06 03:31:31 -06:00
John Alanbrook
9212003401 cannot set unbound 2026-02-06 03:24:01 -06:00
John Alanbrook
f9f8a4db42 Merge branch 'newsyn' into mach 2026-02-06 03:10:14 -06:00
John Alanbrook
8db95c654b more info in AST parser 2026-02-06 03:00:46 -06:00
John Alanbrook
63feabed5d mach vm 2026-02-06 02:50:48 -06:00
John Alanbrook
c814c0e1d8 rm new; rm void 2026-02-06 02:12:19 -06:00
John Alanbrook
bead0c48d4 Merge branch 'mcode' into newsyn 2026-02-06 02:02:46 -06:00
John Alanbrook
98dcab4ba7 comprehensive syntax test; fix multiple default args 2026-02-06 02:02:17 -06:00
John Alanbrook
ae44ce7b4b mcode and mach 2026-02-06 01:56:26 -06:00
John Alanbrook
1c38699b5a fix scope resolution 2026-02-06 01:41:03 -06:00
John Alanbrook
9a70a12d82 object literal 2026-02-05 21:41:34 -06:00
John Alanbrook
a8a271e014 Merge branch 'syntax' into ast 2026-02-05 20:39:56 -06:00
John Alanbrook
91761c03e6 push/pop syntax 2026-02-05 20:39:53 -06:00
John Alanbrook
5a479cc765 function literal in record literal 2026-02-05 20:32:57 -06:00
John Alanbrook
97a003e025 errors 2026-02-05 20:12:06 -06:00
John Alanbrook
20f14abd17 string templates 2026-02-05 19:34:06 -06:00
John Alanbrook
19ba184fec default params for functions 2026-02-05 18:44:40 -06:00
John Alanbrook
7909b11f6b better errors 2026-02-05 18:35:48 -06:00
John Alanbrook
27229c675c add parser and tokenizer errors 2026-02-05 18:14:49 -06:00
John Alanbrook
64d234ee35 Merge branch 'syntax' into ast 2026-02-05 17:45:15 -06:00
John Alanbrook
e861d73eec mkarecord 2026-02-05 17:45:13 -06:00
87 changed files with 14315 additions and 5089 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.git/
.obj/
website/
website/public/
website/.hugo_build.lock
bin/
build/
*.zip
@@ -15,6 +16,7 @@ build/
source/shaders/*.h
.DS_Store
*.html
!website/themes/**/*.html
.vscode
*.icns
icon.ico

View File

@@ -1,9 +0,0 @@
nav:
- index.md
- cellscript.md
- actors.md
- packages.md
- cli.md
- c-modules.md
- Standard Library: library

74
docs/_index.md Normal file
View File

@@ -0,0 +1,74 @@
---
title: "Documentation"
description: "ƿit language documentation"
type: "docs"
---
![image](/images/wizard.png)
ƿit is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
## Key Features
- **Actor Model** — isolated memory, message passing, no shared state
- **Immutability** — `stone()` makes values permanently frozen
- **Prototype Inheritance** — objects without classes
- **C Integration** — seamlessly extend with native code
- **Cross-Platform** — deploy to desktop, web, and embedded
## Quick Start
```javascript
// hello.ce - A simple actor
log.console("Hello, ƿit!")
$stop()
```
```bash
pit hello
```
## Language
- [**ƿit Language**](/docs/language/) — syntax, types, and built-in functions
- [**Actors and Modules**](/docs/actors/) — the execution model
- [**Packages**](/docs/packages/) — code organization and sharing
- [**Command Line**](/docs/cli/) — the `pit` tool
- [**Writing C Modules**](/docs/c-modules/) — native extensions
## Reference
- [**Built-in Functions**](/docs/functions/) — intrinsics reference
## Standard Library
- [text](/docs/library/text/) — string manipulation
- [number](/docs/library/number/) — numeric operations (functions are global: `floor()`, `max()`, etc.)
- [array](/docs/library/array/) — array utilities
- [object](/docs/library/object/) — object utilities
- [blob](/docs/library/blob/) — binary data
- [time](/docs/library/time/) — time and dates
- [math](/docs/library/math/) — trigonometry and math
- [json](/docs/library/json/) — JSON encoding/decoding
- [random](/docs/library/random/) — random numbers
## Architecture
ƿit programs are organized into **packages**. Each package contains:
- **Modules** (`.cm`) — return a value, cached and frozen
- **Actors** (`.ce`) — run independently, communicate via messages
- **C files** (`.c`) — compiled to native libraries
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
## Installation
```bash
# Clone and bootstrap
git clone https://gitea.pockle.world/john/cell
cd cell
make bootstrap
```
The ƿit shop is stored at `~/.pit/`.

View File

@@ -1,10 +1,15 @@
# Actors and Modules
---
title: "Actors and Modules"
description: "The ƿit execution model"
weight: 20
type: "docs"
---
Cell organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
ƿit organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
## The Actor Model
Cell is built on the actor model of computation. Each actor:
ƿit is built on the actor model of computation. Each actor:
- Has its own **isolated memory** — actors never share state
- Runs to completion each **turn** — no preemption
@@ -62,10 +67,10 @@ An actor is a script that **does not return a value**. It runs as an independent
// worker.ce
log.console("Worker started")
$on_message = function(msg) {
$receiver(function(msg, reply) {
log.console("Received:", msg)
// Process message...
}
})
```
**Key properties:**
@@ -177,11 +182,11 @@ $time_limit(my_requestor, 10) // 10 second timeout
## Module Resolution
When you call `use('name')`, Cell searches:
When you call `use('name')`, ƿit searches:
1. **Current package** — files relative to package root
2. **Dependencies** — packages declared in `cell.toml`
3. **Core** — built-in Cell modules
2. **Dependencies** — packages declared in `pit.toml`
3. **Core** — built-in ƿit modules
```javascript
// From within package 'myapp':

View File

@@ -1,6 +1,11 @@
# Writing C Modules
---
title: "Writing C Modules"
description: "Extending ƿit with native code"
weight: 50
type: "docs"
---
Cell makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
ƿit makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
## Basic Structure
@@ -45,12 +50,12 @@ Where:
- `<filename>` is the C file name without extension
Examples:
- `mypackage/math.c` `js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c` `js_gitea_pockle_world_john_lib_render_use`
- `mypackage/math.c` -> `js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use`
## Required Headers
Include `cell.h` for all Cell integration:
Include `cell.h` for all ƿit integration:
```c
#include "cell.h"
@@ -63,7 +68,7 @@ This provides:
## Conversion Functions
### JavaScript C
### JavaScript <-> C
```c
// Numbers
@@ -201,7 +206,7 @@ static const JSCFunctionListEntry js_funcs[] = {
CELL_USE_FUNCS(js_funcs)
```
Usage in Cell:
Usage in ƿit:
```javascript
var vector = use('vector')
@@ -211,7 +216,7 @@ var n = vector.normalize(3, 4) // {x: 0.6, y: 0.8}
var d = vector.dot(1, 0, 0, 1) // 0
```
## Combining C and Cell
## Combining C and ƿit
A common pattern is to have a C file provide low-level functions and a `.cm` file provide a higher-level API:
@@ -244,11 +249,11 @@ return Vector
C files are automatically compiled when you run:
```bash
cell build
cell update
pit build
pit update
```
The resulting dynamic library is placed in `~/.cell/lib/`.
The resulting dynamic library is placed in `~/.pit/lib/`.
## Platform-Specific Code
@@ -260,7 +265,7 @@ audio_playdate.c # Playdate
audio_emscripten.c # Web/Emscripten
```
Cell selects the appropriate file based on the target platform.
ƿit selects the appropriate file based on the target platform.
## Static Declarations

View File

@@ -1,138 +1,143 @@
# Command Line Interface
---
title: "Command Line Interface"
description: "The pit tool"
weight: 40
type: "docs"
---
Cell provides a command-line interface for managing packages, running scripts, and building applications.
ƿit provides a command-line interface for managing packages, running scripts, and building applications.
## Basic Usage
```bash
cell <command> [arguments]
pit <command> [arguments]
```
## Commands
### cell version
### pit version
Display the Cell version.
Display the ƿit version.
```bash
cell version
pit version
# 0.1.0
```
### cell install
### pit install
Install a package to the shop.
```bash
cell install gitea.pockle.world/john/prosperon
cell install /Users/john/local/mypackage # local path
pit install gitea.pockle.world/john/prosperon
pit install /Users/john/local/mypackage # local path
```
### cell update
### pit update
Update packages from remote sources.
```bash
cell update # update all packages
cell update <package> # update specific package
pit update # update all packages
pit update <package> # update specific package
```
### cell remove
### pit remove
Remove a package from the shop.
```bash
cell remove gitea.pockle.world/john/oldpackage
pit remove gitea.pockle.world/john/oldpackage
```
### cell list
### pit list
List installed packages.
```bash
cell list # list all installed packages
cell list <package> # list dependencies of a package
pit list # list all installed packages
pit list <package> # list dependencies of a package
```
### cell ls
### pit ls
List modules and actors in a package.
```bash
cell ls # list files in current project
cell ls <package> # list files in specified package
pit ls # list files in current project
pit ls <package> # list files in specified package
```
### cell build
### pit build
Build the current package.
```bash
cell build
pit build
```
### cell test
### pit test
Run tests.
```bash
cell test # run tests in current package
cell test all # run all tests
cell test <package> # run tests in specific package
pit test # run tests in current package
pit test all # run all tests
pit test <package> # run tests in specific package
```
### cell link
### pit link
Manage local package links for development.
```bash
cell link add <canonical> <local_path> # link a package
cell link list # show all links
cell link delete <canonical> # remove a link
cell link clear # remove all links
pit link add <canonical> <local_path> # link a package
pit link list # show all links
pit link delete <canonical> # remove a link
pit link clear # remove all links
```
### cell fetch
### pit fetch
Fetch package sources without extracting.
```bash
cell fetch <package>
pit fetch <package>
```
### cell upgrade
### pit upgrade
Upgrade the Cell installation itself.
Upgrade the ƿit installation itself.
```bash
cell upgrade
pit upgrade
```
### cell clean
### pit clean
Clean build artifacts.
```bash
cell clean
pit clean
```
### cell help
### pit help
Display help information.
```bash
cell help
cell help <command>
pit help
pit help <command>
```
## Running Scripts
Any `.ce` file in the Cell core can be run as a command:
Any `.ce` file in the ƿit core can be run as a command:
```bash
cell version # runs version.ce
cell build # runs build.ce
cell test # runs test.ce
pit version # runs version.ce
pit build # runs build.ce
pit test # runs test.ce
```
## Package Locators
@@ -143,16 +148,16 @@ Packages are identified by locators:
- **Local**: `/absolute/path/to/package`
```bash
cell install gitea.pockle.world/john/prosperon
cell install /Users/john/work/mylib
pit install gitea.pockle.world/john/prosperon
pit install /Users/john/work/mylib
```
## Configuration
Cell stores its data in `~/.cell/`:
ƿit stores its data in `~/.pit/`:
```
~/.cell/
~/.pit/
├── packages/ # installed packages
├── lib/ # compiled dynamic libraries
├── build/ # build cache
@@ -163,7 +168,7 @@ Cell stores its data in `~/.cell/`:
## Environment
Cell reads the `HOME` environment variable to locate the shop directory.
ƿit reads the `HOME` environment variable to locate the shop directory.
## Exit Codes

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
# Cell
![image](wizard.png)
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
## Key Features
- **Actor Model** — isolated memory, message passing, no shared state
- **Immutability** — `stone()` makes values permanently frozen
- **Prototype Inheritance** — objects without classes
- **C Integration** — seamlessly extend with native code
- **Cross-Platform** — deploy to desktop, web, and embedded
## Quick Start
```javascript
// hello.ce - A simple actor
log.console("Hello, Cell!")
$stop()
```
```bash
cell hello
```
## Documentation
- [**Cell Language**](cellscript.md) — syntax, types, and built-in functions
- [**Actors and Modules**](actors.md) — the execution model
- [**Packages**](packages.md) — code organization and sharing
- [**Command Line**](cli.md) — the `cell` tool
- [**Writing C Modules**](c-modules.md) — native extensions
## Standard Library
- [text](library/text.md) — string manipulation
- [number](library/number.md) — numeric operations (functions are global: `floor()`, `max()`, etc.)
- [array](library/array.md) — array utilities
- [object](library/object.md) — object utilities
- [blob](library/blob.md) — binary data
- [time](library/time.md) — time and dates
- [math](library/math.md) — trigonometry and math
- [json](library/json.md) — JSON encoding/decoding
- [random](library/random.md) — random numbers
## Architecture
Cell programs are organized into **packages**. Each package contains:
- **Modules** (`.cm`) — return a value, cached and frozen
- **Actors** (`.ce`) — run independently, communicate via messages
- **C files** (`.c`) — compiled to native libraries
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
## Installation
```bash
# Clone and bootstrap
git clone https://gitea.pockle.world/john/cell
cd cell
make bootstrap
```
The Cell shop is stored at `~/.cell/`.

94
docs/kim.md Normal file
View File

@@ -0,0 +1,94 @@
---
title: "Kim Encoding"
description: "Compact character and count encoding"
weight: 80
type: "docs"
---
Kim is a character and count encoding designed by Douglas Crockford. It encodes Unicode characters and variable-length integers using continuation bytes. Kim is simpler and more compact than UTF-8 for most text.
## Continuation Bytes
The fundamental idea in Kim is the continuation byte:
```
C D D D D D D D
```
- **C** — continue bit. If 1, read another byte. If 0, this is the last byte.
- **D** (7 bits) — data bits.
To decode: shift the accumulator left by 7 bits, add the 7 data bits. If the continue bit is 1, repeat with the next byte. If 0, the value is complete.
To encode: take the value, emit 7 bits at a time from most significant to least significant, setting the continue bit on all bytes except the last.
## Character Encoding
Kim encodes Unicode codepoints directly as continuation byte sequences:
| Range | Bytes | Characters |
|-------|-------|------------|
| U+0000 to U+007F | 1 | ASCII |
| U+0080 to U+3FFF | 2 | First quarter of BMP |
| U+4000 to U+10FFFF | 3 | All other Unicode |
Unlike UTF-8, there is no need for surrogate pairs or escapement. Every Unicode character, including emoji and characters from extended planes, is encoded in at most 3 bytes.
### Examples
```
'A' (U+0041) → 41
'é' (U+00E9) → 81 69
'💩' (U+1F4A9) → 87 E9 29
```
## Count Encoding
Kim is also used for encoding counts (lengths, sizes). The same continuation byte format represents non-negative integers of arbitrary size:
| Range | Bytes |
|-------|-------|
| 0 to 127 | 1 |
| 128 to 16383 | 2 |
| 16384 to 2097151 | 3 |
## Comparison with UTF-8
| Property | Kim | UTF-8 |
|----------|-----|-------|
| ASCII | 1 byte | 1 byte |
| BMP (first quarter) | 2 bytes | 2-3 bytes |
| Full Unicode | 3 bytes | 3-4 bytes |
| Self-synchronizing | No | Yes |
| Sortable | No | Yes |
| Simpler to implement | Yes | No |
| Byte count for counts | Variable (7 bits/byte) | Not applicable |
Kim trades self-synchronization (the ability to find character boundaries from any position) for simplicity and compactness. In practice, Kim text is accessed sequentially, so self-synchronization is not needed.
## Usage in ƿit
Kim is used internally by blobs and by the Nota message format.
### In Blobs
The `blob.write_text` and `blob.read_text` functions use Kim to encode text into binary data:
```javascript
var blob = use('blob')
var b = blob.make()
blob.write_text(b, "hello") // Kim-encoded length + characters
stone(b)
var text = blob.read_text(b, 0) // "hello"
```
### In Nota
Nota uses Kim for two purposes:
1. **Counts** — array lengths, text lengths, blob sizes, record pair counts
2. **Characters** — text content within Nota messages
The preamble byte of each Nota value incorporates the first few bits of a Kim-encoded count, with the continue bit indicating whether more bytes follow.
See [Nota Format](#nota) for the full specification.

View File

@@ -1,6 +1,11 @@
# Cell Language
---
title: "ƿit Language"
description: "Syntax, types, operators, and built-in functions"
weight: 10
type: "docs"
---
Cell is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
ƿit is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
## Basics
@@ -13,7 +18,7 @@ def PI = 3.14159 // constant (cannot be reassigned)
### Data Types
Cell has six fundamental types:
ƿit has six fundamental types:
- **number** — DEC64 decimal floating point (no rounding errors)
- **text** — Unicode strings
@@ -49,7 +54,7 @@ null
["a", "b", "c"]
// Objects
{name: "cell", version: 1}
{name: "pit", version: 1}
{x: 10, y: 20}
```
@@ -108,7 +113,7 @@ while (condition) {
break
continue
return value
throw "error message"
disrupt
```
### Functions
@@ -266,7 +271,7 @@ log.error("problem") // error output
## Pattern Matching
Cell supports regex patterns in string functions, but not standalone regex objects.
ƿit supports regex patterns in string functions, but not standalone regex objects.
```javascript
text.search("hello world", /world/)
@@ -275,14 +280,32 @@ replace("hello", /l/g, "L")
## Error Handling
```javascript
try {
riskyOperation()
} catch (e) {
log.error(e)
}
ƿit uses `disrupt` and `disruption` for error handling. A `disrupt` signals that something went wrong. The `disruption` block attached to a function catches it.
throw "something went wrong"
```javascript
var safe_divide = function(a, b) {
if (b == 0) disrupt
return a / b
} disruption {
log.error("something went wrong")
}
```
If an actor has an uncaught error, it crashes.
`disrupt` is a bare keyword — it does not carry a value. The `disruption` block knows that something went wrong, but not what.
To test whether an operation disrupts:
```javascript
var should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
```
If an actor has an unhandled disruption, it crashes.

View File

@@ -1,10 +0,0 @@
nav:
- text.md
- number.md
- array.md
- object.md
- blob.md
- time.md
- math.md
- json.md
- random.md

22
docs/library/_index.md Normal file
View File

@@ -0,0 +1,22 @@
---
title: "Standard Library"
description: "ƿit standard library modules"
weight: 90
type: "docs"
---
ƿit includes a standard library of modules loaded with `use()`.
| Module | Description |
|--------|-------------|
| [text](/docs/library/text/) | String conversion and manipulation |
| [number](/docs/library/number/) | Numeric conversion and operations |
| [array](/docs/library/array/) | Array creation and manipulation |
| [object](/docs/library/object/) | Object creation and manipulation |
| [blob](/docs/library/blob/) | Binary data (bits, not bytes) |
| [time](/docs/library/time/) | Time constants and conversions |
| [math](/docs/library/math/) | Trigonometry, logarithms, roots |
| [json](/docs/library/json/) | JSON encoding and decoding |
| [random](/docs/library/random/) | Random number generation |
Most numeric functions (`floor`, `max`, `abs`, etc.) are global intrinsics and do not require `use`. See [Built-in Functions](/docs/functions/) for the full list.

View File

@@ -1,4 +1,9 @@
# array
---
title: "array"
description: "Array creation and manipulation"
weight: 30
type: "docs"
---
The `array` function and its methods handle array creation and manipulation.
@@ -60,7 +65,6 @@ Split text into grapheme clusters.
```javascript
array("hello") // ["h", "e", "l", "l", "o"]
array("👨‍👩‍👧") // ["👨‍👩‍👧"]
```
### array(text, separator)

View File

@@ -1,4 +1,9 @@
# blob
---
title: "blob"
description: "Binary data containers (bits, not bytes)"
weight: 50
type: "docs"
---
Blobs are binary large objects — containers of bits (not bytes). They're used for encoding data, messages, images, network payloads, and more.

View File

@@ -1,4 +1,9 @@
# json
---
title: "json"
description: "JSON encoding and decoding"
weight: 80
type: "docs"
---
JSON encoding and decoding.

View File

@@ -1,6 +1,11 @@
# math
---
title: "math"
description: "Trigonometry, logarithms, and roots"
weight: 70
type: "docs"
---
Cell provides three math modules with identical functions but different angle representations:
ƿit provides three math modules with identical functions but different angle representations:
```javascript
var math = use('math/radians') // angles in radians
@@ -35,7 +40,7 @@ math.tangent(math.pi / 4) // 1 (radians)
Inverse sine.
```javascript
math.arc_sine(1) // π/2 (radians)
math.arc_sine(1) // pi/2 (radians)
```
### arc_cosine(n)
@@ -43,7 +48,7 @@ math.arc_sine(1) // π/2 (radians)
Inverse cosine.
```javascript
math.arc_cosine(0) // π/2 (radians)
math.arc_cosine(0) // pi/2 (radians)
```
### arc_tangent(n, denominator)
@@ -51,9 +56,9 @@ math.arc_cosine(0) // π/2 (radians)
Inverse tangent. With two arguments, computes atan2.
```javascript
math.arc_tangent(1) // π/4 (radians)
math.arc_tangent(1, 1) // π/4 (radians)
math.arc_tangent(-1, -1) // -3π/4 (radians)
math.arc_tangent(1) // pi/4 (radians)
math.arc_tangent(1, 1) // pi/4 (radians)
math.arc_tangent(-1, -1) // -3pi/4 (radians)
```
## Exponentials and Logarithms
@@ -64,7 +69,7 @@ Euler's number raised to a power. Default power is 1.
```javascript
math.e() // 2.718281828...
math.e(2) // e²
math.e(2) // e^2
```
### ln(n)

View File

@@ -1,4 +1,9 @@
# number
---
title: "number"
description: "Numeric conversion and operations"
weight: 20
type: "docs"
---
The `number` function and its methods handle numeric conversion and operations.

View File

@@ -1,4 +1,9 @@
# object
---
title: "object"
description: "Object creation and manipulation"
weight: 40
type: "docs"
---
The `object` function and related utilities handle object creation and manipulation.

View File

@@ -1,4 +1,9 @@
# random
---
title: "random"
description: "Random number generation"
weight: 90
type: "docs"
---
Random number generation.

View File

@@ -1,4 +1,9 @@
# text
---
title: "text"
description: "String conversion and manipulation"
weight: 10
type: "docs"
---
The `text` function and its methods handle string conversion and manipulation.
@@ -101,7 +106,7 @@ text.format("{0} + {1} = {2}", [1, 2, 3])
Unicode normalize the text (NFC form).
```javascript
text.normalize("café") // normalized form
text.normalize("cafe\u0301") // normalized form
```
### text.codepoint(text)
@@ -110,7 +115,6 @@ Get the Unicode codepoint of the first character.
```javascript
text.codepoint("A") // 65
text.codepoint("😀") // 128512
```
### text.extract(text, pattern, from, to)

View File

@@ -1,4 +1,9 @@
# time
---
title: "time"
description: "Time constants and conversion functions"
weight: 60
type: "docs"
---
The time module provides time constants and conversion functions.

View File

@@ -1,248 +0,0 @@
# Cell actor scripting language
Cell is a Misty [https://mistysystem.com](https://mistysystem.com) implementation.
## Memory
Values are 32 bit for 32 bit builds and 64 bit for 64 bit builds.
### 32 bit value
LSB = 0
payload is a 31 bit signed int
LSB = 01
payload is a 30 bit pointer
LSB = 11
next 3 bits = special tag. 27 bits of payload.
### 64 bit value
LSB = 0
payload is a 32 bit signed int, using high 32 bits
LSB = 01
payload is a 61 bit pointer
LSB = 101
Short float: a 61 bit double, with 3 less exponent bits
LSB = 11
Special tag: next 3 bits. 5 bits total. 59 bits of payload. 8 total special tags.
Special tags:
1: Bool. Payload is 0 or 1.
2: null. payload is 0.
3: exception.
4: string.
Immediate string. Next 3 low bits = length in bytes. Rest is string data. This allows for strings up to 7 ascii letters. Encoded in utf8.
## Numbers and math
Cell can be compiled with different levels of exactness for numeracy. Any number which cannot be represented exactly becomes "null". Any numeric operation which includes "null" results in "null".
Using short floats in a 64 bit system means you have doubles in the range of +- 10^38, not the full range of double. If you create a number out of that range, it's null.
You can also compile a 64 bit system with full precision doubles, but this will use more memory and may be slower.
You can also compile a 64 bit system with 32 bit floats, stored as a 32 bit int is. Again, out of the 32 bit float range = null.
You can compile without floating point support at all; 32 bit ints are then used for fixed point calculations.
Or, you can compile using Dec64, which is a 64 bit decimal floating point format, for exact precision.
## Objects
Objects are heap allocated, referenced by a pointer value. They are all preceded by an object header, the length of a word on the system.
### 64 bit build
56 bits capacity
1 bit memory reclamation flag: note that this obj has already been moved
2 bit reserved (per object)
1 bit stone: note that this obj is immutable
3 bit type: note the type of the object
1 bit: fwd: note that this obj is a forward linkage
Last bit ..1:
The forward type indicates that the object (an array, blob, pretext, or record) has grown beyond its capacity and is now residing at a new address. The remaining 63 bits contain the address of the enlarged object. Forward linkages are cleaned up by the memory reclaimer.
Type 7: C light C object
Header
Pointer
Capacity is an ID of a registered C type.
Pointer is a pointer to the opaque C object.
Type 0: Array
Header
Length
Element[]
Capacity is number of elements the array can hold. Length is number of elements in use. Number of words used by an array is capacity + 2.
Type 1: blob
Header
Length
Bit[]
Capacity is number of bits the blob can hold. Length is number of bits in use. Bits follow, from [0] to [capacity - 1], with [0] bit in the most significant position of word 2, and [63] in the least significant position of word 2. The last word is zero filled, if necessary.
Number of words used is (capacity + 63) // 64 + 2
Type 2: Text
Text has two forms, depending on if it is stone or not, which changes the meaning of its length word.
Header
Length(pretext) or Hash(text)
Character[0] and character[1]
Capacity of pretex is the number of characters it can hold. During stoning and reclamation, capacity is set to the length.
The capacity of a text is its length.
The length of a pretext is the number of characters it contains; it is not greater than the capacity.
Hash of a text is used for organizing records. If the hash is zero, it's not been computed yet. All texts in the immutable memory have hashes.
A text object contains UTF32 characters, packed two per word. If the number of characters is odd, the least significant half of the last word is zero filled.
The number of words used by a text is (capacity + 1) // 2 + 2
Type 3: Record
A record is an array of fields represented as key/value pairs. Fields are located by hashes of texts, using open addressing with linear probing and lazy deletion. The load factor is less than 0.5.
Header
Prototype
Length
Key[0]
Value[0]
Key[1]
Value[1]
...
The capacity is the number of fields the record can hold. It is a power of two minus one. It is at least twice the length.
The length is the number of fields that the record currently contains.
A field candidate number is identified by and(key.hash, capacity). In case of hash collision, advance to the next field. If this goes past the end, continue with field 1. Field 0 is reserved.
The "exception" special tag is used to mark deleted entries in the object map.
The number of words used by a record is (capacity + 1) * 2.
Prototypes are searched for for properties if one cannot be found on the record itself. Prototypes can have prototypes.
#### key[0] and value[0]
These are reserved for internal use, and skipped over during key probing.
The first 32 bits of key are used as a 32 bit integer key, if this object has ever been used as a key itself.
The last 32 bits are used as an opaque C class key. C types can be registered with the system, and each are assigned a monotonically increasing number. In the case that this object has a C type, then the bottom 32 bits of key[0] are not 0. If that is the case, then a pointer to its C object is stored in value[0].
#### Valid keys & Hashing
Keys are stored directly in object maps. There are three possibilities for a vaild key: an object text, an object record, or an immediate text.
In the case of an immediate text, the hash is computed on the fly using the fash64_hash_one function, before being used to look up the key in the object map. Direct value comparison is used to confirm the key.
For object texts (texts longer than 7 ascii chars), the hash is stored in the text object itself. When an object text is used as a key, a stone version is created and interned. Any program static texts reference this stoned, interned text. When looking up a heap text as a key, it is first discovered if it's in the interned table. If it's not, the key is not in the object (since all keys are interned). If it is, the interned version is returned to check against the object map. The hash of the interned text is used to look up the key in the object map, and then direct pointer comparison is used to confirm the key.
For record keys, these are unique; once a record is used as a key, it gets assigned a monotonically increasing 32 bit integer, stored in key[0]. When checking it in an object map, the integer is used directly as the key. If key[0] is 0, the record has not been used as a key yet. If it's not 0, fash64_hash_one is used to compute a hash of its ID, and then direct value pointer comparison is used to confirm.
### Text interning
Texts that cannot fit in an immediate, and which are used as an object key, create a stoned and interned version (the pointer which is used as the key). Any text literals are also stoned and interned.
The interning table is an open addressed hash, with a load of 0.8, using a robin hood value. Probing is done using the text hash, confirmation is done using length, and then memcmp of the text.
When the GC run, a new interned text table is created. Each text literal, and each text used as a key, is added to the new table, as the live objects are copied. This keeps the interning table from becoming a graveyard. Interned values are never deleted until a GC.
Type 4: Function
Header
Code
Outer
A function object has zero capacity and is always stone.
Code is a pointer to the code object that the function executes.
Outer is a pointer to the frame that created this function object.
Size is 3 words.
Type 5: Frame
Header
Function
Caller
Return address
The activation frame is created when a function is invoked to hold its linkages and state.
The capacity is the number of slots, including the inputs, variables, temporaries, and the four words of overhead. A frame, unlike the other types, is never stone.
The function is the address of the function object being called.
The caller is the address of the frame that is invoking the function.
The return address is the address of the instruction in the code that should be executed upon return.
Next come the input arguments, if any.
Then the variables closed over by the inner functions.
Then the variables that are not closed over, followed by the temporaries.
When a function returns, the caller is set to zero. This is a signal to the memory reclaimer that the frame can be reduced.
Type 6: Code
Header
Arity
Size
Closure size
Entry point
Disruption point
A code object exists in the actor's immutable memory. A code object never exists in mutable memory.
A code object has a zero capacity and is always stone.
The arity is the maximum number of inputs.
The size is the capacity of an activation frame that will execute this code.
The closure size is a reduced capacity for returned frames that survive memory reclamation.
The entry point is the address at which to begin execution.
The disruption point is the address of the disruption clause.
### opaque C objects
Records can have opaque C data attached to them.
A C class can register a GC clean up, and a GC trace function. The trace function is called when the record is encountered in the live object graph; and it should mark any values it wants to keep alive in that function.
The system maintains an array of live opaque C objects. When such an object is encountered, it marks it as live in the array. When the GC completes, it iterates this array and calls the GC clean up function for each C object in the array with alive=0. Alive is then cleared for the next GC cycle.
## 32 bit build
~3 bit type
1 bit stone
1 bit memory reclamation flag
27 bit capacity
Key differences here are
blob max capacity is 2**27 bits = 2**24 bytes = 16 MB [this likely needs addressed]
fwd is type ...0, and the pointer is 31 bits
other types are
111 array
101 object
011 blob
001
## Memory
Cell uses a single block of memory that it doles out as needed to the actors in its system.
Actors are given a block of memory in standard sizes using a doubling buddy memory manager. An actor is given an immutable data section on birth, as well as a mutable data section. When its mutable data becomes full, it requests a new one. Actors utilize their mutable memory with a simple bump allocation. If there is not sufficient memory available, the actor suspends and its status changes to exhausted.
The smallest block size is determined per platform, but it can be as small as 4KB on 64 bit systems.
The actor is then given a new block of memory of the same size, and it runs a garbage collector to reclaim memory. It uses the cheney copying algorithm. If a disappointing amount of memory was reclaimed, it is noted, and the actor is given a larger block of memory on the next request.

156
docs/nota.md Normal file
View File

@@ -0,0 +1,156 @@
---
title: "Nota Format"
description: "Network Object Transfer Arrangement"
weight: 85
type: "docs"
---
Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols.
Nota stands for Network Object Transfer Arrangement.
## Design Philosophy
JSON had three design rules: minimal, textual, and subset of JavaScript. The textual and JavaScript rules are no longer necessary. Nota maintains JSON's philosophy of being at the intersection of most programming languages and most data types, but departs by using counts instead of brackets and binary encoding instead of text.
Nota uses Kim continuation bytes for counts and character encoding. See [Kim Encoding](#kim) for details.
## Type Summary
| Bits | Type |
|------|------|
| `000` | Blob |
| `001` | Text |
| `010` | Array |
| `011` | Record |
| `100` | Floating Point (positive exponent) |
| `101` | Floating Point (negative exponent) |
| `110` | Integer (zero exponent) |
| `111` | Symbol |
## Preambles
Every Nota value starts with a preamble byte that is a Kim value with the three most significant bits used for type information.
Most types provide 3 or 4 data bits in the preamble. If the Kim encoding of the data fits in those bits, it is incorporated directly and the continue bit is off. Otherwise the continue bit is on and the continuation follows.
## Blob
```
C 0 0 0 D D D D
```
- **C** — continue the number of bits
- **DDDD** — the number of bits
A blob is a string of bits. The data produces the number of bits. The number of bytes that follow: `floor((number_of_bits + 7) / 8)`. The final byte is padded with 0 if necessary.
Example: A blob containing 25 bits `1111000011100011001000001`:
```
80 19 F0 E3 20 80
```
## Text
```
C 0 0 1 D D D D
```
- **C** — continue the number of characters
- **DDDD** — the number of characters
The data produces the number of characters. Kim-encoded characters follow. ASCII characters are 1 byte, first quarter BMP characters are 2 bytes, all other Unicode characters are 3 bytes. Unlike JSON, there is never a need for escapement.
Examples:
```
"" → 10
"cat" → 13 63 61 74
```
## Array
```
C 0 1 0 D D D D
```
- **C** — continue the number of elements
- **DDDD** — the number of elements
An array is an ordered sequence of values. Following the preamble are the elements, each beginning with its own preamble. Nesting is encouraged.
## Record
```
C 0 1 1 D D D D
```
- **C** — continue the number of pairs
- **DDDD** — the number of pairs
A record is an unordered collection of key/value pairs. Keys must be text and must be unique within the record. Values can be any Nota type.
## Floating Point
```
C 1 0 E S D D D
```
- **C** — continue the exponent
- **E** — sign of the exponent
- **S** — sign of the coefficient
- **DDD** — three bits of the exponent
Nota floating point represents numbers as `coefficient * 10^exponent`. The coefficient must be an integer. The preamble may contain the first three bits of the exponent, followed by the continuation of the exponent (if any), followed by the coefficient.
Use the integer type when the exponent is zero.
Examples:
```
-1.01 → 5A 65
98.6 → 51 87 5A
-0.5772156649 → D8 0A 95 C0 B0 BD 69
-10000000000000 → C8 0D 01
```
## Integer
```
C 1 1 0 S D D D
```
- **C** — continue the integer
- **S** — sign
- **DDD** — three bits of the integer
Integers in the range -7 to 7 fit in a single byte. Integers in the range -1023 to 1023 fit in two bytes. Integers in the range -131071 to 131071 fit in three bytes.
Examples:
```
0 → 60
2023 → E0 8F 67
-1 → 69
```
## Symbol
```
0 1 1 1 D D D D
```
- **DDDD** — the symbol
There are currently five symbols:
```
null → 70
false → 72
true → 73
private → 78
system → 79
```
The private prefix must be followed by a record containing a private process address. The system prefix must be followed by a record containing a system message. All other symbols are reserved.

View File

@@ -1,14 +1,19 @@
# Packages
---
title: "Packages"
description: "Code organization and sharing in ƿit"
weight: 30
type: "docs"
---
Packages are the fundamental unit of code organization and sharing in Cell.
Packages are the fundamental unit of code organization and sharing in ƿit.
## Package Structure
A package is a directory containing a `cell.toml` manifest:
A package is a directory containing a `pit.toml` manifest:
```
mypackage/
├── cell.toml # package manifest
├── pit.toml # package manifest
├── main.ce # entry point (optional)
├── utils.cm # module
├── helper/
@@ -17,7 +22,7 @@ mypackage/
└── _internal.cm # private module (underscore prefix)
```
## cell.toml
## pit.toml
The package manifest declares metadata and dependencies:
@@ -38,11 +43,11 @@ mylib = "/Users/john/work/mylib"
## Module Resolution
When importing with `use()`, Cell searches in order:
When importing with `use()`, ƿit searches in order:
1. **Local package** — relative to package root
2. **Dependencies** — via aliases in `cell.toml`
3. **Core** — built-in Cell modules
2. **Dependencies** — via aliases in `pit.toml`
3. **Core** — built-in ƿit modules
```javascript
// In package 'myapp' with dependency: renderer = "gitea.pockle.world/john/renderer"
@@ -85,10 +90,10 @@ Local packages are symlinked into the shop, making development seamless.
## The Shop
Cell stores all packages in the **shop** at `~/.cell/`:
ƿit stores all packages in the **shop** at `~/.pit/`:
```
~/.cell/
~/.pit/
├── packages/
│ ├── core -> gitea.pockle.world/john/cell
│ ├── gitea.pockle.world/
@@ -134,20 +139,20 @@ target = "/Users/john/work/prosperon"
```bash
# Install from remote
cell install gitea.pockle.world/john/prosperon
pit install gitea.pockle.world/john/prosperon
# Install from local path
cell install /Users/john/work/mylib
pit install /Users/john/work/mylib
```
## Updating Packages
```bash
# Update all
cell update
pit update
# Update specific package
cell update gitea.pockle.world/john/prosperon
pit update gitea.pockle.world/john/prosperon
```
## Development Workflow
@@ -156,12 +161,12 @@ For active development, link packages locally:
```bash
# Link a package for development
cell link add gitea.pockle.world/john/prosperon /Users/john/work/prosperon
pit link add gitea.pockle.world/john/prosperon /Users/john/work/prosperon
# Changes to /Users/john/work/prosperon are immediately visible
# Remove link when done
cell link delete gitea.pockle.world/john/prosperon
pit link delete gitea.pockle.world/john/prosperon
```
## C Extensions
@@ -170,14 +175,14 @@ C files in a package are compiled into a dynamic library:
```
mypackage/
├── cell.toml
├── pit.toml
├── render.c # compiled to mypackage.dylib
└── render.cm # optional Cell wrapper
└── render.cm # optional ƿit wrapper
```
The library is named after the package and placed in `~/.cell/lib/`.
The library is named after the package and placed in `~/.pit/lib/`.
See [Writing C Modules](c-modules.md) for details.
See [Writing C Modules](/docs/c-modules/) for details.
## Platform-Specific Files
@@ -190,4 +195,4 @@ mypackage/
└── audio_emscripten.c # Web-specific
```
Cell selects the appropriate file based on the build target.
ƿit selects the appropriate file based on the build target.

176
docs/requestors.md Normal file
View File

@@ -0,0 +1,176 @@
---
title: "Requestors"
description: "Asynchronous work with requestors"
weight: 25
type: "docs"
---
Requestors are functions that encapsulate asynchronous work. They provide a structured way to compose callbacks, manage cancellation, and coordinate concurrent operations between actors.
## What is a Requestor
A requestor is a function with this signature:
```javascript
function my_requestor(callback, value) {
// Do async work, then call callback with result
// Return a cancel function
}
```
- **callback** — called when the work completes: `callback(value, reason)`
- On success: `callback(result)` or `callback(result, null)`
- On failure: `callback(null, reason)` where reason explains the failure
- **value** — input passed from the previous step (or the initial caller)
- **return** — a cancel function, or null if cancellation is not supported
The cancel function, when called, should abort the in-progress work.
## Writing a Requestor
```javascript
function fetch_data(callback, url) {
$contact(function(connection) {
$send(connection, {get: url}, function(response) {
callback(response)
})
}, {host: url, port: 80})
return function cancel() {
// clean up if needed
}
}
```
A requestor that always succeeds immediately:
```javascript
function constant(callback, value) {
callback(42)
}
```
A requestor that always fails:
```javascript
function broken(callback, value) {
callback(null, "something went wrong")
}
```
## Composing Requestors
ƿit provides four built-in functions for composing requestors into pipelines.
### sequence(requestor_array)
Run requestors one after another. Each result becomes the input to the next. The final result is passed to the callback.
```javascript
var pipeline = sequence([
fetch_user,
validate_permissions,
load_profile
])
pipeline(function(profile, reason) {
if (reason) {
log.error(reason)
} else {
log.console(profile.name)
}
}, user_id)
```
If any step fails, the remaining steps are skipped and the failure propagates.
### parallel(requestor_array, throttle, need)
Start all requestors concurrently. Results are collected into an array matching the input order.
```javascript
var both = parallel([
fetch_profile,
fetch_settings
])
both(function(results, reason) {
var profile = results[0]
var settings = results[1]
}, user_id)
```
- **throttle** — limit how many requestors run at once (null for no limit)
- **need** — minimum number of successes required (default: all)
### race(requestor_array, throttle, need)
Like `parallel`, but returns as soon as the needed number of results arrive. Unfinished requestors are cancelled.
```javascript
var fastest = race([
fetch_from_cache,
fetch_from_network,
fetch_from_backup
])
fastest(function(results) {
// results[0] is whichever responded first
}, request)
```
Default need is 1. Useful for redundant operations where only one result matters.
### fallback(requestor_array)
Try each requestor in order. If one fails, try the next. Return the first success.
```javascript
var resilient = fallback([
fetch_from_primary,
fetch_from_secondary,
use_cached_value
])
resilient(function(data, reason) {
if (reason) {
log.error("all sources failed")
}
}, key)
```
## Timeouts
Wrap any requestor with `$time_limit` to add a timeout:
```javascript
var timed = $time_limit(fetch_data, 5) // 5 second timeout
timed(function(result, reason) {
// reason will explain timeout if it fires
}, url)
```
If the requestor does not complete within the time limit, it is cancelled and the callback receives a failure.
## Requestors and Actors
Requestors are particularly useful with actor messaging. Since `$send` is callback-based, it fits naturally:
```javascript
function ask_worker(callback, task) {
$send(worker, task, function(reply) {
callback(reply)
})
}
var pipeline = sequence([
ask_worker,
process_result,
store_result
])
pipeline(function(stored) {
log.console("done")
$stop()
}, {type: "compute", data: [1, 2, 3]})
```

118
docs/spec/bytecode.md Normal file
View File

@@ -0,0 +1,118 @@
---
title: "Bytecode VM"
description: "Stack-based virtual machine"
---
## Overview
The bytecode VM is a stack-based virtual machine. Instructions operate on an implicit operand stack, pushing and popping values. This is the original execution backend for ƿit.
## Compilation Pipeline
```
Source → Tokenize → Parse (AST) → Bytecode → Link → Execute
```
The compiler emits `JSFunctionBytecode` objects containing opcode sequences, constant pools, and debug information.
## Instruction Categories
### Value Loading
| Opcode | Description |
|--------|-------------|
| `push_i32` | Push a 32-bit immediate integer |
| `push_const` | Push a value from the constant pool |
| `null` | Push null |
| `push_false` | Push false |
| `push_true` | Push true |
### Stack Manipulation
| Opcode | Description |
|--------|-------------|
| `drop` | Remove top of stack |
| `dup` | Duplicate top of stack |
| `dup1` / `dup2` / `dup3` | Duplicate item at depth |
| `swap` | Swap top two items |
| `rot3l` / `rot3r` | Rotate top three items |
| `insert2` / `insert3` | Insert top item deeper |
| `nip` | Remove second item |
### Variable Access
| Opcode | Description |
|--------|-------------|
| `get_var` | Load variable by name (pre-link) |
| `put_var` | Store variable by name (pre-link) |
| `get_loc` / `put_loc` | Access local variable by index |
| `get_arg` / `put_arg` | Access function argument by index |
| `get_env_slot` / `set_env_slot` | Access closure variable (post-link) |
| `get_global_slot` / `set_global_slot` | Access global variable (post-link) |
Variable access opcodes are patched during linking. `get_var` instructions are rewritten to `get_loc`, `get_env_slot`, or `get_global_slot` depending on where the variable is resolved.
### Arithmetic
| Opcode | Description |
|--------|-------------|
| `add` / `sub` / `mul` / `div` | Basic arithmetic |
| `mod` / `pow` | Modulo and power |
| `neg` / `inc` / `dec` | Unary operations |
| `add_loc` / `inc_loc` / `dec_loc` | Optimized local variable update |
### Comparison and Logic
| Opcode | Description |
|--------|-------------|
| `strict_eq` / `strict_neq` | Equality (ƿit uses strict only) |
| `lt` / `lte` / `gt` / `gte` | Ordered comparison |
| `not` / `lnot` | Logical / bitwise not |
| `and` / `or` / `xor` | Bitwise operations |
### Control Flow
| Opcode | Description |
|--------|-------------|
| `goto` | Unconditional jump |
| `if_true` / `if_false` | Conditional jump |
| `goto8` / `goto16` | Short jumps (size-optimized) |
| `if_true8` / `if_false8` | Short conditional jumps |
| `catch` | Set exception handler |
### Function Calls
| Opcode | Description |
|--------|-------------|
| `call` | Call function with N arguments |
| `tail_call` | Tail-call optimization |
| `call_method` | Call method on object |
| `return` | Return value from function |
| `return_undef` | Return null from function |
| `throw` | Throw exception (disrupt) |
### Property Access
| Opcode | Description |
|--------|-------------|
| `get_field` | Get named property |
| `put_field` | Set named property |
| `get_array_el` | Get computed property |
| `put_array_el` | Set computed property |
| `define_field` | Define property during object literal |
### Object Creation
| Opcode | Description |
|--------|-------------|
| `object` | Create new empty object |
| `array_from` | Create array from stack values |
## Bytecode Patching
During the link/integrate phase, symbolic variable references are resolved to concrete access instructions. This is a critical optimization — the interpreter does not perform name lookups at runtime.
A `get_var "x"` instruction becomes:
- `get_loc 3` — if x is local variable at index 3
- `get_env_slot 1, 5` — if x is captured from outer scope (depth 1, slot 5)
- `get_global_slot 7` — if x is a global

77
docs/spec/dec64.md Normal file
View File

@@ -0,0 +1,77 @@
---
title: "DEC64 Numbers"
description: "Decimal floating point representation"
---
## Overview
ƿit uses DEC64 as its number format. DEC64 represents numbers as `coefficient * 10^exponent` in a 64-bit word. This eliminates the rounding errors that plague IEEE 754 binary floating point — `0.1 + 0.2` is exactly `0.3`.
DEC64 was designed by Douglas Crockford as a general-purpose number type suitable for both business and scientific computation.
## Format
A DEC64 number is a 64-bit value:
```
[coefficient: 56 bits][exponent: 8 bits]
```
- **Coefficient** — a 56-bit signed integer (two's complement)
- **Exponent** — an 8-bit signed integer (range: -127 to 127)
The value of a DEC64 number is: `coefficient * 10^exponent`
### Examples
| Value | Coefficient | Exponent | Hex |
|-------|------------|----------|-----|
| `0` | 0 | 0 | `0000000000000000` |
| `1` | 1 | 0 | `0000000000000100` |
| `3.14159` | 314159 | -5 | `000000004CB2FFFB` |
| `-1` | -1 | 0 | `FFFFFFFFFFFFFF00` |
| `1000000` | 1 | 6 | `0000000000000106` |
## Special Values
### Null
The exponent `0x80` (-128) indicates null. This is the only special value — there is no infinity, no NaN, no negative zero. Operations that would produce undefined results (such as division by zero) return null.
```
coefficient: any, exponent: 0x80 → null
```
## Arithmetic Properties
- **Exact decimals**: All decimal fractions with up to 17 significant digits are represented exactly
- **No rounding**: `0.1 + 0.2 == 0.3` is true
- **Integer range**: Exact integers up to 2^55 (about 3.6 * 10^16)
- **Normalized on demand**: The runtime normalizes coefficients to remove trailing zeros when needed for comparison
## Comparison with IEEE 754
| Property | DEC64 | IEEE 754 double |
|----------|-------|----------------|
| Decimal fractions | Exact | Approximate |
| Significant digits | ~17 | ~15-16 |
| Special values | null only | NaN, ±Infinity, -0 |
| Rounding errors | None (decimal) | Common |
| Financial arithmetic | Correct | Requires libraries |
| Scientific range | ±10^127 | ±10^308 |
DEC64 trades a smaller exponent range for exact decimal arithmetic. Most applications never need exponents beyond ±127.
## In ƿit
All numbers in ƿit are DEC64. There is no separate integer type at the language level — the distinction is internal. The `is_integer` function checks whether a number has no fractional part.
```javascript
var x = 42 // coefficient: 42, exponent: 0
var y = 3.14 // coefficient: 314, exponent: -2
var z = 1000000 // coefficient: 1, exponent: 6 (normalized)
is_integer(x) // true
is_integer(y) // false
1 / 0 // null
```

82
docs/spec/gc.md Normal file
View File

@@ -0,0 +1,82 @@
---
title: "Garbage Collection"
description: "Cheney copying collector"
---
## Overview
ƿit uses a Cheney copying collector for automatic memory management. Each actor has its own independent heap — actors never share mutable memory, so garbage collection is per-actor with no global pauses.
## Algorithm
The Cheney algorithm is a two-space copying collector:
1. **Allocate new space** — a fresh memory block for the new heap
2. **Copy roots** — copy all live root objects from old space to new space
3. **Scan** — walk the new space, updating all internal references
4. **Free old space** — the entire old heap is freed at once
### Copying and Forwarding
When an object is copied from old space to new space:
1. The object's data is copied to the next free position in new space
2. The old object's header is overwritten with a **forwarding pointer** (`OBJ_FORWARD`) containing the new address
3. Future references to the old address find the forwarding pointer and follow it to the new location
```
Old space: New space:
┌──────────────┐ ┌──────────────┐
│ OBJ_FORWARD ─┼────────> │ copied object│
│ (new addr) │ │ │
└──────────────┘ └──────────────┘
```
### Scan Phase
After roots are copied, the collector scans new space linearly. For each object, it examines every JSValue field:
- If the field points to old space, copy the referenced object (or follow its forwarding pointer if already copied)
- If the field points to stone memory, skip it (stone objects are permanent)
- If the field is an immediate value (integer, boolean, null, immediate string), skip it
The scan continues until the scan pointer catches up with the allocation pointer — at that point, all live objects have been found and copied.
## Roots
The collector traces from these root sources:
- **Global object** — all global variables
- **Class prototypes** — built-in type prototypes
- **Exception** — the current exception value
- **Value stack** — all values on the operand stack
- **Frame stack** — all stack frames (bytecode and register VM)
- **GC reference stack** — manually registered roots (via `JS_PUSH_VALUE` / `JS_POP_VALUE`)
- **Parser constant pool** — during compilation, constants being built
## Per-Actor Heaps
Each actor maintains its own heap with independent collection:
- No stop-the-world pauses across actors
- No synchronization between collectors
- Each actor's GC runs at the end of a turn (between message deliveries)
- Heap sizes adapt independently based on each actor's allocation patterns
## Heap Growth
The collector uses a buddy allocator for heap blocks. After each collection, if less than 20% of the heap was recovered, the next block size is doubled. The new space size is: `max(live_estimate + alloc_size, next_block_size)`.
All allocations within a heap block use bump allocation (advance a pointer), which is extremely fast.
## Alignment
All objects are aligned to 8-byte boundaries. Object sizes are rounded up to ensure this alignment, which guarantees that the low 3 bits of any heap pointer are always zero — available for JSValue tag bits.
## Interaction with Stone Memory
Stone memory objects (S bit set) are never copied by the collector. When the scanner encounters a pointer to stone memory, it leaves it unchanged. This means:
- Stone objects are effectively permanent GC roots
- No overhead for tracing through immutable object graphs
- Module return values and interned strings impose zero GC cost

156
docs/spec/mach.md Normal file
View File

@@ -0,0 +1,156 @@
---
title: "Register VM"
description: "Register-based virtual machine (Mach)"
---
## Overview
The Mach VM is a register-based virtual machine using 32-bit instructions. It is modeled after Lua's register VM — operands are register indices rather than stack positions, reducing instruction count and improving performance.
## Instruction Formats
All instructions are 32 bits wide. Four encoding formats are used:
### iABC — Three-Register
```
[op: 8][A: 8][B: 8][C: 8]
```
Used for operations on three registers: `R(A) = R(B) op R(C)`.
### iABx — Register + Constant
```
[op: 8][A: 8][Bx: 16]
```
Used for loading constants: `R(A) = K(Bx)`.
### iAsBx — Register + Signed Offset
```
[op: 8][A: 8][sBx: 16]
```
Used for conditional jumps: if `R(A)` then jump by `sBx`.
### isJ — Signed Jump
```
[op: 8][sJ: 24]
```
Used for unconditional jumps with a 24-bit signed offset.
## Registers
Each function frame has a fixed number of register slots, determined at compile time. Registers hold:
- **R(0)** — `this` binding
- **R(1)..R(arity)** — function arguments
- **R(arity+1)..** — local variables and temporaries
## Instruction Set
### Loading
| Opcode | Format | Description |
|--------|--------|-------------|
| `LOADK` | iABx | `R(A) = K(Bx)` — load from constant pool |
| `LOADI` | iAsBx | `R(A) = sBx` — load small integer |
| `LOADNULL` | iA | `R(A) = null` |
| `LOADTRUE` | iA | `R(A) = true` |
| `LOADFALSE` | iA | `R(A) = false` |
| `MOVE` | iABC | `R(A) = R(B)` — register copy |
### Arithmetic
| Opcode | Format | Description |
|--------|--------|-------------|
| `ADD` | iABC | `R(A) = R(B) + R(C)` |
| `SUB` | iABC | `R(A) = R(B) - R(C)` |
| `MUL` | iABC | `R(A) = R(B) * R(C)` |
| `DIV` | iABC | `R(A) = R(B) / R(C)` |
| `MOD` | iABC | `R(A) = R(B) % R(C)` |
| `POW` | iABC | `R(A) = R(B) ^ R(C)` |
| `NEG` | iABC | `R(A) = -R(B)` |
| `INC` | iABC | `R(A) = R(B) + 1` |
| `DEC` | iABC | `R(A) = R(B) - 1` |
### Comparison
| Opcode | Format | Description |
|--------|--------|-------------|
| `EQ` | iABC | `R(A) = R(B) == R(C)` |
| `NEQ` | iABC | `R(A) = R(B) != R(C)` |
| `LT` | iABC | `R(A) = R(B) < R(C)` |
| `LE` | iABC | `R(A) = R(B) <= R(C)` |
| `GT` | iABC | `R(A) = R(B) > R(C)` |
| `GE` | iABC | `R(A) = R(B) >= R(C)` |
### Property Access
| Opcode | Format | Description |
|--------|--------|-------------|
| `GETFIELD` | iABC | `R(A) = R(B)[K(C)]` — named property |
| `SETFIELD` | iABC | `R(A)[K(B)] = R(C)` — set named property |
| `GETINDEX` | iABC | `R(A) = R(B)[R(C)]` — computed property |
| `SETINDEX` | iABC | `R(A)[R(B)] = R(C)` — set computed property |
### Variable Resolution
| Opcode | Format | Description |
|--------|--------|-------------|
| `GETNAME` | iABx | Unresolved variable (compiler placeholder) |
| `GETINTRINSIC` | iABx | Global intrinsic / built-in |
| `GETENV` | iABx | Module environment variable |
| `GETUP` | iABC | `R(A) = UpFrame(B).slots[C]` — closure upvalue |
| `SETUP` | iABC | `UpFrame(A).slots[B] = R(C)` — set closure upvalue |
### Control Flow
| Opcode | Format | Description |
|--------|--------|-------------|
| `JMP` | isJ | Unconditional jump |
| `JMPTRUE` | iAsBx | Jump if `R(A)` is true |
| `JMPFALSE` | iAsBx | Jump if `R(A)` is false |
| `JMPNULL` | iAsBx | Jump if `R(A)` is null |
### Function Calls
| Opcode | Format | Description |
|--------|--------|-------------|
| `CALL` | iABC | Call `R(A)` with `B` args starting at `R(A+1)`, `C`=keep result |
| `RETURN` | iA | Return `R(A)` |
| `RETNIL` | — | Return null |
| `CLOSURE` | iABx | Create closure from function pool entry `Bx` |
### Object / Array
| Opcode | Format | Description |
|--------|--------|-------------|
| `NEWOBJECT` | iA | `R(A) = {}` |
| `NEWARRAY` | iABC | `R(A) = array(B)` |
| `PUSH` | iABC | Push `R(B)` to array `R(A)` |
## JSCodeRegister
The compiled output for a function:
```c
struct JSCodeRegister {
uint16_t arity; // argument count
uint16_t nr_slots; // total register count
uint32_t cpool_count; // constant pool size
JSValue *cpool; // constant pool
uint32_t instr_count; // instruction count
MachInstr32 *instructions; // 32-bit instruction array
uint32_t func_count; // nested function count
JSCodeRegister **functions; // nested function table
JSValue name; // function name
uint16_t disruption_pc; // exception handler offset
};
```
The constant pool holds all non-immediate values referenced by `LOADK` instructions: strings, large numbers, and other constants.

90
docs/spec/mcode.md Normal file
View File

@@ -0,0 +1,90 @@
---
title: "Mcode IR"
description: "JSON-based intermediate representation"
---
## Overview
Mcode is a JSON-based intermediate representation that can be interpreted directly. It represents the same operations as the Mach register VM but uses string-based instruction dispatch rather than binary opcodes. Mcode is intended as an intermediate step toward native code compilation.
## Pipeline
```
Source → Tokenize → Parse (AST) → Mcode (JSON) → Interpret
→ Compile to Mach (planned)
→ Compile to native (planned)
```
Mcode is produced by the `JS_Mcode` compiler pass, which emits a cJSON tree. The mcode interpreter walks this tree directly, dispatching on instruction name strings.
## JSMCode Structure
```c
struct JSMCode {
uint16_t nr_args; // argument count
uint16_t nr_slots; // register count
cJSON **instrs; // pre-flattened instruction array
uint32_t instr_count; // number of instructions
struct {
const char *name; // label name
uint32_t index; // instruction index
} *labels;
uint32_t label_count;
struct JSMCode **functions; // nested functions
uint32_t func_count;
cJSON *json_root; // keeps JSON alive
const char *name; // function name
const char *filename; // source file
uint16_t disruption_pc; // exception handler offset
};
```
## Instruction Format
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands:
```json
["LOADK", 0, 42]
["ADD", 2, 0, 1]
["JMPFALSE", 3, "else_label"]
["CALL", 0, 2, 1]
```
The instruction set mirrors the Mach VM opcodes — same operations, same register semantics, but with string dispatch instead of numeric opcodes.
## Labels
Control flow uses named labels instead of numeric offsets:
```json
["LABEL", "loop_start"]
["ADD", 1, 1, 2]
["JMPFALSE", 3, "loop_end"]
["JMP", "loop_start"]
["LABEL", "loop_end"]
```
Labels are collected into a name-to-index map during loading, enabling O(1) jump resolution.
## Differences from Mach
| Property | Mcode | Mach |
|----------|-------|------|
| Instructions | cJSON arrays | 32-bit binary |
| Dispatch | String comparison | Switch on opcode byte |
| Constants | Inline in JSON | Separate constant pool |
| Jump targets | Named labels | Numeric offsets |
| Memory | Heap (cJSON nodes) | Off-heap (malloc) |
## Purpose
Mcode serves as an inspectable, debuggable intermediate format:
- **Human-readable** — the JSON representation can be printed and examined
- **Language-independent** — any tool that produces the correct JSON can target the ƿit runtime
- **Compilation target** — the Mach compiler can consume mcode as input, and future native code generators can work from the same representation
The cost of string-based dispatch makes mcode slower than the binary Mach VM, so it is primarily useful during development and as a compilation intermediate rather than for production execution.

142
docs/spec/objects.md Normal file
View File

@@ -0,0 +1,142 @@
---
title: "Object Types"
description: "Heap object header format and types"
---
## Object Header
Every heap-allocated object begins with a 64-bit header word (`objhdr_t`):
```
[capacity: 56 bits][flags: 5 bits][type: 3 bits]
```
### Type Field (bits 0-2)
| Value | Type | Description |
|-------|------|-------------|
| 0 | `OBJ_ARRAY` | Dynamic array of JSValues |
| 1 | `OBJ_BLOB` | Binary data (bits) |
| 2 | `OBJ_TEXT` | Unicode text string |
| 3 | `OBJ_RECORD` | Key-value object with prototype chain |
| 4 | `OBJ_FUNCTION` | Function (C, bytecode, register, or mcode) |
| 5 | `OBJ_CODE` | Compiled bytecode |
| 6 | `OBJ_FRAME` | Stack frame for closures |
| 7 | `OBJ_FORWARD` | Forwarding pointer (GC) |
### Flags (bits 3-7)
- **Bit 3 (S)** — Stone flag. If set, the object is immutable and excluded from GC.
- **Bit 4 (P)** — Properties flag.
- **Bit 5 (A)** — Array flag.
- **Bit 7 (R)** — Reserved.
### Capacity (bits 8-63)
The interpretation of the 56-bit capacity field depends on the object type.
## Array
```c
struct JSArray {
objhdr_t header; // type=0, capacity=element slots
word_t len; // current number of elements
JSValue values[]; // inline flexible array
};
```
Capacity is the number of JSValue slots allocated. Length is the number currently in use. Arrays grow by reallocating with a larger capacity.
## Blob
```c
struct JSBlob {
objhdr_t header; // type=1, capacity=allocated bits
word_t length; // length in bits
uint8_t bits[]; // bit-packed data
};
```
Blobs are bit-addressable. The length field tracks the exact number of bits written. A blob starts as antestone (mutable) for writing, then becomes stone (immutable) for reading.
## Text
```c
struct JSText {
objhdr_t header; // type=2, capacity=character slots
word_t length; // length in codepoints (or hash if stoned)
word_t packed[]; // two UTF-32 chars per 64-bit word
};
```
Text is stored as UTF-32, with two 32-bit codepoints packed per 64-bit word. When a text object is stoned, the length field is repurposed to cache the hash value (computed via `fash64`), since stoned text is immutable and the hash never changes.
## Record
```c
struct JSRecord {
objhdr_t header; // type=3, capacity=hash table slots
JSRecord *proto; // prototype chain pointer
word_t len; // number of entries
slot slots[]; // key-value pairs (hash table)
};
```
Records use a hash table with linear probing. Slot 0 is reserved for internal metadata (class ID and record ID). Empty slots use `JS_NULL` as the key; deleted slots use `JS_EXCEPTION` as a tombstone.
The prototype chain is a linked list of JSRecord pointers, traversed during property lookup.
## Function
```c
struct JSFunction {
objhdr_t header; // type=4
JSValue name; // function name
int16_t length; // arity (-1 for variadic)
uint8_t kind; // C, bytecode, register, or mcode
union {
struct { ... } cfunc; // C function pointer
struct { ... } bytecode; // bytecode + frame
struct { ... } regvm; // register VM code
struct { ... } mcode; // mcode IR
} u;
};
```
The kind field selects which union variant is active. Functions can be implemented in C (native), bytecode (stack VM), register code (mach VM), or mcode (JSON interpreter).
## Frame
```c
struct JSFrame {
objhdr_t header; // type=6, capacity=slot count
JSValue function; // owning function
JSValue caller; // parent frame
uint32_t return_pc; // return address
JSValue slots[]; // [this][args][captured][locals][temps]
};
```
Frames capture the execution context for closures. The slots array contains the function's `this` binding, arguments, captured upvalues, local variables, and temporaries. Frames are linked via the caller field for upvalue resolution across closure depth.
## Forwarding Pointer
```
[pointer: 61 bits][111]
```
During garbage collection, when an object is copied to the new heap, the old header is replaced with a forwarding pointer to the new location. This is type 7 (`OBJ_FORWARD`) and stores the new address in bits 3-63. See [Garbage Collection](#gc) for details.
## Object Sizing
All objects are aligned to 8 bytes. The total size in bytes for each type:
| Type | Size |
|------|------|
| Array | `8 + 8 + capacity * 8` |
| Blob | `8 + 8 + ceil(capacity / 8)` |
| Text | `8 + 8 + ceil(capacity / 2) * 8` |
| Record | `8 + 8 + 8 + (capacity + 1) * 16` |
| Function | `sizeof(JSFunction)` (fixed) |
| Code | `sizeof(JSFunctionBytecode)` (fixed) |
| Frame | `8 + 8 + 8 + 4 + capacity * 8` |

82
docs/spec/stone.md Normal file
View File

@@ -0,0 +1,82 @@
---
title: "Stone Memory"
description: "Immutable arena allocation"
---
## Overview
Stone memory is a separate allocation arena for immutable values. Objects in stone memory are permanent — they are never moved, never freed, and never touched by the garbage collector.
The `stone()` function in ƿit petrifies a value, deeply freezing it and all its descendants. Stoned objects have the S bit set in their object header.
## The Stone Arena
Stone memory uses bump allocation from a contiguous arena:
```
stone_base ──────── stone_free ──────── stone_end
[allocated objects] [free space ]
```
Allocation advances `stone_free` forward. When the arena is exhausted, overflow pages are allocated via the system allocator and linked together:
```c
struct StonePage {
struct StonePage *next;
size_t size;
uint8_t data[];
};
```
## The S Bit
Bit 3 of the object header is the stone flag. When set:
- The object is **immutable** — writes disrupt
- The object is **excluded from GC** — the collector skips it entirely
- For text objects, the length field caches the **hash** instead of the character count (since the text cannot change, the hash is computed once and reused)
## What Gets Stoned
When `stone(value)` is called:
1. If the value is already stone, return immediately
2. Recursively walk all nested values (array elements, record fields, etc.)
3. Copy each mutable object into the stone arena
4. Set the S bit on each copied object
5. Return the stoned value
The operation is deep — an entire object graph becomes permanently immutable.
## Text Interning
The stone arena maintains a hash table for text interning. When a text value is stoned, it is looked up in the intern table. If an identical string already exists in stone memory, the existing one is reused. This deduplicates strings and makes equality comparison O(1) for stoned text.
The hash is computed with `fash64` over the packed UTF-32 words.
## Usage Patterns
### Module Return Values
Every module's return value is automatically stoned:
```javascript
// config.cm
return {
debug: true,
timeout: 30
}
// The returned object is stone — shared safely between actors
```
### Message Passing
Messages between actors are stoned before delivery, ensuring actors never share mutable state.
### Constants
Literal objects and arrays that can be determined at compile time may be allocated directly in stone memory.
## Relationship to GC
The Cheney copying collector only operates on the mutable heap. During collection, when the collector encounters a pointer to stone memory (S bit set), it skips it — stone objects are roots that never move. This means stone memory acts as a permanent root set with zero GC overhead.

96
docs/spec/values.md Normal file
View File

@@ -0,0 +1,96 @@
---
title: "Value Representation"
description: "JSValue tagging and encoding"
---
## Overview
Every value in ƿit is a 64-bit word called a JSValue. The runtime uses LSB (least significant bit) tagging to pack type information directly into the value, avoiding heap allocation for common types.
## Tag Encoding
The lowest bits of a JSValue determine its type:
| LSB Pattern | Type | Payload |
|-------------|------|---------|
| `xxxxxxx0` | Integer | 31-bit signed integer in upper bits |
| `xxxxx001` | Pointer | 61-bit aligned heap pointer |
| `xxxxx101` | Short float | 8-bit exponent + 52-bit mantissa |
| `xxxxx011` | Special | 5-bit tag selects subtype |
### Integers
If the least significant bit is 0, the value is an immediate 31-bit signed integer. The integer is stored in the upper bits, extracted via `v >> 1`.
```
[integer: 31 bits][0]
```
Range: -1073741824 to 1073741823. Numbers outside this range are stored as short floats or heap-allocated.
### Pointers
If the lowest 3 bits are `001`, the value is a pointer to a heap object. The pointer is 8-byte aligned, so the low 3 bits are available for the tag. The actual address is extracted by clearing the low 3 bits.
```
[pointer: 61 bits][001]
```
All heap objects (arrays, records, blobs, text, functions, etc.) are referenced through pointer-tagged JSValues.
### Short Floats
If the lowest 3 bits are `101`, the value encodes a floating-point number directly. The format uses an 8-bit exponent (bias 127) and 52-bit mantissa, similar to IEEE 754 but with reduced range.
```
[sign: 1][exponent: 8][mantissa: 52][101]
```
Range: approximately ±3.4 * 10^38. Numbers outside this range fall back to null. Zero is always positive zero.
### Specials
If the lowest 2 bits are `11`, the next 3 bits select a special type:
| 5-bit Tag | Value |
|-----------|-------|
| `00011` | Boolean (true/false in upper bits) |
| `00111` | Null |
| `01111` | Exception marker |
| `10111` | Uninitialized |
| `11011` | Immediate string |
| `11111` | Catch offset |
## Immediate Strings
Short ASCII strings (up to 7 characters) are packed directly into the JSValue without heap allocation:
```
[char6][char5][char4][char3][char2][char1][char0][length: 3][11011]
```
Each character occupies 8 bits. The length (0-7) is stored in bits 5-7. Only ASCII characters (0-127) qualify — any non-ASCII character forces heap allocation.
```javascript
var s = "hello" // 5 chars, fits in immediate string
var t = "" // immediate (length 0)
var u = "longtext" // 8 chars, heap-allocated
```
## Null
Null is encoded as a special-tagged value with tag `00111`. There is no `undefined` in ƿit — only null.
```javascript
var x = null // special tag null
var y = 1 / 0 // also null (division by zero)
var z = {}.missing // null (missing field)
```
## Boolean
True and false are encoded as specials with tag `00011`, distinguished by a bit in the upper payload.
## Summary
The tagging scheme ensures that the most common values — small integers, booleans, null, and short strings — require zero heap allocation. This significantly reduces GC pressure and improves cache locality.

119
docs/wota.md Normal file
View File

@@ -0,0 +1,119 @@
---
title: "Wota Format"
description: "Word Object Transfer Arrangement"
weight: 86
type: "docs"
---
Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume.
Wota stands for Word Object Transfer Arrangement.
## Type Summary
| Byte | Type |
|------|------|
| `00` | Integer |
| `01` | Floating Point |
| `02` | Array |
| `03` | Record |
| `04` | Blob |
| `05` | Text |
| `07` | Symbol |
## Preambles
Every Wota value starts with a preamble word. The least significant byte contains the type. The remaining 56 bits contain type-specific data.
## Blob
A blob is a string of bits. The remaining field contains the number of bits. The number of words that follow: `floor((number_of_bits + 63) / 64)`. The first bit of the blob goes into the most significant bit of the first word. The final word is padded with 0.
Example: A blob containing 25 bits `111100001110001100100001`:
```
0000000000001904 # preamble: 25 bits, type blob
F0E3208000000000 # data (padded to 64 bits)
```
## Text
The text is a string of UTF-32 characters packed 2 per word. The remaining field contains the number of characters. The number of words that follow: `floor((number_of_characters + 1) / 2)`. The final word is padded with 0.
Example: `"cat"`:
```
0000000000000305 # preamble: 3 characters, type text
0000006300000061 # 'c' and 'a'
0000007400000000 # 't' and padding
```
## Array
An array is an ordered sequence of values. The remaining field contains the number of elements. Following the preamble are the elements, each beginning with its own preamble. Nesting is encouraged. Cyclic structures are not allowed.
Example: `["duck", "dragon"]`:
```
0000000000000202 # preamble: 2 elements, type array
0000000000000405 # text "duck": 4 chars
0000006400000074 # 'd' 't' (reversed pair order)
000000630000006B # 'c' 'k'
0000000000000605 # text "dragon": 6 chars
0000006400000072 # 'd' 'r'
0000006100000067 # 'a' 'g'
0000006F0000006E # 'o' 'n'
```
## Record
A record is a set of key/value pairs. Keys must be text. The remaining field contains the number of pairs.
Example: `{"ox": ["O", "X"]}`:
```
0000000000000103 # preamble: 1 pair, type record
0000000000000205 # key "ox": 2 chars
0000006F00000078 # 'o' 'x'
0000000000000202 # value: array of 2
0000000000000105 # "O": 1 char
0000004F00000000 # 'O'
0000000000000105 # "X": 1 char
0000005800000000 # 'X'
```
## Number
Numbers are represented as DEC64. To arrange an integer, shift the integer up 8 bits. The number is incorporated directly into the preamble.
Example: `7`:
```
0000000000000700 # integer 7 as DEC64
```
To arrange a floating point number, place the number in the word following the floating point preamble.
Example: `4.25`:
```
0000000000000001 # preamble: type floating point
000000000001A9FE # DEC64 encoding of 4.25
```
Care must be taken when decoding that the least significant byte of the number is not `80` (the null exponent).
## Symbol
The remaining field contains the symbol.
Example: `[null, false, true, private, system]`:
```
0000000000000502 # array of 5
0000000000000007 # null
0000000000000207 # false
0000000000000307 # true
0000000000000807 # private
0000000000000907 # system
```

View File

@@ -9,6 +9,7 @@
#include "cell.h"
#include "cell_internal.h"
#include "cJSON.h"
#define ENGINE "internal/engine.cm"
#define CELL_SHOP_DIR ".cell"
@@ -104,6 +105,35 @@ static char* load_core_file(const char *filename, size_t *out_size) {
return data;
}
static int print_json_errors(const char *json) {
if (!json) return 0;
cJSON *root = cJSON_Parse(json);
if (!root) return 0;
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) {
cJSON_Delete(root);
return 0;
}
const char *filename = "<unknown>";
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
if (cJSON_IsString(fname))
filename = fname->valuestring;
cJSON *e;
cJSON_ArrayForEach(e, errors) {
const char *msg = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(e, "message"));
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column");
if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
fprintf(stderr, "%s:%d:%d: error: %s\n",
filename, (int)line->valuedouble, (int)col->valuedouble, msg);
else if (msg)
fprintf(stderr, "%s: error: %s\n", filename, msg);
}
cJSON_Delete(root);
return 1;
}
// Get the core path for use by scripts
const char* cell_get_core_path(void) {
return core_path;
@@ -369,32 +399,18 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *json = JS_AST(ctx, script, strlen(script), filename);
char *json = JS_AST(script, strlen(script), filename);
if (json) {
int has_errors = print_json_errors(json);
printf("%s\n", json);
free(json);
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to parse AST\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return json ? 0 : 1;
return 1;
}
}
/* Check for --tokenize flag to output token array JSON */
@@ -426,35 +442,165 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
char *json = JS_Tokenize(script, strlen(script), filename);
if (json) {
int has_errors = print_json_errors(json);
printf("%s\n", json);
free(json);
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to tokenize\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
}
/* Check for --mcode flag to output MCODE JSON IR */
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
char *ast_json = JS_AST(script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
char *json = JS_Tokenize(ctx, script, strlen(script), filename);
if (json) {
printf("%s\n", json);
free(json);
if (print_json_errors(ast_json)) {
free(ast_json);
free(allocated_script);
return 1;
}
char *mcode_json = JS_Mcode(ast_json);
free(ast_json);
if (!mcode_json) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
printf("%s\n", mcode_json);
free(mcode_json);
free(allocated_script);
return 0;
}
/* Check for --run-mcode flag to execute via MCODE interpreter */
if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; }
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) { fclose(f); printf("Failed to allocate memory\n"); return 1; }
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
printf("Failed to tokenize\n");
script = (char *)script_or_file;
}
char *ast_json = JS_AST(script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json); free(allocated_script);
return 1;
}
char *mcode_json = JS_Mcode(ast_json);
free(ast_json);
if (!mcode_json) {
printf("Failed to generate MCODE\n");
free(allocated_script);
return 1;
}
if (print_json_errors(mcode_json)) {
free(mcode_json); free(allocated_script);
return 1;
}
/* Use a larger heap context for execution */
JSRuntime *rt = JS_NewRuntime();
if (!rt) { printf("Failed to create JS runtime\n"); free(mcode_json); free(allocated_script); return 1; }
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
if (!ctx) { printf("Failed to create execution context\n"); free(mcode_json); JS_FreeRuntime(rt); free(allocated_script); return 1; }
JSValue result = JS_CallMcode(ctx, mcode_json);
free(mcode_json);
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
const char *str = JS_ToCString(ctx, exc);
if (str) { printf("Error: %s\n", str); JS_FreeCString(ctx, str); }
cJSON *stack = JS_GetStack(ctx);
if (stack) {
int n = cJSON_GetArraySize(stack);
for (int i = 0; i < n; i++) {
cJSON *fr = cJSON_GetArrayItem(stack, i);
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
}
cJSON_Delete(stack);
}
JS_FreeValue(ctx, exc);
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); }
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return json ? 0 : 1;
return JS_IsException(result) ? 1 : 0;
}
/* Check for --mach flag to output machine code JSON */
/* Check for --mach flag to dump MACH bytecode */
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
@@ -483,114 +629,42 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
char *ast_json = JS_AST(script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
if (print_json_errors(ast_json)) {
free(ast_json);
if (mach_json) {
printf("%s\n", mach_json);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
}
/* Check for --vmcode flag to dump linked register VM bytecode */
if (argc >= 3 && strcmp(argv[1], "--vmcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
free(ast_json); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
free(ast_json); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
JS_DumpMach(ctx, ast_json, JS_NULL);
free(ast_json);
if (mach_json) {
JS_DumpRegisterMach(ctx, mach_json, JS_NULL);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
return 0;
}
/* Check for --mach-run flag to generate and run machine code through register VM */
/* Check for --mach-run flag to compile and run through MACH VM */
if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
@@ -619,46 +693,35 @@ int cell_init(int argc, char **argv)
script = (char *)script_or_file;
}
char *ast_json = JS_AST(script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json);
free(allocated_script);
return 1;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
free(ast_json); free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
free(ast_json); JS_FreeRuntime(rt); free(allocated_script);
return 1;
}
/* Parse to AST */
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Generate machine code */
char *mach_json = JS_Mach(ctx, ast_json);
JSValue result = JS_RunMach(ctx, ast_json, JS_NULL);
free(ast_json);
if (!mach_json) {
printf("Failed to generate machine code\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Execute through register VM */
JSValue result = JS_IntegrateRegister(ctx, mach_json, JS_NULL);
free(mach_json);
int exit_code = 0;
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
@@ -667,9 +730,22 @@ int cell_init(int argc, char **argv)
printf("Error: %s\n", err_str);
JS_FreeCString(ctx, err_str);
}
cJSON *stack = JS_GetStack(ctx);
if (stack) {
int n = cJSON_GetArraySize(stack);
for (int i = 0; i < n; i++) {
cJSON *fr = cJSON_GetArrayItem(stack, i);
const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function"));
const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file"));
int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line"));
int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column"));
printf(" at %s (%s:%d:%d)\n", fn ? fn : "<anonymous>", file ? file : "<unknown>", line, col);
}
cJSON_Delete(stack);
}
JS_FreeValue(ctx, exc);
exit_code = 1;
} else if (!JS_IsNull(result)) {
/* Print result */
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);

File diff suppressed because it is too large Load Diff

View File

@@ -1219,26 +1219,51 @@ CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_
CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename);
/* Parse source code and return AST as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename);
Returns malloc'd JSON string (caller must free), or NULL on error.
No JSContext needed — pure string transformation. */
char *JS_AST (const char *source, size_t len, const char *filename);
/* Tokenize source code and return token array as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *filename);
Returns malloc'd JSON string (caller must free), or NULL on error.
No JSContext needed — pure string transformation. */
char *JS_Tokenize (const char *source, size_t len, const char *filename);
/* Generate register-based machine code from AST JSON.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Mach (JSContext *ctx, const char *ast_json);
/* Compiled bytecode (context-free, serializable) */
typedef struct MachCode MachCode;
/* Link and execute register-based machine code.
mach_json: JSON output from JS_Mach
env: stone record for external variable resolution (or JS_NULL)
/* Compile AST JSON to context-free MachCode.
Returns MachCode* (caller must free with JS_FreeMachCode), or NULL on error. */
MachCode *JS_CompileMach(const char *ast_json);
/* Free a compiled MachCode tree. */
void JS_FreeMachCode(MachCode *mc);
/* Load compiled MachCode into a JSContext, materializing JSValues.
Returns JSCodeRegister* linked and ready for execution. */
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
/* Dump MACH bytecode to stdout for debugging. Takes AST JSON.
Internally compiles, loads, and dumps binary bytecode. */
void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Compile and execute MACH bytecode. Takes AST JSON.
Internally compiles, loads, and executes.
Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_IntegrateRegister (JSContext *ctx, const char *mach_json, JSValue env);
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Dump linked register VM bytecode to stdout for debugging.
Links mach_json and prints the resulting bytecode with resolved labels. */
void JS_DumpRegisterMach (JSContext *ctx, const char *mach_json, JSValue env);
/* Compile AST JSON to MCODE JSON (string-based IR).
Returns malloc'd JSON string, or NULL on error. Caller must free.
No JSContext needed — pure string transformation. */
char *JS_Mcode (const char *ast_json);
/* Parse and execute MCODE JSON directly via the MCODE interpreter.
Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
/* Get stack trace as cJSON array of frame objects.
Returns NULL if no register VM frame is active.
Caller must call cJSON_Delete() on the result. */
struct cJSON *JS_GetStack (JSContext *ctx);
/* Integrate a CellModule with an environment and execute.
Returns callable function value, or JS_EXCEPTION on error. */

View File

@@ -5,6 +5,7 @@
*/
#include "quickjs.h"
#include "cJSON.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
@@ -2132,6 +2133,286 @@ TEST(cell_module_string_constant) {
return 1;
}
/* ============================================================================
ERROR RECOVERY TESTS - Helper macros
============================================================================ */
#define ASSERT_HAS_ERRORS(json_str, min_count) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (!_errs || !cJSON_IsArray(_errs) || cJSON_GetArraySize(_errs) < (min_count)) { \
printf("[line %d: expected at least %d error(s), got %d] ", __LINE__, (min_count), \
_errs && cJSON_IsArray(_errs) ? cJSON_GetArraySize(_errs) : 0); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_NO_ERRORS(json_str) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (_errs && cJSON_IsArray(_errs) && cJSON_GetArraySize(_errs) > 0) { \
cJSON *_first = cJSON_GetArrayItem(_errs, 0); \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_first, "message")); \
printf("[line %d: expected no errors, got: %s] ", __LINE__, _msg ? _msg : "?"); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_ERROR_MSG_CONTAINS(json_str, substring) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
int _found = 0; \
if (_errs && cJSON_IsArray(_errs)) { \
cJSON *_e; \
cJSON_ArrayForEach(_e, _errs) { \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_e, "message")); \
if (_msg && strstr(_msg, (substring))) { _found = 1; break; } \
} \
} \
if (!_found) { \
printf("[line %d: no error containing '%s'] ", __LINE__, (substring)); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
/* ============================================================================
TOKENIZER ERROR TESTS
============================================================================ */
TEST(tokenize_unterminated_string) {
const char *src = "var x = \"hello";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated string");
free(json);
return 1;
}
TEST(tokenize_unterminated_template) {
const char *src = "var x = `hello";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated template");
free(json);
return 1;
}
TEST(tokenize_unterminated_block_comment) {
const char *src = "var x /* comment";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated block comment");
free(json);
return 1;
}
TEST(tokenize_malformed_hex) {
const char *src = "var x = 0x";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed hex");
free(json);
return 1;
}
TEST(tokenize_malformed_binary) {
const char *src = "var x = 0b";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed binary");
free(json);
return 1;
}
TEST(tokenize_malformed_exponent) {
const char *src = "var x = 1e+";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "no digits after exponent");
free(json);
return 1;
}
TEST(tokenize_valid_no_errors) {
const char *src = "var x = 42";
char *json = JS_Tokenize(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
PARSER ERROR TESTS
============================================================================ */
TEST(ast_missing_identifier_after_var) {
const char *src = "var = 1";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "expected identifier");
free(json);
return 1;
}
TEST(ast_missing_initializer_def) {
const char *src = "def x";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "missing initializer");
free(json);
return 1;
}
TEST(ast_recovery_continues_after_error) {
const char *src = "var = 1; var y = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_HAS_ERRORS(json, 1);
/* Check that 'y' statement is present in the AST */
ASSERT_MSG(strstr(json, "\"y\"") != NULL, "recovery failed: 'y' not in AST");
free(json);
return 1;
}
TEST(ast_valid_no_errors) {
const char *src = "var x = 1; var y = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
AST SEMANTIC ERROR TESTS
============================================================================ */
TEST(ast_sem_assign_to_const) {
const char *src = "def x = 5; x = 3";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_assign_to_arg) {
const char *src = "function(x) { x = 5; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_redeclare_const) {
const char *src = "def x = 1; def x = 2";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot redeclare constant");
free(json);
return 1;
}
TEST(ast_sem_break_outside_loop) {
const char *src = "break";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_continue_outside_loop) {
const char *src = "continue";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_break_inside_loop_ok) {
const char *src = "while (true) { break; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_increment_const) {
const char *src = "def x = 1; x++";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_shadow_var_ok) {
const char *src = "var array = []; array";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_var_assign_ok) {
const char *src = "var x = 1; x = x + 1";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_nested_function_scope) {
const char *src = "var x = 1; function f(x) { return x + 1; }";
char *json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
CODEGEN TESTS (updated for new direct AST-to-bytecode compiler)
============================================================================ */
TEST(mach_compile_basic) {
const char *src = "var x = 1; x = x + 1";
char *ast_json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
MachCode *mc = JS_CompileMach(ast_json);
free(ast_json);
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
JS_FreeMachCode(mc);
return 1;
}
TEST(mach_compile_function) {
const char *src = "function f(x) { return x + 1 }";
char *ast_json = JS_AST(src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
MachCode *mc = JS_CompileMach(ast_json);
free(ast_json);
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
JS_FreeMachCode(mc);
return 1;
}
/* ============================================================================
MAIN TEST RUNNER
============================================================================ */
@@ -2348,6 +2629,37 @@ int run_c_test_suite(JSContext *ctx)
RUN_TEST(cell_module_roundtrip_execute);
RUN_TEST(cell_module_string_constant);
printf("\nTokenizer Errors:\n");
RUN_TEST(tokenize_unterminated_string);
RUN_TEST(tokenize_unterminated_template);
RUN_TEST(tokenize_unterminated_block_comment);
RUN_TEST(tokenize_malformed_hex);
RUN_TEST(tokenize_malformed_binary);
RUN_TEST(tokenize_malformed_exponent);
RUN_TEST(tokenize_valid_no_errors);
printf("\nParser Errors:\n");
RUN_TEST(ast_missing_identifier_after_var);
RUN_TEST(ast_missing_initializer_def);
RUN_TEST(ast_recovery_continues_after_error);
RUN_TEST(ast_valid_no_errors);
printf("\nAST Semantic Errors:\n");
RUN_TEST(ast_sem_assign_to_const);
RUN_TEST(ast_sem_assign_to_arg);
RUN_TEST(ast_sem_redeclare_const);
RUN_TEST(ast_sem_break_outside_loop);
RUN_TEST(ast_sem_continue_outside_loop);
RUN_TEST(ast_sem_break_inside_loop_ok);
RUN_TEST(ast_sem_increment_const);
RUN_TEST(ast_sem_shadow_var_ok);
RUN_TEST(ast_sem_var_assign_ok);
RUN_TEST(ast_sem_nested_function_scope);
printf("\nCodegen:\n");
RUN_TEST(mach_compile_basic);
RUN_TEST(mach_compile_function);
printf("\n=================================\n");
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);
printf("=================================\n\n");

39
tests/demo.ce Normal file
View File

@@ -0,0 +1,39 @@
function safe_add(a, b) {
return a + b
} disruption {
print("disruption caught in safe_add")
}
function inner() {
disrupt
}
function outer() {
inner()
} disruption {
print("disruption caught in outer — from inner()")
}
// Test 1: explicit disrupt with handler
function test_explicit() {
disrupt
} disruption {
print("test 1: explicit disrupt handled")
}
test_explicit()
// Test 2: type error disrupt (number + function)
safe_add(1, print)
// Test 3: unwinding — inner disrupts, outer catches
outer()
// Test 4: disrupt from inside disruption clause
function test_nested() {
disrupt
} disruption {
print("test 4: first disruption")
}
test_nested()
print("done")

3376
vm_suite.ce Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
var x = 0; outer: { x = 1; break outer; x = 2 }; x

View File

@@ -1 +0,0 @@
typeof 5

View File

@@ -1 +0,0 @@
void 0

View File

@@ -1 +0,0 @@
var r; switch(2) { case 1: r = 1; break; case 2: r = 2; break }; r

336
vm_test/syntax.txt Normal file
View File

@@ -0,0 +1,336 @@
/* comprehensive syntax test - exercises all AST paths */
// --- variables and constants ---
var a
var b = 1
var c = 2, d = 3
def PI = 3.14159
def MSG = "hello"
// --- number literals ---
var n_int = 42
var n_float = 3.14
var n_hex = 0xFF
var n_octal = 0o77
var n_binary = 0b1010
var n_underscore = 1_000_000
var n_exp = 1e10
var n_exp_neg = 2.5e-3
// --- string literals ---
var s_single = 'single'
var s_double = "double"
var s_escape = "tab\there\nnewline\\slash\"quote"
var s_unicode = "\u0041\u0042"
// --- template literals ---
var t_basic = `hello ${b} world`
var t_nested = `a ${b + c} b ${d}`
var t_expr = `result: ${b > 0 ? "yes" : "no"}`
// --- array literals ---
var arr_empty = []
var arr_simple = [1, 2, 3]
var arr_mixed = [1, "two", true, null]
var arr_nested = [[1, 2], [3, [4, 5]]]
// --- record/object literals ---
var rec_empty = {}
var rec_simple = {x: 1, y: 2}
var rec_nested = {a: {b: {c: 3}}}
var shorthand_var = 10
var rec_shorthand = {shorthand_var}
var rec_computed = {["key" + "1"]: 100}
var rec_method = {
greet() { return "hi" }
}
var rec_mixed = {
name: "test",
value: 42,
nested: {inner: true},
items: [1, 2, 3]
}
// --- all binary arithmetic operators ---
var arith_add = 1 + 2
var arith_sub = 5 - 3
var arith_mul = 4 * 5
var arith_div = 10 / 3
var arith_mod = 10 % 3
var arith_pow = 2 ** 8
// --- comparison operators ---
var cmp_lt = 1 < 2
var cmp_gt = 2 > 1
var cmp_lte = 1 <= 1
var cmp_gte = 2 >= 2
var cmp_eq = 1 == 1
var cmp_neq = 1 != 2
// --- logical operators ---
var log_and = true && false
var log_or = false || true
var log_not = !false
// --- bitwise operators ---
var bit_and = 0xFF & 0x0F
var bit_or = 0xF0 | 0x0F
var bit_xor = 0xFF ^ 0x0F
var bit_not = ~0
var bit_shl = 1 << 8
var bit_shr = 256 >> 4
var bit_shru = -1 >>> 0
// --- nullish coalescing ---
var nullish = null ?? "default"
// --- ternary operator ---
var tern = b > 0 ? "pos" : "neg"
var tern_nested = b > 0 ? (b > 10 ? "big" : "small") : "neg"
// --- comma operator ---
var comma_result = (1, 2, 3)
// --- unary operators ---
var unary_pos = +b
var unary_neg = -b
// --- increment/decrement ---
var inc_val = 0
inc_val++
inc_val--
;++inc_val
;--inc_val
// --- all compound assignments ---
var ca = 10
ca += 5
ca -= 3
ca *= 2
ca /= 4
ca %= 3
ca **= 2
ca <<= 1
ca >>= 1
ca >>>= 0
ca &= 0xFF
ca |= 0x01
ca ^= 0x10
ca &&= true
ca ||= false
ca ??= 1
// --- chained assignment ---
var ch1, ch2
ch1 = ch2 = 42
// --- property access ---
var obj = {a: {b: {c: 1}}, items: [10, 20, 30]}
var dot_access = obj.a
var dot_chain = obj.a.b.c
var bracket_access = obj["a"]
var bracket_dynamic = obj["it" + "ems"]
var bracket_index = obj.items[1]
// --- optional chaining ---
var opt_obj = {x: {y: 1}}
var opt_prop = opt_obj?.x
var opt_deep = opt_obj?.x?.y
var opt_null = null?.foo
var opt_bracket = opt_obj?.["x"]
var opt_call_obj = {f: function() { return 1 }}
var opt_call = opt_call_obj?.f?.()
// --- function declarations ---
function add(a, b) {
return a + b
}
function no_params() {
return 42
}
function with_default(x = 10, y = 20) {
return x + y
}
// --- function expressions ---
var func_expr = function(x) { return x * 2 }
var func_named = function multiply(x, y) { return x * y }
// --- arrow functions (all forms) ---
var arrow_no_param = () => 0
var arrow_one = x => x + 1
var arrow_multi = (a, b) => a + b
var arrow_block = (a, b) => { return a + b }
var arrow_default = (x = 1, y = 2) => x + y
var arrow_default_expr = (x = 1 + 2, y = arr_simple[0]) => x + y
// --- closures ---
function make_counter() {
var count = 0
return function() {
count += 1
return count
}
}
function outer_fn() {
var x = 10
function middle() {
var y = 20
function inner() {
return x + y
}
return inner()
}
return middle()
}
// --- this in methods ---
var counter = {
val: 0,
inc() { this.val += 1 },
get() { return this.val }
}
// --- if/else/else-if ---
var if_result
if (b > 0) {
if_result = "positive"
} else if (b == 0) {
if_result = "zero"
} else {
if_result = "negative"
}
// --- while loop ---
var w = 0
while (w < 5) {
w += 1
}
// --- do-while loop ---
var dw = 0
do {
dw += 1
} while (dw < 3)
// --- for loop ---
var f_sum = 0
for (var i = 0; i < 10; i++) {
f_sum += i
}
// --- for loop with break ---
var fb = 0
for (var j = 0; j < 100; j++) {
if (j == 5) break
fb = j
}
// --- for loop with continue ---
var fc = 0
for (var k = 0; k < 10; k++) {
if (k % 2 == 0) continue
fc += k
}
// --- labeled break ---
outer: for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
if (y == 1) break outer
}
}
// --- labeled continue ---
var lc = 0
loop: for (var m = 0; m < 3; m++) {
for (var n = 0; n < 3; n++) {
if (n == 1) continue loop
lc += 1
}
}
// --- disrupt and disruption ---
function disrupt_test() {
disrupt
}
function disruption_test() {
var x = 1
} disruption {
var y = 2
}
function disrupt_with_disruption() {
disrupt
} disruption {
var handled = true
}
// --- delete operator ---
var del_obj = {a: 1, b: 2}
delete del_obj.a
// --- in operator ---
var in_result = "b" in del_obj
// --- go statement ---
function async_task() { return 1 }
function caller() { go async_task() }
// --- IIFE ---
var iife = (function() { return 99 })()
// --- recursive function ---
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
// --- mutually recursive functions ---
function is_even(n) {
if (n == 0) return true
return is_odd(n - 1)
}
function is_odd(n) {
if (n == 0) return false
return is_even(n - 1)
}
// --- block scoping ---
var block_val = 1
{
var block_val = 2
}
// --- nested blocks ---
{
var nb = 1
{
var nb2 = nb + 1
{
var nb3 = nb2 + 1
}
}
}
// --- empty statement ---
;
// --- parenthesized expression for precedence ---
var prec = (1 + 2) * (3 + 4)
// --- complex expressions ---
var complex = arr_simple[0] + rec_simple.x * (b > 0 ? 2 : 1)
// --- operator precedence chain ---
var prec_chain = 1 + 2 * 3 ** 2 - 4 / 2 % 3
// --- regex literals ---
var re = /hello/
var re_flags = /world/gi
// --- line comment at end ---
var end = 1 // done

View File

@@ -1 +0,0 @@
try { 1 } catch(e) { 2 }

View File

@@ -1 +0,0 @@
var x = 1; try { throw 0 } catch(e) { x = 2 } finally { x = x + 1 }; x

View File

@@ -1 +0,0 @@
var x = 1; try { x = 2 } finally { x = 3 }; x

View File

@@ -1 +0,0 @@
try { throw "err" } catch(e) { e }

40
website/content/_index.md Normal file
View File

@@ -0,0 +1,40 @@
---
title: "ƿit"
---
```javascript
// hello.ce — a simple actor
log.console("Hello, ƿit!")
$stop()
```
```bash
pit hello
```
## Why ƿit
- **Actors, not threads** — isolated memory, message passing, no shared state. Concurrent programs that are safe by default.
- **Everything is stoned** — `stone()` makes values permanently immutable. Messages between actors are frozen automatically. No defensive copying.
- **Prototypes, not classes** — objects inherit directly from other objects. No class hierarchies, no `new`, no `this` confusion.
- **C when you need it** — drop a `.c` file in your package and it becomes a native module. No FFI bindings, no build scripts.
- **Small and predictable** — DEC64 numbers with no rounding errors. No `undefined`. Strict equality only. A runtime that fits in your head.
<div class="home-art">
<img src="/images/wizard.png" alt="ƿit wizard">
</div>
<div class="home-links">
<a href="/start/">Get Started</a>
<a href="/manual/">Language Manual</a>
</div>
<div class="donate-section">
## Support ƿit
ƿit is free and open source. If you find it useful, consider supporting its development.
Donation options coming soon.
</div>

View File

@@ -0,0 +1,175 @@
---
title: "Command Line Interface"
description: "The pit tool"
type: "standalone"
---
ƿit provides a command-line interface for managing packages, running scripts, and building applications.
## Basic Usage
```bash
pit <command> [arguments]
```
## Commands
### pit version
Display the ƿit version.
```bash
pit version
# 0.1.0
```
### pit install
Install a package to the shop.
```bash
pit install gitea.pockle.world/john/prosperon
pit install /Users/john/local/mypackage # local path
```
### pit update
Update packages from remote sources.
```bash
pit update # update all packages
pit update <package> # update specific package
```
### pit remove
Remove a package from the shop.
```bash
pit remove gitea.pockle.world/john/oldpackage
```
### pit list
List installed packages.
```bash
pit list # list all installed packages
pit list <package> # list dependencies of a package
```
### pit ls
List modules and actors in a package.
```bash
pit ls # list files in current project
pit ls <package> # list files in specified package
```
### pit build
Build the current package.
```bash
pit build
```
### pit test
Run tests.
```bash
pit test # run tests in current package
pit test all # run all tests
pit test <package> # run tests in specific package
```
### pit link
Manage local package links for development.
```bash
pit link add <canonical> <local_path> # link a package
pit link list # show all links
pit link delete <canonical> # remove a link
pit link clear # remove all links
```
### pit fetch
Fetch package sources without extracting.
```bash
pit fetch <package>
```
### pit upgrade
Upgrade the ƿit installation itself.
```bash
pit upgrade
```
### pit clean
Clean build artifacts.
```bash
pit clean
```
### pit help
Display help information.
```bash
pit help
pit help <command>
```
## Running Scripts
Any `.ce` file in the ƿit core can be run as a command:
```bash
pit version # runs version.ce
pit build # runs build.ce
pit test # runs test.ce
```
## Package Locators
Packages are identified by locators:
- **Remote**: `gitea.pockle.world/user/repo`
- **Local**: `/absolute/path/to/package`
```bash
pit install gitea.pockle.world/john/prosperon
pit install /Users/john/work/mylib
```
## Configuration
ƿit stores its data in `~/.pit/`:
```
~/.pit/
├── packages/ # installed packages
├── lib/ # compiled dynamic libraries
├── build/ # build cache
├── cache/ # downloaded archives
├── lock.toml # installed package versions
└── link.toml # local development links
```
## Environment
ƿit reads the `HOME` environment variable to locate the shop directory.
## Exit Codes
- `0` — Success
- Non-zero — Error (check output for details)

View File

@@ -0,0 +1,60 @@
---
title: "Contributing"
description: "How to contribute to ƿit"
type: "standalone"
---
ƿit is developed openly. Contributions of all kinds are welcome.
## Report Bugs
Found a problem? Open an issue on the [ƿit issue tracker](https://gitea.pockle.world/john/cell/issues). Include:
- What you expected to happen
- What actually happened
- A minimal reproduction (a short `.ce` or `.cm` file)
- Your platform and ƿit version (`pit version`)
## Submit Packages
Share your ƿit packages by hosting them on a Gitea instance. Any package with a valid `pit.toml` can be installed by others:
```bash
pit install gitea.example.com/you/your-package
```
See [Packages](/manual/#packages) for how to structure and publish packages.
## Contribute to the Runtime
The ƿit runtime is written in C. To build from source:
```bash
git clone https://gitea.pockle.world/john/cell
cd cell
make bootstrap
```
### Code Style
- C code uses 2-space indentation
- Functions and variables are `static` unless exported
- No headers between files in the same package
- Use `JS_NULL` / `JS_IsNull` — there is no `undefined`
- Objects over classes; limit prototype usage
### Submitting Patches
1. Fork the repository on Gitea
2. Create a branch for your change
3. Keep commits focused — one logical change per commit
4. Test your changes with `pit test all`
5. Open a pull request with a clear description
## Improve Documentation
Documentation lives in the `docs/` directory as Markdown files. Fixes for typos, unclear explanations, or missing examples are always appreciated.
## Code of Conduct
Be respectful. Communicate clearly. Assume good faith. Technical disagreements are fine; personal attacks are not.

View File

@@ -0,0 +1,5 @@
---
title: "Language Manual"
description: "Complete ƿit language reference"
type: "manual"
---

View File

@@ -0,0 +1,5 @@
---
title: "Language Specification"
description: "ƿit internals for language implementers"
type: "spec"
---

View File

@@ -0,0 +1,153 @@
---
title: "Getting Started"
description: "Install ƿit and write your first program"
---
## Prerequisites
- A C compiler (gcc or clang)
- [Meson](https://mesonbuild.com/) build system
- Git
## Installation
```bash
# Clone the repository
git clone https://gitea.pockle.world/john/cell
cd cell
# Bootstrap the build
make bootstrap
```
This compiles the ƿit runtime and installs the `pit` binary. The ƿit shop is created at `~/.pit/`.
Verify your installation:
```bash
pit version
```
## Hello World
Create a file called `hello.ce`:
```javascript
// hello.ce
log.console("Hello, ƿit!")
$stop()
```
Run it:
```bash
pit hello
```
You should see `Hello, ƿit!` printed to the console.
Every `.ce` file is an **actor** — an independent unit of execution. The `$stop()` call tells the actor to shut down when it's done.
## A Counting Actor
Actors can schedule work over time. Create `counter.ce`:
```javascript
// counter.ce
var count = 0
$clock(function(dt) {
count = count + 1
log.console(`tick ${count}`)
if (count >= 5) {
$stop()
}
})
```
```bash
pit counter
```
The `$clock` intrinsic calls your function every tick. The actor runs until you stop it.
## Two Actors Talking
The power of ƿit is in actors communicating through messages. Create two files:
```javascript
// greeter.ce
$receiver(function(msg, reply) {
reply({greeting: `Hello, ${msg.name}!`})
})
```
```javascript
// main.ce
$start(function(greeter) {
$send(greeter, {name: "world"}, function(response) {
log.console(response.greeting)
$stop()
})
}, "greeter")
```
```bash
pit main
```
`$start` launches a new actor. `$send` sends a message and provides a callback for the reply. Messages are automatically serialized — actors never share memory.
## Using Modules
Modules (`.cm` files) return a value that is cached and frozen. Create a module:
```javascript
// math_helpers.cm
function square(x) {
return x * x
}
function distance(x1, y1, x2, y2) {
var math = use('math/radians')
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
return {
square: square,
distance: distance
}
```
Use it from an actor:
```javascript
// calc.ce
var helpers = use('math_helpers')
log.console(helpers.square(5)) // 25
log.console(helpers.distance(0, 0, 3, 4)) // 5
$stop()
```
## Creating a Package
To share code or manage dependencies, create a `pit.toml`:
```toml
package = "myproject"
version = "0.1.0"
[dependencies]
```
Your package can now use `pit build`, `pit test`, and install dependencies.
## What's Next
- [**Language Manual**](/manual/) — full syntax reference, actors, packages, standard library
- [**CLI Reference**](/cli/) — all `pit` commands
- [**Language Spec**](/spec/) — internals for implementers

View File

@@ -0,0 +1,41 @@
sections:
- title: "Language"
pages:
- title: "Language Reference"
url: "/docs/language/"
- title: "Actors and Modules"
url: "/docs/actors/"
- title: "Packages"
url: "/docs/packages/"
- title: "Tools"
pages:
- title: "CLI"
url: "/docs/cli/"
- title: "C Modules"
url: "/docs/c-modules/"
- title: "Reference"
pages:
- title: "Built-in Functions"
url: "/docs/functions/"
- title: "Standard Library"
pages:
- title: "Overview"
url: "/docs/library/"
- title: "text"
url: "/docs/library/text/"
- title: "number"
url: "/docs/library/number/"
- title: "array"
url: "/docs/library/array/"
- title: "object"
url: "/docs/library/object/"
- title: "blob"
url: "/docs/library/blob/"
- title: "time"
url: "/docs/library/time/"
- title: "math"
url: "/docs/library/math/"
- title: "json"
url: "/docs/library/json/"
- title: "random"
url: "/docs/library/random/"

View File

@@ -0,0 +1,58 @@
sections:
- title: "Language Syntax"
page: "/docs/language/"
id: "language"
- title: "Actors and Modules"
page: "/docs/actors/"
id: "actors"
- title: "Requestors"
page: "/docs/requestors/"
id: "requestors"
- title: "Packages"
page: "/docs/packages/"
id: "packages"
- title: "Built-in Functions"
page: "/docs/functions/"
id: "functions"
- title: "Standard Library"
page: "/docs/library/"
id: "library"
- title: "text"
page: "/docs/library/text/"
id: "library-text"
- title: "number"
page: "/docs/library/number/"
id: "library-number"
- title: "array"
page: "/docs/library/array/"
id: "library-array"
- title: "object"
page: "/docs/library/object/"
id: "library-object"
- title: "blob"
page: "/docs/library/blob/"
id: "library-blob"
- title: "time"
page: "/docs/library/time/"
id: "library-time"
- title: "math"
page: "/docs/library/math/"
id: "library-math"
- title: "json"
page: "/docs/library/json/"
id: "library-json"
- title: "random"
page: "/docs/library/random/"
id: "library-random"
- title: "Writing C Modules"
page: "/docs/c-modules/"
id: "c-modules"
- title: "Kim Encoding"
page: "/docs/kim/"
id: "kim"
- title: "Nota Format"
page: "/docs/nota/"
id: "nota"
- title: "Wota Format"
page: "/docs/wota/"
id: "wota"

View File

@@ -0,0 +1,25 @@
sections:
- title: "DEC64 Numbers"
page: "/docs/spec/dec64/"
id: "dec64"
- title: "Value Representation"
page: "/docs/spec/values/"
id: "values"
- title: "Object Types"
page: "/docs/spec/objects/"
id: "objects"
- title: "Stone Memory"
page: "/docs/spec/stone/"
id: "stone"
- title: "Garbage Collection"
page: "/docs/spec/gc/"
id: "gc"
- title: "Bytecode VM"
page: "/docs/spec/bytecode/"
id: "bytecode"
- title: "Register VM"
page: "/docs/spec/mach/"
id: "mach"
- title: "Mcode IR"
page: "/docs/spec/mcode/"
id: "mcode"

45
website/hugo.toml Normal file
View File

@@ -0,0 +1,45 @@
baseURL = 'https://pit-lang.org/'
languageCode = 'en-us'
title = 'ƿit'
theme = 'knr'
[markup]
[markup.highlight]
noClasses = false
style = 'monokailight'
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true
[menus]
[[menus.main]]
name = 'Getting Started'
pageRef = '/start/'
weight = 10
[[menus.main]]
name = 'Manual'
pageRef = '/manual/'
weight = 20
[[menus.main]]
name = 'Spec'
pageRef = '/spec/'
weight = 30
[[menus.main]]
name = 'CLI'
pageRef = '/cli/'
weight = 40
[[menus.main]]
name = 'Contributing'
pageRef = '/contributing/'
weight = 50
[module]
[[module.mounts]]
source = "content"
target = "content"
[[module.mounts]]
source = "../docs"
target = "content/docs"
[params]
description = 'An actor-based scripting language for building concurrent applications.'

View File

@@ -1,6 +0,0 @@
site_name: Cell
docs_dir: ../docs
theme:
name: mkdocs
color_mode: auto

View File

@@ -1 +0,0 @@
mkdocs==1.6.1

13
website/static/_headers Normal file
View File

@@ -0,0 +1,13 @@
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/fonts/*
Cache-Control: public, max-age=31536000, immutable
/images/*
Cache-Control: public, max-age=86400
/css/*
Cache-Control: public, max-age=86400

View File

@@ -0,0 +1,8 @@
/docs/ /manual/ 301
/docs/language/ /manual/#language 301
/docs/actors/ /manual/#actors 301
/docs/packages/ /manual/#packages 301
/docs/functions/ /manual/#functions 301
/docs/library/ /manual/#library 301
/docs/cli/ /cli/ 301
/docs/c-modules/ /manual/#c-modules 301

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
<head>
{{ partial "head.html" . }}
</head>
<body>
{{ partial "header.html" . }}
<main>
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer.html" . }}
</body>
</html>

View File

@@ -0,0 +1,6 @@
{{ define "main" }}
<article class="content-single">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
{{ end }}

View File

@@ -0,0 +1,6 @@
{{ define "main" }}
<article class="content-single">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
{{ end }}

View File

@@ -0,0 +1,9 @@
{{ define "main" }}
<div class="docs-layout">
{{ partial "nav-docs.html" . }}
<article class="docs-content">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,9 @@
{{ define "main" }}
<div class="docs-layout">
{{ partial "nav-docs.html" . }}
<article class="docs-content">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
</div>
{{ end }}

View File

@@ -0,0 +1,13 @@
{{ define "main" }}
<div class="home">
<div class="hero">
<div class="wynn">ƿ</div>
<h1 class="hero-title">ƿit</h1>
<p class="hero-tagline">An actor-based language for building concurrent applications.</p>
</div>
<div class="home-content">
{{ .Content }}
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,19 @@
{{ define "main" }}
<div class="longform-layout">
<nav class="toc-nav" id="toc-nav">
<h3>Contents</h3>
<ul id="toc-list"></ul>
</nav>
<article class="longform-content" id="longform-content">
<h1>{{ .Title }}</h1>
{{ range .Site.Data.manual_sections.sections }}
<section id="{{ .id }}" data-toc-title="{{ .title }}">
{{ with $.Site.GetPage .page }}
{{ .Content }}
{{ end }}
</section>
{{ end }}
</article>
</div>
<script src="/js/toc.js"></script>
{{ end }}

View File

@@ -0,0 +1,3 @@
<footer class="site-footer">
<p>ƿit &mdash; an actor-based scripting language</p>
</footer>

View File

@@ -0,0 +1,9 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }}</title>
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<link rel="icon" href="/images/favicon.gif" type="image/gif">
<link rel="preload" href="/fonts/charter-regular.woff" as="font" type="font/woff" crossorigin>
<link rel="preload" href="/fonts/jetbrains-mono-regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/syntax.css">

View File

@@ -0,0 +1,10 @@
<header class="site-header">
<nav class="site-nav">
<a href="/" class="site-logo">ƿit</a>
<div class="nav-links">
{{ range .Site.Menus.main }}
<a href="{{ .URL }}"{{ if $.IsMenuCurrent "main" . }} class="active"{{ end }}>{{ .Name }}</a>
{{ end }}
</div>
</nav>
</header>

View File

@@ -0,0 +1,16 @@
<nav class="docs-nav">
<h3><a href="/docs/">Documentation</a></h3>
{{ $current := . }}
{{ range .Site.Data.docs_nav.sections }}
<div class="nav-section">
<h4>{{ .title }}</h4>
<ul>
{{ range .pages }}
<li{{ if eq $current.RelPermalink .url }} class="active"{{ end }}>
<a href="{{ .url }}">{{ .title }}</a>
</li>
{{ end }}
</ul>
</div>
{{ end }}
</nav>

View File

@@ -0,0 +1,19 @@
{{ define "main" }}
<div class="longform-layout">
<nav class="toc-nav" id="toc-nav">
<h3>Contents</h3>
<ul id="toc-list"></ul>
</nav>
<article class="longform-content" id="longform-content">
<h1>{{ .Title }}</h1>
{{ range .Site.Data.spec_sections.sections }}
<section id="{{ .id }}" data-toc-title="{{ .title }}">
{{ with $.Site.GetPage .page }}
{{ .Content }}
{{ end }}
</section>
{{ end }}
</article>
</div>
<script src="/js/toc.js"></script>
{{ end }}

View File

@@ -0,0 +1,13 @@
{{ define "main" }}
<div class="longform-layout">
<nav class="toc-nav" id="toc-nav">
<h3>Contents</h3>
<ul id="toc-list"></ul>
</nav>
<article class="longform-content" id="longform-content">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
</div>
<script src="/js/toc.js"></script>
{{ end }}

View File

@@ -0,0 +1,587 @@
/* K&R aesthetic — cream paper, serif fonts, sparse layout */
/* ---- Fonts ---- */
/* TODO: Add Junicode woff2 for the ƿ glyph (psb1558/Junicode-font on GitHub) */
@font-face {
font-family: 'Charter';
src: url('/fonts/charter-regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Charter';
src: url('/fonts/charter-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('/fonts/jetbrains-mono-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* ---- Reset ---- */
*, *::before, *::after {
box-sizing: border-box;
}
body, h1, h2, h3, h4, p, ul, ol, figure, blockquote {
margin: 0;
padding: 0;
}
/* ---- Base ---- */
:root {
--bg: #FAF8F1;
--bg-code: #F2EFE4;
--text: #2C2C2C;
--text-secondary: #5C5C5C;
--accent: #4A3728;
--accent-hover: #2C2C2C;
--border: #D4CFC4;
--font-body: 'Charter', Georgia, 'Times New Roman', serif;
--font-code: 'JetBrains Mono', 'Source Code Pro', 'Menlo', monospace;
--font-wynn: 'Junicode', 'Charter', Georgia, serif;
--content-width: 720px;
--sidebar-width: 180px;
}
html {
font-size: 18px;
}
body {
font-family: var(--font-body);
color: var(--text);
background: var(--bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* ---- Typography ---- */
h1, h2, h3, h4 {
font-family: var(--font-body);
font-weight: 700;
line-height: 1.3;
color: var(--text);
}
h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
h2 {
font-size: 1.4rem;
margin-top: 2.5rem;
margin-bottom: 0.75rem;
padding-bottom: 0.3rem;
border-bottom: 1px solid var(--border);
}
h3 {
font-size: 1.15rem;
margin-top: 2rem;
margin-bottom: 0.5rem;
}
h4 {
font-size: 1rem;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
p {
margin-bottom: 1rem;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
color: var(--accent-hover);
text-decoration: underline;
}
strong {
font-weight: 700;
}
em {
font-style: italic;
}
hr {
border: none;
border-top: 1px solid var(--border);
margin: 2rem 0;
}
ul, ol {
padding-left: 1.5em;
margin-bottom: 1rem;
}
li {
margin-bottom: 0.25rem;
}
li > ul, li > ol {
margin-top: 0.25rem;
margin-bottom: 0;
}
blockquote {
border-left: 3px solid var(--border);
padding-left: 1rem;
color: var(--text-secondary);
margin: 1rem 0;
}
img {
max-width: 100%;
height: auto;
}
/* ---- Code ---- */
code {
font-family: var(--font-code);
font-size: 0.85em;
background: var(--bg-code);
padding: 0.15em 0.35em;
border-radius: 3px;
}
pre {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1rem 1.25rem;
overflow-x: auto;
margin: 1rem 0 1.5rem;
line-height: 1.5;
}
pre code {
background: none;
padding: 0;
font-size: 0.85rem;
}
/* ---- Tables ---- */
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0 1.5rem;
font-size: 0.95rem;
}
th, td {
padding: 0.5rem 0.75rem;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
font-weight: 700;
border-bottom: 2px solid var(--border);
}
/* ---- Site Header ---- */
.site-header {
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.site-nav {
max-width: calc(var(--content-width) + var(--sidebar-width) + 2rem);
margin: 0 auto;
padding: 0.75rem 1.5rem;
display: flex;
align-items: baseline;
gap: 2rem;
}
.site-logo {
font-family: var(--font-wynn);
font-size: 1.5rem;
font-weight: 700;
color: var(--text);
text-decoration: none;
}
.site-logo:hover {
text-decoration: none;
color: var(--accent);
}
.nav-links {
display: flex;
gap: 1.5rem;
}
.nav-links a {
color: var(--text-secondary);
font-size: 0.9rem;
}
.nav-links a:hover,
.nav-links a.active {
color: var(--text);
text-decoration: none;
}
/* ---- Site Footer ---- */
.site-footer {
border-top: 1px solid var(--border);
padding: 1.5rem;
text-align: center;
color: var(--text-secondary);
font-size: 0.85rem;
margin-top: 3rem;
}
/* ---- Content ---- */
.content-single {
max-width: var(--content-width);
margin: 2rem auto;
padding: 0 1.5rem;
}
/* ---- Home Page ---- */
.home {
max-width: var(--content-width);
margin: 0 auto;
padding: 0 1.5rem;
}
.hero {
text-align: center;
padding: 3rem 0 2rem;
}
.wynn {
font-family: var(--font-wynn);
font-size: 18rem;
line-height: 1;
color: var(--text);
margin-bottom: 0;
}
.hero-title {
font-family: var(--font-wynn);
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.hero-tagline {
font-size: 1.15rem;
color: var(--text-secondary);
max-width: 480px;
margin: 0 auto;
}
.home-content {
padding-bottom: 3rem;
}
.home-content h2 {
margin-top: 2rem;
}
.home-content .home-links {
display: flex;
gap: 1.5rem;
margin-top: 2rem;
justify-content: center;
}
.home-content .home-links a {
font-size: 1.1rem;
font-weight: 700;
}
.home-art {
text-align: center;
margin: 2rem 0;
}
.home-art img {
max-width: 280px;
}
/* ---- Docs Layout ---- */
.docs-layout {
max-width: calc(var(--content-width) + var(--sidebar-width) + 3rem);
margin: 0 auto;
display: flex;
gap: 2rem;
padding: 0 1.5rem;
}
.docs-nav {
width: var(--sidebar-width);
flex-shrink: 0;
padding-top: 2rem;
position: sticky;
top: 0;
max-height: 100vh;
overflow-y: auto;
}
.docs-nav h3 {
font-size: 1rem;
margin-bottom: 1rem;
}
.docs-nav h3 a {
color: var(--text);
}
.docs-nav h4 {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
margin-top: 1.25rem;
margin-bottom: 0.25rem;
}
.docs-nav ul {
list-style: none;
padding: 0;
margin: 0;
}
.docs-nav li {
margin: 0;
}
.docs-nav li a {
display: block;
padding: 0.2rem 0;
font-size: 0.9rem;
color: var(--text-secondary);
}
.docs-nav li a:hover {
color: var(--text);
text-decoration: none;
}
.docs-nav li.active a {
color: var(--text);
font-weight: 700;
}
.docs-content {
flex: 1;
min-width: 0;
padding: 2rem 0 3rem;
}
.docs-content h1 {
margin-bottom: 1.5rem;
}
/* ---- Responsive ---- */
@media (max-width: 768px) {
html {
font-size: 16px;
}
.wynn {
font-size: 10rem;
}
.hero-title {
font-size: 2rem;
}
.docs-layout {
flex-direction: column;
}
.docs-nav {
width: 100%;
position: static;
max-height: none;
padding-top: 1rem;
border-bottom: 1px solid var(--border);
padding-bottom: 1rem;
}
.nav-section {
display: inline-block;
margin-right: 1.5rem;
vertical-align: top;
}
pre {
font-size: 0.8rem;
}
.site-nav {
padding: 0.75rem 1rem;
}
}
@media (max-width: 480px) {
.wynn {
font-size: 7rem;
}
.nav-section {
display: block;
margin-right: 0;
}
}
/* ---- Longform Layout (Manual / Spec) ---- */
.longform-layout {
max-width: calc(var(--content-width) + var(--sidebar-width) + 3rem);
margin: 0 auto;
display: flex;
gap: 2rem;
padding: 0 1.5rem;
}
.longform-content {
flex: 1;
min-width: 0;
padding: 2rem 0 3rem;
}
.longform-content h1 {
margin-bottom: 1.5rem;
}
.longform-content section {
margin-bottom: 3rem;
}
.toc-nav {
width: var(--sidebar-width);
flex-shrink: 0;
padding-top: 2rem;
position: sticky;
top: 0;
max-height: 100vh;
overflow-y: auto;
}
.toc-nav h3 {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
.toc-nav ul {
list-style: none;
padding: 0;
margin: 0;
}
.toc-nav li.toc-section {
margin: 0;
}
.toc-nav li.toc-section > a {
display: inline;
font-size: 0.75rem;
color: var(--text-secondary);
line-height: 1.3;
}
.toc-nav li.toc-section > a:hover {
color: var(--text);
text-decoration: none;
}
.toc-nav li.toc-section > a.active {
color: var(--text);
font-weight: 700;
}
.toc-arrow {
display: inline-block;
width: 0.7em;
font-size: 0.65rem;
color: var(--text-secondary);
cursor: pointer;
transition: transform 0.15s;
user-select: none;
}
.toc-section.open > .toc-arrow {
transform: rotate(90deg);
}
.toc-sub {
display: none;
padding-left: 1rem;
margin: 0.1rem 0 0.2rem;
}
.toc-section.open > .toc-sub {
display: block;
}
.toc-sub li a {
display: block;
font-size: 0.7rem;
color: var(--text-secondary);
padding: 0.05rem 0;
line-height: 1.3;
}
.toc-sub li a:hover {
color: var(--text);
text-decoration: none;
}
/* ---- Donate Section ---- */
.donate-section {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1.5rem 2rem;
text-align: center;
margin: 2rem 0;
}
.donate-section h2 {
border-bottom: none;
margin-top: 0;
padding-bottom: 0;
}
.donate-section p {
color: var(--text-secondary);
margin-bottom: 0.5rem;
}
@media (max-width: 768px) {
.longform-layout {
flex-direction: column;
}
.toc-nav {
width: 100%;
position: static;
max-height: none;
padding-top: 1rem;
border-bottom: 1px solid var(--border);
padding-bottom: 1rem;
}
}

View File

@@ -0,0 +1,124 @@
/* Muted, near-monochrome syntax highlighting — K&R aesthetic */
/* Background & default text */
.highlight pre {
background: #F2EFE4;
color: #2C2C2C;
}
/* Keywords: bold, same color */
.highlight .k,
.highlight .kc,
.highlight .kd,
.highlight .kn,
.highlight .kp,
.highlight .kr,
.highlight .kt {
font-weight: bold;
color: #2C2C2C;
}
/* Strings: slightly lighter */
.highlight .s,
.highlight .s1,
.highlight .s2,
.highlight .sa,
.highlight .sb,
.highlight .sc,
.highlight .dl,
.highlight .se,
.highlight .sh,
.highlight .si,
.highlight .sx {
color: #5C5C5C;
}
/* Comments: italic, lighter */
.highlight .c,
.highlight .c1,
.highlight .ch,
.highlight .cm,
.highlight .cp,
.highlight .cpf,
.highlight .cs {
color: #8B8B8B;
font-style: italic;
}
/* Numbers */
.highlight .m,
.highlight .mb,
.highlight .mf,
.highlight .mh,
.highlight .mi,
.highlight .mo {
color: #2C2C2C;
}
/* Functions */
.highlight .nf,
.highlight .fm {
color: #2C2C2C;
}
/* Operators */
.highlight .o,
.highlight .ow {
color: #2C2C2C;
}
/* Variables and names */
.highlight .n,
.highlight .na,
.highlight .nb,
.highlight .nc,
.highlight .nd,
.highlight .ne,
.highlight .ni,
.highlight .nl,
.highlight .nn,
.highlight .no,
.highlight .nt,
.highlight .nv,
.highlight .bp {
color: #2C2C2C;
}
/* Punctuation */
.highlight .p {
color: #2C2C2C;
}
/* Template strings */
.highlight .sa {
color: #5C5C5C;
}
/* Built-in constants */
.highlight .kc {
color: #2C2C2C;
font-weight: bold;
}
/* Regular expressions */
.highlight .sr {
color: #5C5C5C;
}
/* Errors */
.highlight .err {
color: #2C2C2C;
background: none;
border: none;
}
/* Line numbers */
.highlight .ln {
color: #8B8B8B;
}
/* Generic */
.highlight .gd { color: #5C5C5C; }
.highlight .gi { color: #2C2C2C; }
.highlight .ge { font-style: italic; }
.highlight .gs { font-weight: bold; }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,99 @@
(function() {
var container = document.getElementById('longform-content');
var tocList = document.getElementById('toc-list');
if (!container || !tocList) return;
var sections = container.querySelectorAll('section[id]');
var allLinks = [];
if (sections.length > 0) {
// composite page (manual, spec): section-level ToC with disclosure
sections.forEach(function(sec) {
var li = document.createElement('li');
li.className = 'toc-section';
var a = document.createElement('a');
a.href = '#' + sec.id;
a.textContent = sec.dataset.tocTitle || sec.id;
var h2s = sec.querySelectorAll('h2');
if (h2s.length > 0) {
var toggle = document.createElement('span');
toggle.className = 'toc-arrow';
toggle.textContent = '\u25B8';
li.appendChild(toggle);
li.appendChild(a);
var sub = document.createElement('ul');
sub.className = 'toc-sub';
h2s.forEach(function(h) {
if (!h.id) return;
var sli = document.createElement('li');
var sa = document.createElement('a');
sa.href = '#' + h.id;
sa.textContent = h.textContent;
sli.appendChild(sa);
sub.appendChild(sli);
});
li.appendChild(sub);
toggle.addEventListener('click', function(e) {
e.preventDefault();
li.classList.toggle('open');
});
} else {
li.appendChild(a);
}
allLinks.push({el: sec, link: a, parent: li});
tocList.appendChild(li);
});
var currentSection = null;
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
if (currentSection) currentSection.classList.remove('active');
var item = allLinks.find(function(i) { return i.el === entry.target; });
if (item) {
item.link.classList.add('active');
currentSection = item.link;
item.parent.classList.add('open');
}
}
});
}, {rootMargin: '0px 0px -70% 0px', threshold: 0});
sections.forEach(function(sec) { observer.observe(sec); });
} else {
// standalone page (cli, contributing): flat h2-only ToC
var headings = container.querySelectorAll('h2');
if (headings.length === 0) return;
headings.forEach(function(h) {
var li = document.createElement('li');
li.className = 'toc-section';
var a = document.createElement('a');
a.href = '#' + h.id;
a.textContent = h.textContent;
li.appendChild(a);
tocList.appendChild(li);
allLinks.push({el: h, link: a});
});
var current = null;
var obs = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
if (current) current.classList.remove('active');
var item = allLinks.find(function(i) { return i.el === entry.target; });
if (item) {
item.link.classList.add('active');
current = item.link;
}
}
});
}, {rootMargin: '0px 0px -70% 0px', threshold: 0});
headings.forEach(function(h) { obs.observe(h); });
}
})();