diff --git a/.gitignore b/.gitignore index 8b90ed00..cfcba86d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index 94ababe6..f2bfe99c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,25 +1,123 @@ -# Code style -All code is done with 2 spaces for indentation. +# ƿit (pit) Language Project -For cell script and its integration files, objects are preferred over classes, and preferrably limited use of prototypes, make objects sendable between actors (.ce files). +## Building -## cell script format -Cell script files end in .ce or .cm. Cell script is similar to Javascript but with some differences. +Recompile after changes: `make` +Bootstrap from scratch (first time): `make bootstrap` +Run `cell --help` to see all CLI flags. -Variables are delcared with 'var'. Var behaves like let. -Constants are declared with 'def'. -!= and == are strict, there is no !== or ===. -There is no undefined, only null. -There are no classes, only objects and prototypes. -Prefer backticks for string interpolation. Otherwise, convering non strings with the text() function is required. -Everything should be lowercase. +## Code Style -There are no arraybuffers, only blobs, which work with bits. They must be stoned like stone(blob) before being read from. +All code uses 2 spaces for indentation. K&R style for C and Javascript. -## c format -For cell script integration files, everything should be declared static that can be. Most don't have headers at all. Files in a package are not shared between packages. +## ƿit Script Quick Reference -There is no undefined, so JS_IsNull and JS_NULL should be used only. +ƿit script files: `.ce` (actors) and `.cm` (modules). The syntax is similar to JavaScript with important differences listed below. -## how module loading is done in cell script -Within a package, a c file, if using the correct macros (CELL_USE_FUNCS etc), will be loaded as a module with its name; so png.c inside ac package is loaded as /png, giving you access to its functions. \ No newline at end of file +### Key Differences from JavaScript + +- `var` (mutable) and `def` (constant) — no `let` or `const` +- `==` and `!=` are strict (no `===` or `!==`) +- No `undefined` — only `null` +- No classes — only objects and prototypes (`meme()`, `proto()`, `isa()`) +- No `for...in`, `for...of`, spread (`...`), rest params, or default params +- No named function declarations — use `var fn = function() {}` or arrow functions +- Variables must be declared at function body level only (not in if/while/for/blocks) +- All variables must be initialized at declaration (`var x` alone is an error; use `var x = null`) +- No `try`/`catch`/`throw` — use `disrupt`/`disruption` +- No arraybuffers — only `blob` (works with bits; must `stone(blob)` before reading) +- Identifiers can contain `?` and `!` (e.g., `nil?`, `set!`, `is?valid`) +- Prefer backticks for string interpolation; otherwise use `text()` to convert non-strings +- Everything should be lowercase + +### Intrinsic Functions (always available, no `use()` needed) + +The creator functions are **polymorphic** — behavior depends on argument types: + +- `array(number)` — create array of size N filled with null +- `array(number, value_or_fn)` — create array with initial values +- `array(array)` — copy array +- `array(array, fn)` — map +- `array(array, array)` — concatenate +- `array(array, from, to)` — slice +- `array(record)` — get keys as array of text +- **`array(text)` — split text into individual characters** (e.g., `array("hello")` → `["h","e","l","l","o"]`) +- `array(text, separator)` — split by separator +- `array(text, length)` — split into chunks of length + +- `text(array, separator)` — join array into text +- `text(number)` or `text(number, radix)` — number to text +- `text(text, from, to)` — substring + +- `number(text)` or `number(text, radix)` — parse text to number +- `number(logical)` — boolean to number + +- `record(record)` — copy +- `record(record, another)` — merge +- `record(array_of_keys)` — create record from keys + +Other key intrinsics: `length()`, `stone()`, `is_stone()`, `print()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()` + +Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc. + +### Standard Library (loaded with `use()`) + +- `blob` — binary data (bits, not bytes) +- `time` — time constants and conversions +- `math` — trig, logarithms, roots (`math/radians`, `math/turns`) +- `json` — JSON encoding/decoding +- `random` — random number generation + +### Actor Model + +- `.ce` files are actors (independent execution units, don't return values) +- `.cm` files are modules (return a value, cached and frozen) +- Actors never share memory; communicate via `$send()` message passing +- Actor intrinsics start with `$`: `$me`, `$stop()`, `$send()`, `$start()`, `$delay()`, `$receiver()`, `$clock()`, `$portal()`, `$contact()`, `$couple()`, `$unneeded()`, `$connection()`, `$time_limit()` + +### Requestors (async composition) + +`sequence()`, `parallel()`, `race()`, `fallback()` — compose asynchronous operations. See docs/requestors.md. + +### Error Handling + +```javascript +var fn = function() { + disrupt // bare keyword, no value +} disruption { + // handle error; can re-raise with disrupt +} +``` + +### Push/Pop Syntax + +```javascript +var a = [1, 2] +a[] = 3 // push: [1, 2, 3] +var v = a[] // pop: v is 3, a is [1, 2] +``` + +## C Integration + +- Declare everything `static` that can be +- Most files don't have headers; files in a package are not shared between packages +- No undefined in C API: use `JS_IsNull` and `JS_NULL` only +- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('/png')`) + +## Project Layout + +- `source/` — C source for the cell runtime and CLI +- `docs/` — master documentation (Markdown), reflected on the website +- `website/` — Hugo site; theme at `website/themes/knr/` +- `internal/` — internal ƿit scripts (engine.cm etc.) +- `packages/` — core packages +- `Makefile` — build system (`make` to rebuild, `make bootstrap` for first build) + +## Documentation + +The `docs/` folder is the single source of truth. The website at `website/` mounts it via Hugo. Key files: +- `docs/language.md` — language syntax reference +- `docs/functions.md` — all built-in intrinsic functions +- `docs/actors.md` — actor model and actor intrinsics +- `docs/requestors.md` — async requestor pattern +- `docs/library/*.md` — intrinsic type reference (text, number, array, object) and standard library modules diff --git a/docs/.pages b/docs/.pages deleted file mode 100644 index 883fb269..00000000 --- a/docs/.pages +++ /dev/null @@ -1,9 +0,0 @@ -nav: - - index.md - - cellscript.md - - actors.md - - packages.md - - cli.md - - c-modules.md - - Standard Library: library - diff --git a/docs/_index.md b/docs/_index.md new file mode 100644 index 00000000..ea4edd8e --- /dev/null +++ b/docs/_index.md @@ -0,0 +1,90 @@ +--- +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 +print("Hello, ƿit!") +$stop() +``` + +```bash +pit hello +``` + +## Language + +- [**ƿit Language**](/docs/language/) — syntax, types, and operators +- [**Actors and Modules**](/docs/actors/) — the execution model +- [**Requestors**](/docs/requestors/) — asynchronous composition +- [**Packages**](/docs/packages/) — code organization and sharing + +## Reference + +- [**Built-in Functions**](/docs/functions/) — intrinsics reference +- [text](/docs/library/text/) — text 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, prototypes, and serialization + +## Standard Library + +Modules loaded with `use()`: + +- [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 + +## Tools + +- [**Command Line**](/docs/cli/) — the `pit` tool +- [**Writing C Modules**](/docs/c-modules/) — native extensions + +## 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/`. + +## Development + +After making changes, recompile with: + +```bash +make +``` + +Run `cell --help` to see all available CLI flags. diff --git a/docs/actors.md b/docs/actors.md index 21f7a0ec..e25ff919 100644 --- a/docs/actors.md +++ b/docs/actors.md @@ -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 @@ -21,13 +26,13 @@ A module is a script that **returns a value**. The returned value is cached and // math_utils.cm var math = use('math/radians') -function distance(x1, y1, x2, y2) { +var distance = function(x1, y1, x2, y2) { var dx = x2 - x1 var dy = y2 - y1 return math.sqrt(dx * dx + dy * dy) } -function midpoint(x1, y1, x2, y2) { +var midpoint = function(x1, y1, x2, y2) { return { x: (x1 + x2) / 2, y: (y1 + y2) / 2 @@ -60,12 +65,12 @@ An actor is a script that **does not return a value**. It runs as an independent ```javascript // worker.ce -log.console("Worker started") +print("Worker started") -$on_message = function(msg) { - log.console("Received:", msg) +$receiver(function(msg, reply) { + print("Received:", msg) // Process message... -} +}) ``` **Key properties:** @@ -83,7 +88,7 @@ Actors have access to special functions prefixed with `$`: Reference to the current actor. ```javascript -log.console($me) // actor reference +print($me) // actor reference ``` ### $stop() @@ -100,7 +105,7 @@ Send a message to another actor. ```javascript $send(other_actor, {type: "ping", data: 42}, function(reply) { - log.console("Got reply:", reply) + print("Got reply:", reply) }) ``` @@ -112,7 +117,7 @@ Start a new actor from a script. ```javascript $start(function(new_actor) { - log.console("Started:", new_actor) + print("Started:", new_actor) }, "worker") ``` @@ -122,7 +127,7 @@ Schedule a callback after a delay. ```javascript $delay(function() { - log.console("5 seconds later") + print("5 seconds later") }, 5) ``` @@ -169,19 +174,47 @@ $contact(function(connection) { ### $time_limit(requestor, seconds) -Wrap a requestor with a timeout. +Wrap a requestor with a timeout. See [Requestors](/docs/requestors/) for details. ```javascript $time_limit(my_requestor, 10) // 10 second timeout ``` +### $couple(actor) + +Couple the current actor to another actor. When the coupled actor dies, the current actor also dies. Coupling is automatic between an actor and its overling (parent). + +```javascript +$couple(other_actor) +``` + +### $unneeded(callback, seconds) + +Schedule the actor for removal after a specified time. + +```javascript +$unneeded(function() { + // cleanup before removal +}, 30) +``` + +### $connection(callback, actor, config) + +Get information about the connection to another actor, such as latency, bandwidth, and activity. + +```javascript +$connection(function(info) { + print(info.latency) +}, other_actor, {}) +``` + ## 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': @@ -199,14 +232,14 @@ Files starting with underscore (`_helper.cm`) are private to the package. // main.ce - Entry point var config = use('config') -log.console("Starting application...") +print("Starting application...") $start(function(worker) { $send(worker, {task: "process", data: [1, 2, 3]}) }, "worker") $delay(function() { - log.console("Shutting down") + print("Shutting down") $stop() }, 10) ``` diff --git a/docs/c-modules.md b/docs/c-modules.md index d7a6261a..bf12e81e 100644 --- a/docs/c-modules.md +++ b/docs/c-modules.md @@ -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: - `` 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: @@ -224,7 +229,7 @@ A common pattern is to have a C file provide low-level functions and a `.cm` fil // vector.cm var native = this // C module passed as 'this' -function Vector(x, y) { +var Vector = function(x, y) { return {x: x, y: y} } @@ -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 diff --git a/docs/cellscript.md b/docs/cellscript.md deleted file mode 100644 index 1b102428..00000000 --- a/docs/cellscript.md +++ /dev/null @@ -1,288 +0,0 @@ -# Cell Language - -Cell is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics. - -## Basics - -### Variables and Constants - -```javascript -var x = 10 // mutable variable (block-scoped like let) -def PI = 3.14159 // constant (cannot be reassigned) -``` - -### Data Types - -Cell has six fundamental types: - -- **number** — DEC64 decimal floating point (no rounding errors) -- **text** — Unicode strings -- **logical** — `true` or `false` -- **null** — the absence of a value (no `undefined`) -- **array** — ordered, numerically-indexed sequences -- **object** — key-value records with prototype inheritance -- **blob** — binary data (bits, not bytes) -- **function** — first-class callable values - -### Literals - -```javascript -// Numbers -42 -3.14 -1_000_000 // underscores for readability - -// Text -"hello" -'world' -`template ${x}` // string interpolation - -// Logical -true -false - -// Null -null - -// Arrays -[1, 2, 3] -["a", "b", "c"] - -// Objects -{name: "cell", version: 1} -{x: 10, y: 20} -``` - -### Operators - -```javascript -// Arithmetic -+ - * / % -** // exponentiation - -// Comparison (always strict) -== // equals (like === in JS) -!= // not equals (like !== in JS) -< > <= >= - -// Logical -&& || ! - -// Assignment -= += -= *= /= -``` - -### Control Flow - -```javascript -// Conditionals -if (x > 0) { - log.console("positive") -} else if (x < 0) { - log.console("negative") -} else { - log.console("zero") -} - -// Ternary -var sign = x > 0 ? 1 : -1 - -// Loops -for (var i = 0; i < 10; i++) { - log.console(i) -} - -for (var item of items) { - log.console(item) -} - -for (var key in obj) { - log.console(key, obj[key]) -} - -while (condition) { - // body -} - -// Control -break -continue -return value -throw "error message" -``` - -### Functions - -```javascript -// Named function -function add(a, b) { - return a + b -} - -// Anonymous function -var multiply = function(a, b) { - return a * b -} - -// Arrow function -var square = x => x * x -var sum = (a, b) => a + b - -// Rest parameters -function log_all(...args) { - for (var arg of args) log.console(arg) -} - -// Default parameters -function greet(name, greeting = "Hello") { - return `${greeting}, ${name}!` -} -``` - -All closures capture `this` (like arrow functions in JavaScript). - -## Arrays - -Arrays are **distinct from objects**. They are ordered, numerically-indexed sequences. You cannot add arbitrary string keys to an array. - -```javascript -var arr = [1, 2, 3] -arr[0] // 1 -arr[2] = 10 // [1, 2, 10] -length(arr) // 3 - -// Array spread -var more = [...arr, 4, 5] // [1, 2, 10, 4, 5] -``` - -## Objects - -Objects are key-value records with prototype-based inheritance. - -```javascript -var point = {x: 10, y: 20} -point.x // 10 -point["y"] // 20 - -// Object spread -var point3d = {...point, z: 30} - -// Prototype inheritance -var colored_point = {__proto__: point, color: "red"} -colored_point.x // 10 (inherited) -``` - -### Prototypes - -```javascript -// Create object with prototype -var child = meme(parent) - -// Get prototype -var p = proto(child) - -// Check prototype chain -isa(child, parent) // true -``` - -## Immutability with Stone - -The `stone()` function makes values permanently immutable. - -```javascript -var config = stone({ - debug: true, - maxRetries: 3 -}) - -config.debug = false // Error! Stone objects cannot be modified -``` - -Stone is **deep** — all nested objects and arrays are also frozen. This cannot be reversed. - -```javascript -stone.p(value) // returns true if value is stone -``` - -## Built-in Functions - -### length(value) - -Returns the length of arrays (elements), text (codepoints), blobs (bits), or functions (arity). - -```javascript -length([1, 2, 3]) // 3 -length("hello") // 5 -length(function(a,b){}) // 2 -``` - -### use(path) - -Import a module. Returns the cached, stone value. - -```javascript -var math = use('math/radians') -var json = use('json') -``` - -### isa(value, type) - -Check type or prototype chain. - -```javascript -is_number(42) // true -is_text("hi") // true -is_array([1,2]) // true -is_object({}) // true -isa(child, parent) // true if parent is in prototype chain -``` - -### reverse(array) - -Returns a new array with elements in reverse order. - -```javascript -reverse([1, 2, 3]) // [3, 2, 1] -``` - -### logical(value) - -Convert to boolean. - -```javascript -logical(0) // false -logical(1) // true -logical("true") // true -logical("false") // false -logical(null) // false -``` - -## Logging - -```javascript -log.console("message") // standard output -log.error("problem") // error output -``` - -## Pattern Matching - -Cell supports regex patterns in string functions, but not standalone regex objects. - -```javascript -text.search("hello world", /world/) -replace("hello", /l/g, "L") -``` - -## Error Handling - -```javascript -try { - riskyOperation() -} catch (e) { - log.error(e) -} - -throw "something went wrong" -``` - -If an actor has an uncaught error, it crashes. diff --git a/docs/cli.md b/docs/cli.md index 3bcd753c..deabda04 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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 [arguments] +pit [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 # update specific package +pit update # update all packages +pit update # 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 # list dependencies of a package +pit list # list all installed packages +pit list # 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 # list files in specified package +pit ls # list files in current project +pit ls # 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 # run tests in specific package +pit test # run tests in current package +pit test all # run all tests +pit test # run tests in specific package ``` -### cell link +### pit link Manage local package links for development. ```bash -cell link add # link a package -cell link list # show all links -cell link delete # remove a link -cell link clear # remove all links +pit link add # link a package +pit link list # show all links +pit link delete # remove a link +pit link clear # remove all links ``` -### cell fetch +### pit fetch Fetch package sources without extracting. ```bash -cell fetch +pit fetch ``` -### 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 +pit help +pit help ``` ## 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 diff --git a/docs/functions.md b/docs/functions.md index 5cb3c830..4d4bcf55 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -1,921 +1,513 @@ -# Cell Functions +--- +title: "Built-in Functions" +description: "Intrinsic constants and functions" +weight: 60 +type: "docs" +--- -The intrinsics are constants and functions that are built into the language. The use statement is not needed to access them. +The intrinsics are constants and functions that are built into the language. The `use` statement is not needed to access them. -A programmer is not obliged to consult the list of intrinsics before naming a new variable or input. -New intrinsics may be added to Misty without breaking existing programs. -Constants +A programmer is not obliged to consult the list of intrinsics before naming a new variable or input. New intrinsics may be added to ƿit without breaking existing programs. -false +## Constants -This is the value of 1 = 0. The false value is one of the two logical values. +### false -true +The value of `1 == 0`. One of the two logical values. -This is the value of 1 = 1. The true value is one of the two logical values. +### true -null +The value of `1 == 1`. One of the two logical values. -This is the value of 1 / 0. The null value is an empty immutable object. All attempts to obtain a value from null by refinement will produce null. +### null + +The value of `1 / 0`. The null value is an empty immutable object. All attempts to obtain a value from null by refinement will produce null. Any attempt to modify null will disrupt. Any attempt to call null as a function will disrupt. -null is the value of missing input values, missing fields in records, and invalid numbers. The |default operator can detect the presence of null and substitute another value. +null is the value of missing input values, missing fields in records, and invalid numbers. -pi +### pi -This is an approximation of the circle expression circumference / diameter, or to be precisely approximate, 3.1415926535897932. +An approximation of circumference / diameter: 3.1415926535897932. -Creator Functions +## Creator Functions -The creator functions are used to make new objects. Some of them can take various types. All of these functions can return null if their inputs are not suitable. +The creator functions are **polymorphic** — they examine the types of their arguments to decide what to do. The first argument's type selects the behavior. All return null if their inputs are not suitable. -Array +### array -array(number) +The `array` function creates arrays from various inputs. Its behavior depends on the type of the first argument: -Make an array. All of the elements are initialized to null. +**From a number** — create an array of that size: -number is a non-negative integer, the intended length of the new array. +`array(number)` — All elements are initialized to null. -array(number, initial_value) +`array(number, initial_value)` — All elements are initialized to initial_value. If initial_value is a function, it is called for each element. If the function has arity >= 1, it is passed the element number. -Make an array. All of the elements are initialized to initial_value. +```javascript +array(3) // [null, null, null] +array(3, 0) // [0, 0, 0] +array(5, i => i * 2) // [0, 2, 4, 6, 8] +``` -number is a non-negative integer, the intended length of the new array. +**From an array** — copy, map, concat, or slice: -If initial_value is a function, then the function is called for each element to produce initialization values. If the function has an arity of 1 or more, it is passed the element number. +`array(array)` — Copy. Make a mutable copy of the array. -array(array) +`array(array, function, reverse, exit)` — Map. Call the function with each element, collecting return values in a new array. The function is passed each element and its element number. -Copy. Make a mutable copy of the array. +```javascript +array([1, 2, 3], x => x * 10) // [10, 20, 30] +``` -array(array, function, reverse, exit) +If reverse is true, starts with the last element and works backwards. -Map. Call the function with each element of the array, collecting the return values in a new array. The function is passed each element and its element number. +If exit is not null, when the function returns the exit value, array returns early. The exit value is not stored into the new array. -function (element, element_nr) -If reverse is true, then it starts with the last element and works backwards. +`array(array, another_array)` — Concat. Produce a new array concatenating both. -If exit is not null, then when the function returns the exit value, then the array function returns early. The exit value will not be stored into the new array. If the array was processed normally, then the returned array will be shorter than the input array. If the array was processed in reverse, then the returned array will have the same length as the input array, and the first elements will be null. The elements in the new array that were not set due to an early exit will be set to null. +```javascript +array([1, 2], [3, 4]) // [1, 2, 3, 4] +``` -This is like the for function except that the return values are collected into a new array. +`array(array, from, to)` — Slice. Make a mutable copy of part of an array. If from is negative, add length(array). If to is negative, add length(array). -array(array, another_array) +```javascript +array([1, 2, 3, 4, 5], 1, 4) // [2, 3, 4] +array([1, 2, 3], -2) // [2, 3] +``` -Concat. Produce a new array that concatenates the array and another_array. +**From a record** — get keys: -array(array, from, to) +`array(record)` — Keys. Make an array containing all text keys in the record. -Slice. Make a mutable copy of all or part of an array. +```javascript +array({a: 1, b: 2}) // ["a", "b"] +``` -array: the array to copy +**From text** — split into characters or substrings: -from: the position at which to start copying. Default: 0, the beginning. If negative, add length(array). +`array(text)` — Split text into an array of individual characters (grapheme clusters). This is the standard way to iterate over characters. -to: the position at which to stop copying. Default: length(array), the end. If negative, add length(array). +```javascript +array("hello") // ["h", "e", "l", "l", "o"] +array("ƿit") // ["ƿ", "i", "t"] +``` -If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(array). +`array(text, separator)` — Split text by a separator string into an array of subtexts. -array(record) +```javascript +array("a,b,c", ",") // ["a", "b", "c"] +``` -Keys. Make an array containing all of the text keys in the record. The keys are not guaranteed to be in any particular order. +`array(text, length)` — Dice text into an array of subtexts of a given length. -array(text) +### logical -Split the text into grapheme clusters. A grapheme cluster is a Unicode codepoint and its contributing combining characters, if any. +```javascript +logical(0) // false +logical(false) // false +logical("false") // false +logical(null) // false +logical(1) // true +logical(true) // true +logical("true") // true +``` -array(text, separator) +All other values return null. -Split the text into an array of subtexts. The separator can be a text or pattern. +### number -array(text, length) +The `number` function converts values to numbers. Its behavior depends on the type of the first argument: -Dice the text into an array of subtexts of a given length. +`number(logical)` — Returns 1 or 0. -Logical +`number(number)` — Returns the number. -logical(value) +`number(text, radix)` — Convert text to number. Radix is 2 thru 37 (default: 10). -if value = 0 \/ value = false \/ value = "false" \/ value = null - return false -fi -if value = 1 \/ value = true \/ value = "true" - return true -fi -return null -Number +`number(text, format)` — Parse formatted numbers: -number(logical) +| Format | Radix | Separator | Decimal point | +|--------|-------|-----------|---------------| +| `""` | 10 | | .period | +| `"n"` | 10 | | .period | +| `"u"` | 10 | _underbar | .period | +| `"d"` | 10 | ,comma | .period | +| `"s"` | 10 | space | .period | +| `"v"` | 10 | .period | ,comma | +| `"l"` | 10 | locale | locale | +| `"b"` | 2 | | | +| `"o"` | 8 | | | +| `"h"` | 16 | | | +| `"j"` | auto | | | -The result is 1 or 0. +```javascript +number("123,456,789.10", "d") // 123456789.1 +number("123.456.789,10", "v") // 123456789.1 +number("666", "o") // 438 +number("666", "h") // 1638 +``` -number(number) +### text -The number is returned. +The `text` function converts values to text. Its behavior depends on the type of the first argument: -number(text, radix) +**From an array** — join elements into text: -Convert a text to a number. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10. +`text(array, separator)` — Convert array to text. Elements are concatenated with the separator (default: empty text). -number(text, format) +```javascript +text(["h", "e", "l", "l", "o"]) // "hello" +text(["a", "b", "c"], ", ") // "a, b, c" +``` -number format -format radix separator decimal point -"" 10 .period -"n" -"u" _underbar -"d" ,comma -"s" space -"v" .period ,comma -"l" dependent on locale -"i" _underbar -"b" 2 -"o" 8 -"h" 16 -"t" 32 -"j" 0x- base 16 -0o- base 8 -0b- base 2 -otherwise base 10 -The number function converts a text into a number. +**From a number** — format as text: -If it is unable to (possibly because of a formatting error), it returns null. The format character determines how the text is interpreted. If the format is not one of those listed, then null is returned. +`text(number, radix)` — Convert number to text. Radix is 2 thru 37 (default: 10). -Examples: +`text(number, format)` — Format a number as text: -assign result: number("123,456,789.10", "d") # result is 123456789.1 -assign result: number("123.456.789,10", "v") # result is 123456789.1 -assign result: number("123.456.789,10", "d") # result is null -assign result: number("123 456 789.10", "s") # result is 123456789.1 -assign result: number("12.350") # result is 12.35 -assign result: number("12.350", "v") # result is 12350 -assign result: number("12.350", "i") # result is null -assign result: number("666") # result is 666 -assign result: number("666", "b") # result is null -assign result: number("666", "o") # result is 438 -assign result: number("666", "h") # result is 1638 -assign result: number("666", "t") # result is 6342 -assign result: number("0666") # result is 666 -Record +**Real styles:** -record(record) +| Style | Description | Separator | Decimal | +|-------|-------------|-----------|---------| +| `"e"` | Scientific | | .period | +| `"n"` | Number | | .period | +| `"s"` | Space | space | .period | +| `"u"` | Underbar | _underbar | .period | +| `"d"` | Decimal | ,comma | .period | +| `"c"` | Comma | .period | ,comma | +| `"l"` | Locale | locale | locale | -Copy. Make a mutable copy. +**Integer styles:** -record(record, another_record) +| Style | Base | Separator | +|-------|------|-----------| +| `"i"` | 10 | _underbar | +| `"b"` | 2 | _underbar | +| `"o"` | 8 | _underbar | +| `"h"` | 16 | _underbar | +| `"t"` | 32 | _underbar | -Combine. Make a copy of a record, and then put all the fields of another_record into the copy. +The format text is: `separation_digit` + `style_letter` + `places_digits` -record(record, array_of_keys) +```javascript +var data = 123456789.1 +text(data) // "123456789.1" +text(data, "3s4") // "123 456 789.1000" +text(data, "d2") // "123,456,789.10" +text(data, "e") // "1.234567891e8" +text(data, "i") // "123456789" +text(data, "8b") // "111_01011011_11001101_00010101" +text(data, "h") // "75BCD15" +text(12, "4b8") // "0000_1100" +``` -Select. Make a new record containing only the fields that are named by the array_of_keys. +**From text** — extract a substring: -record(array_of_keys) +`text(text, from, to)` — Extract a substring. If from/to are negative, add length(text). -Set. Make a record using the array as the source of the keys. Each field value is true. +```javascript +var my_text = "miskatonic" +text(my_text, 0, 3) // "mis" +text(my_text, 5) // "tonic" +text(my_text, -3) // "nic" +``` -record(array_of_keys, value) +### record -Value Set. Make a record using the array as the source of the keys. Each field value is value. +The `record` function creates and manipulates records (objects). Its behavior depends on the type of the first argument: -record(array_of_keys, function) +**From a record** — copy, merge, or select: -Functional Value Set. Make a record using the array as the source of the keys. The function is called for each key, yielding the field values. +`record(record)` — Copy. Make a mutable copy. -Text +`record(record, another_record)` — Combine. Copy a record, then put all fields of another into the copy. -text(array) +`record(record, array_of_keys)` — Select. New record with only the named fields. -Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text. +**From an array of keys** — create a new record: -text(array, separator) +`record(array_of_keys)` — Set. New record using array as keys, each value is true. -Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text. The separator text is inserted between each piece. The default separator is the empty text. +`record(array_of_keys, value)` — Value Set. Each field value is value. -text(number, radix) +`record(array_of_keys, function)` — Functional Value Set. The function is called for each key to produce field values. -Convert a number to text. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10. +## Sensory Functions -text(number, format) +The sensory functions always return a logical value. In ƿit, they use the `is_` prefix: -The format of the format text is +```javascript +is_array([]) // true +is_blob(blob.make()) // true +is_function(x => x) // true +is_integer(42) // true +is_logical(true) // true +is_null(null) // true +is_number(3.14) // true +is_object({}) // true +is_text("hello") // true +``` +Additional type checks: `is_character`, `is_data`, `is_digit`, `is_false`, `is_fit`, `is_letter`, `is_lower`, `is_pattern`, `is_stone`, `is_true`, `is_upper`, `is_whitespace`. -format - format_separation format_style format_places +## Standard Functions +### abs(number) -format_separation - "" - digit +Absolute value. Returns null for non-numbers. +### apply(function, array) -format_style - 'b' - 'c' - 'e' - 'h' - 'i' - 'l' - 'n' - 'o' - 's' - 't' - 'u' +Execute the function, passing the array elements as input values. If the array is longer than the function's arity, it disrupts. +### ceiling(number, place) -format_places - digit digit - digit +Round up. If place is 0 or null, round to the smallest integer >= number. -Convert a number to formatted text. The text function converts a number to a text. It takes a format text input. +```javascript +ceiling(12.3775) // 13 +ceiling(12.3775, -2) // 12.38 +ceiling(-12.3775) // -12 +``` -A format text contains a style letter that controls how a text is produced from the number. It is optionally preceded by a separation digit, and optionally followed by a places digit. There are real styles and integer styles. If the format input is not a proper format text, then null is returned. +### character(value) -Separation is a character that is placed between digits to improve readability. If separation is 0, then there is no separation. If separation is 3, then a character is inserted before the quadrillions, trillions, billions, millions, and thousands. +If text, returns the first character. If a non-negative 32-bit integer, returns the character from that codepoint. -Places is the number of places to display after the decimal point (in real styles) or the minimum number of digits to display with zero-fill (in integer styles). If places is 0, then as many digits as necessary are displayed. Places can be zero or one or two digits. +### codepoint(text) -real style base default -separation default -places decimal -point separator -e exponential 10 0 0 .period -n number -s space 3 space -u underbar _underbar -d decimal 2 ,comma -c comma ,comma .period -l locale determined by the locale -The real format options are +Returns the codepoint number of the first character. -"e" uses scientific notation. One digit is placed before the decimal point, and all of the remaining digits after, followed by e and the exponent. +### ends_with(text, suffix) -"n" uses .period as the decimal point and no separator. It is the format used for numbers in Misty source programs and JSON. Scientific notation is used if the number value is extreme. +Returns `true` if the text ends with the given suffix. -"s" uses .period as the decimal point and a space as the separator. +```javascript +ends_with("hello.ce", ".ce") // true +ends_with("hello.cm", ".ce") // false +``` -"u" uses .period as the decimal point and _underbar as the separator. +### every(array, function) -"d" uses .period as the decimal point and ,comma as the separator. +Returns `true` if every element satisfies the predicate. -"c" uses ,comma as the decimal point and .period as the separator. +```javascript +every([2, 4, 6], x => x % 2 == 0) // true +every([2, 3, 6], x => x % 2 == 0) // false +``` -"l" depends on the locale to determine the characters to use as the decimal point and the separator. +### extract(text, pattern, from, to) -The optional places determines the number of digits after the decimal point. The default is determined by the format, as seen in the table. If the places is 0, then the number of decimal places will be the fewest to exactly display the number without truncating. If the places is larger, then the field is padded if necessary with trailing 0. +Match text to pattern. Returns a record of saved fields, or null if no match. -The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the style. +### fallback(requestor_array) -integer style base default -separation minimum -places separator -i integer 10 0 1 _underbar -b binary 2 -o octal 8 -h hexadecimal 16 -t Base32 32 -The integer styles first trunc the number. The fractional part of the number is ignored. The separation character is _underbar. +Returns a requestor that tries each requestor in order until one succeeds. Returns a cancel function. See [Requestors](/docs/requestors/) for usage. -The optional places determines the minimum number of digits to show. More leading 0 may be shown if necessary. The default is determined by the format, as seen in the table. +### filter(array, function) -The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the format, as seen in the table. +Call function for each element. When it returns true, the element is included in the new array. -Examples: +```javascript +filter([0, 1.25, 2, 3.5, 4, 5.75], is_integer) // [0, 2, 4] +``` -def data: 0123456789.1 -assign result: text(data) # result is "123456789.1" -assign result: text(data, "n") # result is "123456789.1" -assign result: text(data, "3s4") # result is "123 456 789.1000" -assign result: text(data, "s") # result is "123 456 789.1" -assign result: text(data, "d2") # result is "123,456,789.10" -assign result: text(data, "4d0") # result is "1,2345,6789.1" -assign result: text(data, "v2") # result is "123.456.789,10" -assign result: text(data, "e") # result is "1.234567891e8" -assign result: text(data, "e4") # result is "1.2345e8" -assign result: text(data, "i") # result is "123456789" -assign result: text(data, "8b") # result is "111_01011011_11001101_00010101" -assign result: text(data, "o") # result is "726746425" -assign result: text(data, "h") # result is "75BCD15" -assign result: text(data, "t") # result is "3NQK8N" -assign result: text(12) # result is "12" -assign result: text(12, 8) # result is "14" -assign result: text(12, 32) # result is "C" -assign result: text(12, "4b8") # result is "0000_1100" -assign result: text(12, "o3") # result is "014" -assign result: text(12, "h4") # result is "000C" -assign result: text(12, "t2") # result is "0C" -text(text) +### find(array, function, reverse, from) -Return the text. The text is not altered. +Call function for each element. If it returns true, return the element number. If the second argument is not a function, it is compared directly to elements. Returns null if not found. -text(text, from, to) - -Make a copy of part of a text. - -text: the text to copy. +```javascript +find([1, 2, 3], x => x > 1) // 1 +find([1, 2, 3], 2) // 1 +``` -from: the position at which to start copying. Default: 0, the beginning. If negative, add length(text). +### floor(number, place) -to: the position at which to stop copying. Default: length(text), the end. If negative, add length(text). +Round down. If place is 0 or null, round to the greatest integer <= number. -If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(text). +```javascript +floor(12.3775) // 12 +floor(12.3775, -2) // 12.37 +floor(-12.3775) // -13 +``` -assign my_text: "miskatonic" -text(my_text, 0, 3) # "mis" # the first 3 -text(my_text, 3, 6) # "kat" # from 3 to 6 -text(my_text, 5) # "tonic" # exclude the first 5 -text(my_text, 0, -4) # "miskat" # exclude the last 4 -text(my_text, -3) # "nic" # the last 3 -text(my_text, 0, 0) # "" -text(my_text, 10) # "" -text(my_text, 11) # null -text(my_text, 2, 1) # null -Sensory Functions - -The sensory functions end with ?question mark. They always return a logical value. - -actor?(value) - -Is the value an actor address object? - -actor?(me!) # true -actor?("actor") # false -actor?({actor: true}) # false -array?(value) - -Is the value an array? If the value is an array, the result is true. Otherwise, the result is false. - -array?(0) # false -array?({}) # false -array?([]) # true -not(array?([])) # false -array?(pattern (1- {letter digit "_-%"})) # false -array?(null) # false -array?("array") # false -blob?(value) - -Is the value a blob? If the value is a blob, the result is true. Otherwise, the result is false. - -blob?(0) # false -blob?("blob") # false -blob?(blob()) # true -character?(value) - -Is the value a character? If the value is a text with a length of 1, then the result is true. Otherwise, the result is false. - -character?(1) # false -character?("1") # true -character?("character") # false -character?("") # false -character?("\u{FFFE}") # true -character?() # false -character?("/q") # false -character?("\q") # true -character?(<<">>) # true -character?(null) # false -data?(value) - -Is the value data? If the value is a text, number, logical, array, blob, or record, then the result is true. If the value is a function, pattern, or null, the result is false. - -data?(0) # true -data?("") # true -data?(["0"]) # true -data?({}) # true -data?(null) # false -digit?(value) - -Is the value a digit? If the value is a text with a length of 1 and is one of the 10 digit characters, then the result is true. Otherwise, the result is false. - -digit?(0) # false -digit?("0") # true -digit?("9") # true -digit?("09") # false -digit?("digit") # false -digit?("") # false -digit?(1) # false -digit?(["0"]) # false -digit?("Z") # false -false?(value) - -Is the value false? - -fit?(number) - -Is the number a fit number? A number is a fit number if it is an integer that fits in 56 bits. All fit numbers are integers in the range -36028797018963968 thru 36028797018963967. Only fit numbers can be given to the fit functions. Misty has additional integers that are too big to fit. - -function?(value) - -Is the value a function? If the value is a function, then the result is true. Otherwise, the result is false. - -function?(0) # false -function?(function () (null)) # true -function?("function") # false -function?(null) # false -function?(function?) # true -integer?(value) - -Is the value an integer? If the value is a number and if its fraction part is zero, then the result is true. Otherwise, the result is false. - -integer?(0) # true -integer?(13 / 4) # false -integer?(16 / 4) # true -integer?(65.0000000) # true -integer?(65.0000001) # false -integer?(null) # false -integer?(true) # false -integer?(1) # true -integer?(36028797018963968) # true -integer?(1.00001e100) # true -letter?(value) - -Is the value a letter? If the value is a text with a length of 1 and is a letter, then the result is true. Otherwise, the result is false. - -letter?(0) # false -letter?("0") # false -letter?("letter") # false -letter?("l") # true -letter?("L") # true -letter?("") # false -letter?(null) # false -logical?(value) - -Is the value a logical? A logical is either a false or a true. All other values are not logical. - -logical?(false) # true -logical?(true) # true -logical?(0) # false -logical?() # false -logical?(null) # false -lower?(value) - -Is the value a lower case letter? If the value is a text with a length of 1 and is a lower case letter, then the result is true. Otherwise, the result is false. - -lower?(0) # false -lower?("0") # false -lower?("lower") # false -lower?("l") # true -lower?("L") # false -lower?("") # false -null?(value) - -Is the value null? This does the same thing as value = null. - -number?(value) - -Is the value a number? If the value is a number, then the result is true. Otherwise, the result is false. - -number?(0) # true -number?((13 / 4)) # true -number?((13 / 0)) # false -number?(98.6) # true -number?("0") # false -number?(1) # true -pattern?(value) +### for(array, function, reverse, exit) -Is the value a pattern? If the value is a pattern, then the result is true. Otherwise, the result is false. +Call function with each element and its element number. If exit is not null and the function returns the exit value, return early. -pattern?(pattern (1- {letter digit "_-%"})) # true -record?(value) +### format(text, collection, transformer) -Is the value a record? If the value is a record, then the result is true. Otherwise, the result is false. +Substitute `{key}` placeholders in text with values from a collection (array or record). -record?(0) # false -record?({}) # true -record?([]) # false -record?("record") # false -record?("{}") # false -record?(function () ({})) # false -record?(pattern (1- {letter digit "_-%"})) # false -record?(@) # true -stone?(value) +```javascript +format("{0} in {1}!", ["Malmborg", "Plano"]) +// "Malmborg in Plano!" +``` -Is the value stone? +### fraction(number) -stone?("false") # true -stone?(9) # true -stone?(null) # true -stone?({}) # false -stone?(stone({})) # true -text?(value) +Returns the fractional part of a number. See also `whole`. -Is the value a text? If the value is a text, then the result is true. Otherwise, the result is false. +### length(value) -text?(0) # false -text?("0") # true -text?("number") # true -text?("") # true -text?(null) # false -true?(value) +| Value | Result | +|-------|--------| +| array | number of elements | +| blob | number of bits | +| text | number of codepoints | +| function | number of named inputs (arity) | +| record | record.length() | +| other | null | -Is the value true? +### lower(text) -upper?(value) +Returns text with all uppercase characters converted to lowercase. -Is the value an upper case letter? If the value is a text with a length of 1 and is an upper case letter, then the result is true. Otherwise, the result is false. +### max(number, number) -upper?(0) # false -upper?("0") # false -upper?("UPPER") # false -upper?("u") # false -upper?("U") # true -upper?("") # false -whitespace?(value) +Returns the larger of two numbers. Returns null if either is not a number. -Is the value whitespace? If the value is a nonempty text containing only whitespace characters, then the result is true. Otherwise, the result is false. +### min(number, number) -whitespace?(0) # false -whitespace?(32) # false -whitespace?(char(32)) # true -whitespace?("0") # false -whitespace?(" ") # true -whitespace?("\t") # true -whitespace?("\r") # true -whitespace?("\r\n") # true -whitespace?("space") # false -whitespace?(" ") # true -whitespace?(" L") # false -whitespace?("") # false -Standard Functions +Returns the smaller of two numbers. -abs(number) +### modulo(dividend, divisor) -Absolute value. Return the positive form of the number. If the input value is not a number, the result is null. +Result is `dividend - (divisor * floor(dividend / divisor))`. Result has the sign of the divisor. -apply(function, array) +### neg(number) -Apply. Execute the function and return its return value. Pass the elements of the array as input values. See proxy. +Negate. Reverse the sign of a number. -If the first input value is not a function, apply returns its first input value. +### normalize(text) -If length(array) is greater than length(function), it disrupts. +Unicode normalize. -If the second argument is not an array, it is used as a single input value. +### not(logical) -ceiling(number, place) +Returns the opposite logical. Returns null for non-logicals. -If place is 0 or null, the number is rounded up to the smallest integer that is greater than or equal to the number. If place is a small positive integer, then the number is rounded up to that decimal place. +### parallel(requestor_array, throttle, need) -Examples: +Returns a requestor that starts all requestors in the array. Results are collected into an array matching the input order. Optional throttle limits concurrent requestors. Optional need specifies the minimum number of successes required. See [Requestors](/docs/requestors/) for usage. -assign result: ceiling(12.3775) # result is 13 -assign result: ceiling(12.3775, 0) # result is 13 -assign result: ceiling(12.3775, 1) # result is 20 -assign result: ceiling(12.3775, -2) # result is 12.38 -assign result: ceiling(-12.3775, -2) # result is -12.37 -assign result: ceiling(-12.3775) # result is -12 -character(value) +### print(value) -If the value is a text, it returns the first character. If the value is a non-negative 32-bit integer, it returns the character from that codepoint. Otherwise, it returns the empty string. +Print a value to standard output. -codepoint(text) +```javascript +print("hello") +print(42) +print(`result: ${x}`) +``` -The codepoint function returns the codepoint number of the first character of the text. If the input value is not a text, or if it is the empty text, then it returns null. +### race(requestor_array, throttle, need) -extract(text, pattern, from, to) +Like parallel, but returns as soon as the needed number of results are obtained. Default need is 1. Unfinished requestors are cancelled. See [Requestors](/docs/requestors/) for usage. -The text is matched to the pattern. If it does not match, the result is null. If the pattern does match, then the result is a record containing the saved fields. +### reduce(array, function, initial, reverse) -fallback(requestor_array) +Reduce an array to a single value by applying a function to pairs of elements. -The fallback requestor factory returns a requestor function that tries each of the requestors in the requstor_array until it gets a success. When the requestor is called, it calls the first requestor in requestor_array. If that is eventually successful, its value is passed to the callback. But if that requestor fails, the next requestor is called, and so on. If none of the requestors is successful, then the fallback fails. If any one succeeds, then the fallback succeeds. +```javascript +reduce([1, 2, 3, 4, 5, 6, 7, 8, 9], (a, b) => a + b) // 45 +``` -The fallback requestor returns a cancel function that can be called when the result is no longer needed. +### remainder(dividend, divisor) -filter(array, function) +For fit integers: `dividend - ((dividend // divisor) * divisor)`. -The filter function calls a function for every element in the array, passing each element and its element number. +### replace(text, target, replacement, limit) -(element, element_nr) -When the function's return value is true, then the element is copied into a new array. If the function's return value is false, then the element is not copied into the new array. If the return value is not a logical, then the filter returns null. +Return text with target replaced by replacement. Target can be text or pattern. Replacement can be text or a function. -It returns a new array. The length of the new array is between 0 thru length(array). It returns null if the function input is not a function. +### reverse(array) -Example: +Returns a new array with elements in the opposite order. -def data: [0, 1.25, 2, 3.5, 4, 5.75] -def integers: filter(data, integer?) # integers is [0, 2, 4] -find(array, function, reverse, from) +### round(number, place) -Call the function for each element of the array, passing each element and its element number. +Round to nearest. -(element, element_nr) -If the function returns true, then find returns the element number of the current element. +```javascript +round(12.3775) // 12 +round(12.3775, -2) // 12.38 +``` -If the second input value is not a function, then it is compared exactly to the elements. +### search(text, target, from) -If the reverse input value is true, then search begins at the end of the array and works backward. +Search text for target. Returns character position or null. -The from input value gives the element number to search first. The default is 0 unless reverse is true, when the default is length(array) - 1. +### sequence(requestor_array) -find returns the element number of the found value. If nothing is found, find returns null. +Returns a requestor that processes each requestor in order. Each result becomes the input to the next. The last result is the final result. See [Requestors](/docs/requestors/) for usage. -floor(number, place) +### sign(number) -If place is 0 or null, the number is rounded down to the greatest integer that is less than or equal to the number. If place is a small positive integer, then the number is rounded down to that many decimal places. For positive numbers, this is like discarding decimal places. +Returns -1, 0, or 1. -Examples: +### some(array, function) -assign result: floor(12.3775) # result is 12 -assign result: floor(12.3775, -2) # result is 12.37 -assign result: floor(-12.3775, 0) # result is -13 -assign result: floor(-12.3775, -2) # result is -12.38 -for(array, function, reverse, exit) +Returns `true` if any element satisfies the predicate. Stops at the first match. -For each. Call the function with each element of the array. The function is passed each element and its element number. +```javascript +some([1, 2, 3], x => x > 2) // true +some([1, 2, 3], x => x > 5) // false +``` -(element, element_nr) -If reverse is true, then it starts with the last element and works backwards. +### sort(array, select) -If exit is not null, then when the function returns the exit value, then the for function returns early. The exit value is usually true or false, but it may be anything. If exit is null, then every element is processed. +Returns a new sorted array. Sort keys must be all numbers or all texts. Sort is ascending and stable. -The for function returns null unless it did an early exit, when it returns the exit value. +| select type | Sort key | Description | +|-------------|----------|-------------| +| null | element itself | Simple sort | +| text | element[select] | Sort by record field | +| number | element[select] | Sort by array index | +| array | select[index] | External sort keys | -format(text, collection, transformer) +```javascript +sort(["oats", "peas", "beans", "barley"]) +// ["barley", "beans", "oats", "peas"] -The format function makes a new text with substitutions in an original text. A collection is either an array of texts or a record of texts. +sort([{n: 3}, {n: 1}], "n") +// [{n: 1}, {n: 3}] +``` -A search is made for {left brace and }right brace in the text. If they are found, the middle text between them is examined. If the collection is an array, the middle text is used as a number, and then the matched {left brace and middle and }right brace are replaced with the text at that subscript in the array. If the collection is a record, and if the middle text is the key of a member of the collection with a text value, then the value of the member is used in the substitution. Unmatched text is not altered. +### starts_with(text, prefix) -The text between {left brace and }right brace is broken on the :colon character. The left text will be used as a number or name to select a value from the collection. (The value need not be a text.) +Returns `true` if the text starts with the given prefix. -If a transformer input is a function, then it is called with the collection[left text] and right text as inputs. If it returns a text, then the substitution is made. +```javascript +starts_with("hello world", "hello") // true +starts_with("hello world", "world") // false +``` -If a transformer input is a record, then the right text is used to select a function from the transformer. That function is passed the value from the collection. If the return value is a text, that text will substitute. If there is no colon, then the empty text is used to select the function from the transformer. If the transformer does not produce a function, or if the function does not return a text, then no replacement occurs. If transformer[right text](collection[left text]) produces a text, then the substitution is made. +### stone(value) -If the substitution is not made, and if collection[left text] is a number, then the right text is used as a format input in calling collection[left text].text(right text). If it returns a text, then the substitution is made. +Petrify the value, making it permanently immutable. The operation is deep — all nested objects and arrays are also frozen. Returns the value. -Example: +### trim(text, reject) -var result: format("{0} in {1}!", ["Malmborg", "Plano"]) - # result is "Malmborg in Plano!" -fraction(number) +Remove characters from both ends. Default removes whitespace. -The fraction function returns the fractional part of a number It returns null for non-numbers. Also see whole. +### trunc(number, place) -length(value) +Truncate toward zero. -The length function -value result -array number of elements -blob number of bits -logical null -function number of named inputs -null null -number null -record record.length() -text number of codepoints -Length. Find the length of an array in elements, a blob in bits, or a text in codepoints. For functions, it is the arity (or number of inputs). +```javascript +trunc(12.3775) // 12 +trunc(-12.3775) // -12 +``` -If the value is a record containing a length field +### upper(text) -If the length field contains a function, then length(my_record) has the same effect as my_record.length(). +Returns text with all lowercase characters converted to uppercase. -If the length field contains a number, return the number. +### whole(number) -All other values produce null. - -lower(text) - -The lower function returns a text in which all uppercase characters are converted to lowercase. - -Example: - -assign result: lower("Carl Hollywood") # result is "carl hollywood" -max(number, number) - -Maximum. The result is the larger of the two numbers. If either input value is not a number, the result is null. - -max(3, 4) # 4 -max can be used with min to constrain values within an acceptable range. - -min(max(2, 3), 7) # 3 -min(max(4, 3), 7) # 4 -min(max(8, 3), 7) # 7 -max(1, null) # null -max(null, 1) # null -min(number,number) - -Minimum. The result is the smaller of the two numbers. If either input value is not a number, the result is null. - -min(3, 4) # 3 -modulo(dividend, divisor) - -The result of modulo(dividend, divisor) is dividend - (divisor * floor(dividend / divisor)). The result has the sign of the divisor. - -If dividend is 0, then the result is 0. If divisor is 0, or if either operand is not a number, then the result is null. dividend and divisor are not required to be integers. - -If both input values are integers, and if the divisor is a positive power of two, then it is the same as and(dividend, divisor - 1). - -neg(number) - -Negate. Reverse the sign of a number. If the input value is not a number, the result is null. Note that the -minus sign is not used as a prefix negation operator. - -normalize(text) - -Unicode normalize. Return a text whose textual value is the same as text, but whose binary representation is in the specified Unicode normalization form. The two texts will display the same, but might not be equal. - -not(logical) - -Not. Return the opposite logical. If the input value is not a logical, it returns null. - -parallel(requestor_array, throttle, need) - -Parallel. The parallel requestor factory returns a resquestor function. When the requestor function is called with a callback function and a value, every requestor in the requestor_array is called with the value, and the results are placed in corresponding elements of the results array. This all happens in parallel. The value produced by the first element of the requestor_array provides the first element of the result. The completed results array is passed to the callback function. - -By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand all at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. The throttle is optional. If provided, the throttle is a number: the maximum number of requestors to have running at any time. - -Ordinarily, the number of successes must be the same as the number of requestors in the requestor_array. If you need few successes, specify your need with the need argument. The need could be 1, meaning that 1 or more successes are needed. The need could be 0, meaning that no successes are needed. If the number of successes is greater than or equal to need, then the whole operation succeeds. The need must be between 0 and requestor_array.length. - -The requestor function itself returns a cancel function that cancels all of the pending requestors from the requestor_array. - -race(requestor_array, throttle, need) - -Race. The race function is a requestor factory that takes an array of requestor functions and returns a requestor function that starts all of the requestors in the requestor_array at once. - -By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. - -By default, a single result is produced. If an array of results is need, specify the needed number of results in the need parameter. When the needed number of successful results is obtained, the operation ends. The results go into a sparce array aligned with the requestor_array, and unfinished requestors are cancelled. The need argument must be between 1 and requestor_array.length. - -The returned requestor function returns a cancel function that cancels all of the pending requestors from the requestor_array. - -reduce(array, function, initial, reverse) - -Reduce. The reduce function takes a function that takes two input values and returns a value. - -function (first, second) { - return ... -} -The function is called for each element of the array, passing the result of the previous iteration to the next iteration. - -The initial value is optional. If present, the function is called for every element of the array. - -If initial is null: - -If length(array) is 0, then it returns null. -If length(array) is 1, then it returns array[0]. -If length(array) is 2, it returns function(array[0], array[1]). -If length(array) is 3, it returns function(function(array[0], array[1]), array[2]). -And so on. -If initial is not null: - -If length(array) is 0, then it returns initial. -If length(array) is 1, then it returns function(initial, array[0]). -If length(array) is 2, it returns function(function(initial, array[0]), array[1]). -If length(array) is 3, it returns function(function(function(initial, array[0]), array[1]), array[2]). -And so on. -If reverse is true, then the work begins at the end of the array and works backward. - -Example: - -def data: [1, 2, 3, 4, 5, 6, 7, 8, 9] -def total: reduce(data, '+) # total is 45 -def product: reduce(data, '*) # product is 362880 -remainder(dividend, divisor) - -Remainder. For fit integers, the remainder is dividend - ((dividend // divisor) * divisor). - -replace(text, target, replacement, limit) - -Return a new text in which the target is replaced by the replacement. - -text: the source text. - -target: a text or pattern that should be replaced in the source. - -replacement: text to replace the matched text, or a function that takes the matched text and the starting position, and returns the replacement text or null if it should be left alone. - -limit: The maximum number of replacements. The default is all possible replacements. The limit includes null matches. - -reverse(array) - -The reverse function makes a new array or blob with the elements or bits in the opposite order. - -It returns a new, reversed array or blob. - -Example: - -def data: ["I", "am", "Sam"] -def result: reverse(data) # the result is ["Sam", "am", "I"] -round(number, place) - -If place is 0 or null, the number is rounded to the nearest integer. If place is a small integer, then the number is rounded to that many decimal places. - -Examples: - -round(12.3775) # 12 -round(12.3775, -2) # 12.38 -round(-12.3775, 0) # -12 -round(-12.3775, 1) # -10 -round(-12.3775, 2) # 0 -round(-12.3775, -2) # -12.38 -search(text, target, from) - -Search the text for the target. If the target is found, return the character position of the left-most part of the match. If the search fails, return null. - -text: the source text. - -target: a text or pattern that should be found in the source. - -from: The starting position for the search. The default is 0, the beginning of the text. If from is negative, it is added to length(text). - -sequence(requestor_array) - -Sequence. The sequence requestor factory that takes a requestor_array and returns a requestor function that starts all of the requestors in the requestor_array one at a time. The result of each becomes the input to the next. The last result is the result of the sequence. - -sequence returns a requestor that returns a cancel function and processes each requestor in requestor_array one at a time. Each of those requestors is passed the result of the previous requestor as its value argument. If all succeed, then the sequence succeeds, giving the result of the last of the requestors. If any fail, then the sequence fails.The sequence succeeds if every requestor in the requestor_array succeeds - -sign(number) - -The sign function returns -1 if the number is negative, 0 if the number is exactly 0, 1 if the number is positive, and null if it is not a number. - -sort(array, select) - -The sort function produces a new array in which the values are sorted. Sort keys must be either all numbers or all texts. Any other type of key or any error in the key calculation will cause the sort to fail, returning null. The sort is ascending. The sort is stable, so the relative order of equal keys is preserved. - -The optional select input determines how the sort key for each element is selected. - -select type Sort key for array[index] Description -null array[index] The sort key is the element itself. This is useful for sorting simple arrays of numbers or texts. -text array[index][select] The sort key is the select field of each record element. This is useful for sorting arrays of records. -number array[index][select] The sort key is the select element of each array element. This is useful for sorting arrays of arrays. -array select[index] select is an array of the same length containing the sort keys. -It returns a new, sorted array. - -Examples: - -def foods: ["oats", "peas", "beans", "barley"] -def result: sort(foods) # result is ["barley", "beans", "oats", "peas"] - -var stooges: [ - {first: "Moe", last: "Howard"} - {first: "Joe", last: "DeRita"} - {first: "Shemp", last: "Howard"} - {first: "Larry", last: "Fine"} - {first: "Joe", last: "Besser"} - {first: "Curly", last: "Howard"} -] -assign stooges: sort(sort(stooges, "first"), "last") - # stooges is now [ - # {first: "Joe", last: "Besser"} - # {first: "Joe", last: "DeRita"} - # {first: "Larry", last: "Fine"} - # {first: "Curly", last: "Howard"} - # {first: "Moe", last: "Howard"} - # {first: "Shemp", last: "Howard"} - # ] -assign stooges: sort(stooges, [50, 60, 20, 40, 10, 30]) - # stooges is now [ - # {first: "Moe", last: "Howard"} - # {first: "Larry", last: "Fine"} - # {first: "Shemp", last: "Howard"} - # {first: "Curly", last: "Howard"} - # {first: "Joe", last: "Besser"} - # {first: "Joe", last: "DeRita"} - # ] -stone(value) - -Petrify the value, turning it into stone. Its contents are preserved, but it can no longer be modified by the assign statement or the blob.write functions. This is usually performed on arrays, records, and blobs. All other types are already stone. Attempting to turn a value that is stone to stone will have no effect. - -The stone operation is deep. Any mutable objects in the value will also be turned to stone. - -This can not be reversed. Immutable objects can never become mutable. A mutable copy can be made of an immutable object, but the original object remains immutable. - -The stone function returns the value. - -trim(text, reject) - -The trim function removes selected characters from the ends of a text. The default is to remove control characters and spaces. - -assign result: " Hello there ".trim() # result is "Hello there" -trunc(number, place) - -The number is truncated toward zero. If the number is positive, the result is the same as floor(place). If the number is negative, the result is the same as ceiling(place). - -If place is a small integer, then the number is rounded down to that many decimal places. This is like discarding decimal places. - -Examples: - -assign result: trunc(12.3775, 0) # result is 12 -assign result: trunc(12.3775, 2) # result is 12.37 -assign result: trunc(-12.3775) # result is -12 -assign result: trunc(-12.3775, 2) # result is -12.37 -turkish_lower(text) - -Similar to lower, except that I goes to ıdotless i. - -turkish_upper(text) - -Similar to upper, except that i goes to İI dot. - -upper(text) - -The upper function returns a text in which all lowercase characters are converted to uppercase. - -Example: - -assign result: upper("Carl Hollywood") # result is "CARL HOLLYWOOD" -whole(number) - -The whole function returns the whole part of a number. It returns null for non-numbers. Also see fraction. \ No newline at end of file +Returns the whole part of a number. See also `fraction`. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 83bc6249..00000000 --- a/docs/index.md +++ /dev/null @@ -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/`. \ No newline at end of file diff --git a/docs/kim.md b/docs/kim.md new file mode 100644 index 00000000..36259d9a --- /dev/null +++ b/docs/kim.md @@ -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. diff --git a/docs/language.md b/docs/language.md new file mode 100644 index 00000000..62224d14 --- /dev/null +++ b/docs/language.md @@ -0,0 +1,649 @@ +--- +title: "ƿit Language" +description: "Syntax, types, operators, and built-in functions" +weight: 10 +type: "docs" +--- + +ƿ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 + +### Variables and Constants + +Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or bare `{}` blocks. + +```javascript +var x = 10 +var name = "pit" +var empty = null + +def PI = 3.14159 // constant, cannot be reassigned + +var a = 1, b = 2, c = 3 // multiple declarations +``` + +### Data Types + +ƿit has eight fundamental types: + +- **number** — DEC64 decimal floating point (no rounding errors) +- **text** — Unicode strings +- **logical** — `true` or `false` +- **null** — the absence of a value (no `undefined`) +- **array** — ordered, numerically-indexed sequences +- **object** — key-value records with prototype inheritance +- **blob** — binary data (bits, not bytes) +- **function** — first-class callable values + +### Literals + +```javascript +// Numbers +42 +3.14 +-5 +0 +1e3 // scientific notation (1000) + +// Text +"hello" +`template ${x}` // string interpolation +`${1 + 2}` // expression interpolation + +// Logical +true +false + +// Null +null + +// Arrays +[1, 2, 3] +[] + +// Objects +{a: 1, b: "two"} +{} + +// Regex +/\d+/ +/hello/i // with flags +``` + +## Operators + +### Arithmetic + +```javascript +2 + 3 // 5 +5 - 3 // 2 +3 * 4 // 12 +12 / 4 // 3 +10 % 3 // 1 +2 ** 3 // 8 (exponentiation) +``` + +### Comparison + +All comparisons are strict — there is no type coercion. + +```javascript +5 == 5 // true +5 != 6 // true +3 < 5 // true +5 > 3 // true +3 <= 3 // true +5 >= 5 // true +``` + +### Logical + +```javascript +true && true // true +true && false // false +false || true // true +false || false // false +!true // false +!false // true +``` + +Logical operators short-circuit: + +```javascript +var called = false +var fn = function() { called = true; return true } +var r = false && fn() // fn() not called +r = true || fn() // fn() not called +``` + +### Bitwise + +```javascript +5 & 3 // 1 (AND) +5 | 3 // 7 (OR) +5 ^ 3 // 6 (XOR) +~0 // -1 (NOT) +1 << 3 // 8 (left shift) +8 >> 3 // 1 (right shift) +-1 >>> 1 // 2147483647 (unsigned right shift) +``` + +### Unary + +```javascript ++5 // 5 +-5 // -5 +-(-5) // 5 +``` + +### Increment and Decrement + +```javascript +var x = 5 +x++ // returns 5, x becomes 6 (postfix) +++x // returns 7, x becomes 7 (prefix) +x-- // returns 7, x becomes 6 (postfix) +--x // returns 5, x becomes 5 (prefix) +``` + +### Compound Assignment + +```javascript +var x = 10 +x += 3 // 13 +x -= 3 // 10 +x *= 2 // 20 +x /= 4 // 5 +x %= 3 // 2 +``` + +### Ternary + +```javascript +var a = true ? 1 : 2 // 1 +var b = false ? 1 : 2 // 2 +var c = true ? (false ? 1 : 2) : 3 // 2 (nested) +``` + +### Comma + +The comma operator evaluates all expressions and returns the last. + +```javascript +var x = (1, 2, 3) // 3 +``` + +### In + +Test whether a key exists in an object. + +```javascript +var o = {a: 1} +"a" in o // true +"b" in o // false +``` + +### Delete + +Remove a key from an object. + +```javascript +var o = {a: 1, b: 2} +delete o.a +"a" in o // false +o.b // 2 +``` + +## Property Access + +### Dot and Bracket + +```javascript +var o = {x: 10} +o.x // 10 (dot read) +o.x = 20 // dot write +o["x"] // 20 (bracket read) +var key = "x" +o[key] // 20 (computed bracket) +o["y"] = 30 // bracket write +``` + +### Object as Key + +Objects can be used as keys in other objects. + +```javascript +var k = {} +var o = {} +o[k] = 42 +o[k] // 42 +o[{}] // null (different object) +k in o // true +delete o[k] +k in o // false +``` + +### Chained Access + +```javascript +var d = {a: {b: [1, {c: 99}]}} +d.a.b[1].c // 99 +``` + +## Arrays + +Arrays are **distinct from objects**. They are ordered, numerically-indexed sequences. + +```javascript +var arr = [1, 2, 3] +arr[0] // 1 +arr[2] = 10 // [1, 2, 10] +length(arr) // 3 +``` + +### Push and Pop + +```javascript +var a = [1, 2] +a[] = 3 // push: [1, 2, 3] +length(a) // 3 +var v = a[] // pop: v is 3, a is [1, 2] +length(a) // 2 +``` + +## Objects + +Objects are key-value records with prototype-based inheritance. + +```javascript +var point = {x: 10, y: 20} +point.x // 10 +point["y"] // 20 +``` + +### Prototypes + +```javascript +// Create object with prototype +var parent = {x: 10} +var child = meme(parent) +child.x // 10 (inherited) +proto(child) // parent + +// Override does not mutate parent +child.x = 20 +parent.x // 10 +``` + +### Mixins + +```javascript +var p = {a: 1} +var m1 = {b: 2} +var m2 = {c: 3} +var child = meme(p, [m1, m2]) +child.a // 1 (from prototype) +child.b // 2 (from mixin) +child.c // 3 (from mixin) +``` + +## Control Flow + +### If / Else + +```javascript +var x = 0 +if (true) x = 1 +if (false) x = 2 else x = 3 +if (false) x = 4 +else if (true) x = 5 +else x = 6 +``` + +### While + +```javascript +var i = 0 +while (i < 5) i++ + +// break +i = 0 +while (true) { + if (i >= 3) break + i++ +} + +// continue +var sum = 0 +i = 0 +while (i < 5) { + i++ + if (i % 2 == 0) continue + sum += i +} +``` + +### For + +Variables cannot be declared in the for initializer. Declare them at the function body level. + +```javascript +var sum = 0 +var i = 0 +for (i = 0; i < 5; i++) sum += i + +// break +sum = 0 +i = 0 +for (i = 0; i < 10; i++) { + if (i == 5) break + sum += i +} + +// continue +sum = 0 +i = 0 +for (i = 0; i < 5; i++) { + if (i % 2 == 0) continue + sum += i +} + +// nested +sum = 0 +var j = 0 +for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + sum++ + } +} +``` + +## Functions + +### Function Expressions + +```javascript +var add = function(a, b) { return a + b } +add(2, 3) // 5 +``` + +### Arrow Functions + +```javascript +var double = x => x * 2 +double(5) // 10 + +var sum = (a, b) => a + b +sum(2, 3) // 5 + +var block = x => { + var y = x * 2 + return y + 1 +} +block(5) // 11 +``` + +### Return + +A function with no `return` returns `null`. An early `return` exits immediately. + +```javascript +var fn = function() { var x = 1 } +fn() // null + +var fn2 = function() { return 1; return 2 } +fn2() // 1 +``` + +### Arguments + +Extra arguments are ignored. Missing arguments are `null`. + +```javascript +var fn = function(a, b) { return a + b } +fn(1, 2, 3) // 3 (extra arg ignored) + +var fn2 = function(a, b) { return a } +fn2(1) // 1 (b is null) +``` + +### Immediately Invoked Function Expression + +```javascript +var r = (function(x) { return x * 2 })(21) // 42 +``` + +### Closures + +Functions capture variables from their enclosing scope. + +```javascript +var make = function(x) { + return function(y) { return x + y } +} +var add5 = make(5) +add5(3) // 8 +``` + +Captured variables can be mutated: + +```javascript +var counter = function() { + var n = 0 + return function() { n = n + 1; return n } +} +var c = counter() +c() // 1 +c() // 2 +``` + +### Recursion + +```javascript +var fact = function(n) { + if (n <= 1) return 1 + return n * fact(n - 1) +} +fact(5) // 120 +``` + +### This Binding + +When a function is called as a method, `this` refers to the object. + +```javascript +var obj = { + val: 10, + get: function() { return this.val } +} +obj.get() // 10 +``` + +### Currying + +```javascript +var f = function(a) { + return function(b) { + return function(c) { return a + b + c } + } +} +f(1)(2)(3) // 6 +``` + +## Identifiers + +Identifiers can contain `?` and `!` characters, both as suffixes and mid-name. + +```javascript +var nil? = (x) => x == null +nil?(null) // true +nil?(42) // false + +var set! = (x) => x + 1 +set!(5) // 6 + +var is?valid = (x) => x > 0 +is?valid(3) // true + +var do!stuff = () => 42 +do!stuff() // 42 +``` + +The `?` in an identifier is not confused with the ternary operator: + +```javascript +var nil? = (x) => x == null +var a = nil?(null) ? "yes" : "no" // "yes" +``` + +## Type Checking + +### Type Functions + +```javascript +is_number(42) // true +is_text("hi") // true +is_logical(true) // true +is_object({}) // true +is_array([]) // true +is_function(function(){}) // true +is_null(null) // true +is_object([]) // false (array is not object) +is_array({}) // false (object is not array) +``` + +### Truthiness + +Falsy values: `false`, `0`, `""`, `null`. Everything else is truthy. + +```javascript +if (0) ... // not entered +if ("") ... // not entered +if (null) ... // not entered +if (1) ... // entered +if ("hi") ... // entered +if ({}) ... // entered +if ([]) ... // entered +``` + +## Immutability with Stone + +The `stone()` function makes values permanently immutable. + +```javascript +var o = {x: 1} +is_stone(o) // false +stone(o) +is_stone(o) // true +o.x = 2 // disrupts! +``` + +Stone is **deep** — all nested objects and arrays are also frozen. This cannot be reversed. + +## Function Proxy + +A function with two parameters (`name`, `args`) acts as a proxy when properties are accessed on it. Any method call on the function dispatches through the proxy. + +```javascript +var proxy = function(name, args) { + return `${name}:${length(args)}` +} +proxy.hello() // "hello:0" +proxy.add(1, 2) // "add:2" +proxy["method"]() // "method:0" +var m = "dynamic" +proxy[m]() // "dynamic:0" +``` + +For non-proxy functions, property access disrupts: + +```javascript +var fn = function() { return 1 } +fn.foo // disrupts +fn.foo = 1 // disrupts +``` + +## Regex + +Regex literals are written with forward slashes, with optional flags. + +```javascript +var r = /\d+/ +var result = extract("abc123", r) +result[0] // "123" + +var ri = /hello/i +var result2 = extract("Hello", ri) +result2[0] // "Hello" +``` + +## Error Handling + +ƿit uses `disrupt` and `disruption` for error handling. A `disrupt` signals that something went wrong. The `disruption` block attached to a function catches it. + +```javascript +var safe_divide = function(a, b) { + if (b == 0) disrupt + return a / b +} disruption { + print("something went wrong") +} +``` + +`disrupt` is a bare keyword — it does not carry a value. The `disruption` block knows that something went wrong, but not what. + +### Re-raising + +A `disruption` block can re-raise by calling `disrupt` again: + +```javascript +var outer = function() { + var inner = function() { disrupt } disruption { disrupt } + inner() +} disruption { + // caught here after re-raise +} +outer() +``` + +### Testing for Disruption + +```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. + +## Self-Referencing Structures + +Objects can reference themselves: + +```javascript +var o = {name: "root"} +o.self = o +o.self.self.name // "root" +``` + +## Variable Shadowing + +Inner functions can shadow outer variables: + +```javascript +var x = 10 +var fn = function() { + var x = 20 + return x +} +fn() // 20 +x // 10 +``` diff --git a/docs/library/.pages b/docs/library/.pages deleted file mode 100644 index 4f9edf36..00000000 --- a/docs/library/.pages +++ /dev/null @@ -1,10 +0,0 @@ -nav: - - text.md - - number.md - - array.md - - object.md - - blob.md - - time.md - - math.md - - json.md - - random.md diff --git a/docs/library/_index.md b/docs/library/_index.md new file mode 100644 index 00000000..e187bf30 --- /dev/null +++ b/docs/library/_index.md @@ -0,0 +1,18 @@ +--- +title: "Standard Library" +description: "ƿit standard library modules" +weight: 90 +type: "docs" +--- + +The standard library provides modules loaded with `use()`. + +| Module | Description | +|--------|-------------| +| [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 | + +The `text`, `number`, `array`, and `object` functions are intrinsics — they are always available without `use`. See [Built-in Functions](/docs/functions/) for the full list, and the individual reference pages for [text](/docs/library/text/), [number](/docs/library/number/), [array](/docs/library/array/), and [object](/docs/library/object/). diff --git a/docs/library/array.md b/docs/library/array.md index 84d2cc78..c97a6ae1 100644 --- a/docs/library/array.md +++ b/docs/library/array.md @@ -1,12 +1,19 @@ -# array +--- +title: "array" +description: "Array creation and manipulation" +weight: 30 +type: "docs" +--- -The `array` function and its methods handle array creation and manipulation. +The `array` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument. -## Creation +## From a Number + +Create an array of a given size. ### array(number) -Create an array of specified size, filled with `null`. +All elements initialized to `null`. ```javascript array(3) // [null, null, null] @@ -14,24 +21,36 @@ array(3) // [null, null, null] ### array(number, initial) -Create an array with initial values. +All elements initialized to a value. If initial is a function, it is called for each element (passed the index if arity >= 1). ```javascript array(3, 0) // [0, 0, 0] array(3, i => i * 2) // [0, 2, 4] ``` +## From an Array + +Copy, map, concat, or slice. + ### array(array) -Copy an array. +Copy an array (mutable). ```javascript var copy = array(original) ``` +### array(array, function) + +Map — call function with each element, collect results. + +```javascript +array([1, 2, 3], x => x * 2) // [2, 4, 6] +``` + ### array(array, from, to) -Slice an array. +Slice — extract a sub-array. Negative indices count from end. ```javascript array([1, 2, 3, 4, 5], 1, 4) // [2, 3, 4] @@ -40,32 +59,36 @@ array([1, 2, 3], -2) // [2, 3] ### array(array, another) -Concatenate arrays. +Concatenate two arrays. ```javascript array([1, 2], [3, 4]) // [1, 2, 3, 4] ``` -### array(object) +## From a Record -Get keys of an object. +### array(record) + +Get the keys of a record as an array of text. ```javascript array({a: 1, b: 2}) // ["a", "b"] ``` +## From Text + ### array(text) -Split text into grapheme clusters. +Split text into individual characters (grapheme clusters). This is the standard way to iterate over characters in a string. ```javascript -array("hello") // ["h", "e", "l", "l", "o"] -array("👨‍👩‍👧") // ["👨‍👩‍👧"] +array("hello") // ["h", "e", "l", "l", "o"] +array("ƿit") // ["ƿ", "i", "t"] ``` ### array(text, separator) -Split text by separator. +Split text by a separator string. ```javascript array("a,b,c", ",") // ["a", "b", "c"] @@ -73,7 +96,7 @@ array("a,b,c", ",") // ["a", "b", "c"] ### array(text, length) -Split text into chunks. +Dice text into chunks of a given length. ```javascript array("abcdef", 2) // ["ab", "cd", "ef"] @@ -87,13 +110,13 @@ Iterate over elements. ```javascript array.for([1, 2, 3], function(el, i) { - log.console(i, el) + print(i, el) }) // With early exit array.for([1, 2, 3, 4], function(el) { if (el > 2) return true - log.console(el) + print(el) }, false, true) // prints 1, 2 ``` diff --git a/docs/library/blob.md b/docs/library/blob.md index 2dbd5563..da3af998 100644 --- a/docs/library/blob.md +++ b/docs/library/blob.md @@ -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. diff --git a/docs/library/json.md b/docs/library/json.md index 37e2b77c..45adcc94 100644 --- a/docs/library/json.md +++ b/docs/library/json.md @@ -1,4 +1,9 @@ -# json +--- +title: "json" +description: "JSON encoding and decoding" +weight: 80 +type: "docs" +--- JSON encoding and decoding. @@ -86,5 +91,5 @@ var config_text = json.encode(config, 2) // Load configuration var loaded = json.decode(config_text) -log.console(loaded.debug) // true +print(loaded.debug) // true ``` diff --git a/docs/library/math.md b/docs/library/math.md index 827c25ea..2dc33770 100644 --- a/docs/library/math.md +++ b/docs/library/math.md @@ -1,10 +1,15 @@ -# 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 -var math = use('math/degrees') // angles in degrees +var math = use('math/degrees') // angles in degrees var math = use('math/cycles') // angles in cycles (0-1) ``` @@ -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) @@ -130,21 +135,21 @@ math.e() // 2.71828... var math = use('math/radians') // Distance between two points -function distance(x1, y1, x2, y2) { +var distance = function(x1, y1, x2, y2) { var dx = x2 - x1 var dy = y2 - y1 return math.sqrt(dx * dx + dy * dy) } // Angle between two points -function angle(x1, y1, x2, y2) { +var angle = function(x1, y1, x2, y2) { return math.arc_tangent(y2 - y1, x2 - x1) } // Rotate a point -function rotate(x, y, angle) { - var c = math.cosine(angle) - var s = math.sine(angle) +var rotate = function(x, y, a) { + var c = math.cosine(a) + var s = math.sine(a) return { x: x * c - y * s, y: x * s + y * c diff --git a/docs/library/number.md b/docs/library/number.md index dcdcc319..13e4d652 100644 --- a/docs/library/number.md +++ b/docs/library/number.md @@ -1,6 +1,11 @@ -# number +--- +title: "number" +description: "Numeric conversion and operations" +weight: 20 +type: "docs" +--- -The `number` function and its methods handle numeric conversion and operations. +The `number` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument. ## Conversion @@ -29,15 +34,15 @@ Parse formatted numbers. | Format | Description | |--------|-------------| -| `""` | Standard decimal | -| `"u"` | Underbar separator (1_000) | -| `"d"` | Comma separator (1,000) | -| `"s"` | Space separator (1 000) | -| `"v"` | European (1.000,50) | -| `"b"` | Binary | -| `"o"` | Octal | -| `"h"` | Hexadecimal | -| `"j"` | JavaScript style (0x, 0o, 0b prefixes) | +| `""` | Standard decimal | +| `"u"` | Underbar separator (1_000) | +| `"d"` | Comma separator (1,000) | +| `"s"` | Space separator (1 000) | +| `"v"` | European (1.000,50) | +| `"b"` | Binary | +| `"o"` | Octal | +| `"h"` | Hexadecimal | +| `"j"` | JavaScript style (0x, 0o, 0b prefixes) | ```javascript number("1,000", "d") // 1000 @@ -118,20 +123,20 @@ Get the fractional part. fraction(4.75) // 0.75 ``` -### min(...values) +### min(a, b) -Return the smallest value. +Return the smaller of two numbers. ```javascript -min(3, 1, 4, 1, 5) // 1 +min(3, 5) // 3 ``` -### max(...values) +### max(a, b) -Return the largest value. +Return the larger of two numbers. ```javascript -max(3, 1, 4, 1, 5) // 5 +max(3, 5) // 5 ``` ### remainder(dividend, divisor) diff --git a/docs/library/object.md b/docs/library/object.md index 8c65cb6c..676f0151 100644 --- a/docs/library/object.md +++ b/docs/library/object.md @@ -1,8 +1,13 @@ -# object +--- +title: "object" +description: "Object creation and manipulation" +weight: 40 +type: "docs" +--- -The `object` function and related utilities handle object creation and manipulation. +The `object` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the types of its arguments. -## Creation +## From a Record ### object(obj) @@ -29,6 +34,8 @@ Select specific keys. object({a: 1, b: 2, c: 3}, ["a", "c"]) // {a: 1, c: 3} ``` +## From an Array of Keys + ### object(keys) Create object from keys (values are `true`). @@ -60,9 +67,9 @@ object(["a", "b", "c"], (k, i) => i) // {a: 0, b: 1, c: 2} Create a new object with the given prototype. ```javascript -var animal = {speak: function() { log.console("...") }} +var animal = {speak: function() { print("...") }} var dog = meme(animal) -dog.speak = function() { log.console("woof") } +dog.speak = function() { print("woof") } ``` ### proto(obj) @@ -104,9 +111,4 @@ var obj = {a: 1, b: 2, c: 3} // Get all keys var keys = array(obj) // ["a", "b", "c"] - -// Iterate -for (var key in obj) { - log.console(key, obj[key]) -} ``` diff --git a/docs/library/random.md b/docs/library/random.md index 4f3f1237..f385ea26 100644 --- a/docs/library/random.md +++ b/docs/library/random.md @@ -1,4 +1,9 @@ -# random +--- +title: "random" +description: "Random number generation" +weight: 90 +type: "docs" +--- Random number generation. @@ -43,7 +48,7 @@ var random = use('random') var coin_flip = random.random() < 0.5 // Random element from array -function pick(arr) { +var pick = function(arr) { return arr[random.random_whole(length(arr))] } @@ -51,11 +56,14 @@ var colors = ["red", "green", "blue"] var color = pick(colors) // Shuffle array -function shuffle(arr) { +var shuffle = function(arr) { var result = array(arr) // copy - for (var i = length(result) - 1; i > 0; i--) { - var j = random.random_whole(i + 1) - var temp = result[i] + var i = length(result) - 1 + var j = 0 + var temp = null + for (i = length(result) - 1; i > 0; i--) { + j = random.random_whole(i + 1) + temp = result[i] result[i] = result[j] result[j] = temp } @@ -63,8 +71,8 @@ function shuffle(arr) { } // Random in range -function random_range(min, max) { - return min + random.random() * (max - min) +var random_range = function(lo, hi) { + return lo + random.random() * (hi - lo) } var x = random_range(-10, 10) // -10 to 10 diff --git a/docs/library/text.md b/docs/library/text.md index aab23cab..5543dc4e 100644 --- a/docs/library/text.md +++ b/docs/library/text.md @@ -1,19 +1,28 @@ -# text +--- +title: "text" +description: "String conversion and manipulation" +weight: 10 +type: "docs" +--- -The `text` function and its methods handle string conversion and manipulation. +The `text` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument. -## Conversion +To split text into characters, use `array(text)` — see [array](/docs/library/array/). + +## From an Array ### text(array, separator) -Convert an array to text, joining elements with a separator (default: space). +Join array elements into text with a separator (default: empty string). ```javascript -text([1, 2, 3]) // "1 2 3" -text([1, 2, 3], ", ") // "1, 2, 3" -text(["a", "b"], "-") // "a-b" +text(["h", "e", "l", "l", "o"]) // "hello" +text([1, 2, 3], ", ") // "1, 2, 3" +text(["a", "b"], "-") // "a-b" ``` +## From a Number + ### text(number, radix) Convert a number to text. Radix is 2-36 (default: 10). @@ -24,13 +33,16 @@ text(255, 16) // "ff" text(255, 2) // "11111111" ``` +## From Text + ### text(text, from, to) -Extract a substring from index `from` to `to`. +Extract a substring from index `from` to `to`. Negative indices count from end. ```javascript text("hello world", 0, 5) // "hello" text("hello world", 6) // "world" +text("hello", -3) // "llo" ``` ## Methods @@ -101,7 +113,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) @@ -109,8 +121,7 @@ text.normalize("café") // normalized form Get the Unicode codepoint of the first character. ```javascript -text.codepoint("A") // 65 -text.codepoint("😀") // 128512 +text.codepoint("A") // 65 ``` ### text.extract(text, pattern, from, to) diff --git a/docs/library/time.md b/docs/library/time.md index e0f932b7..7fc6f469 100644 --- a/docs/library/time.md +++ b/docs/library/time.md @@ -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. @@ -96,7 +101,7 @@ var last_week = now - time.week var later = now + (2 * time.hour) // Format future time -log.console(time.text(tomorrow)) +print(time.text(tomorrow)) ``` ## Example @@ -108,9 +113,9 @@ var time = use('time') var start = time.number() // ... do work ... var elapsed = time.number() - start -log.console(`Took ${elapsed} seconds`) +print(`Took ${elapsed} seconds`) // Schedule for tomorrow var tomorrow = time.number() + time.day -log.console(`Tomorrow: ${time.text(tomorrow, "yyyy-MM-dd")}`) +print(`Tomorrow: ${time.text(tomorrow, "yyyy-MM-dd")}`) ``` diff --git a/docs/memory.md b/docs/memory.md deleted file mode 100644 index d55ce42f..00000000 --- a/docs/memory.md +++ /dev/null @@ -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. diff --git a/docs/nota.md b/docs/nota.md new file mode 100644 index 00000000..c04047c8 --- /dev/null +++ b/docs/nota.md @@ -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. diff --git a/docs/packages.md b/docs/packages.md index 2a817a1e..29576db7 100644 --- a/docs/packages.md +++ b/docs/packages.md @@ -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. diff --git a/docs/pitcode.md b/docs/pitcode.md deleted file mode 100644 index 94289ecc..00000000 --- a/docs/pitcode.md +++ /dev/null @@ -1,287 +0,0 @@ -# Pitmachine -A pitmachine is an abstract register machine for executing pitcode. - -The pitmachine assembly language is JSON. The instructions field contains an array of labels and instructions. Labels are the targets of branch instructions. They are simple text. Slots are elements in an activation frame. They are designated by a small positive integer. Slots are the general registers of the pitmachine. Slots hold the arguments, variables, and temporaries of a function invocation. - -{ - "name": "program_name", - "data": {⸳⸳⸳}, - "source": "...", -The labels record associates labels with tokens for use by debuggers. - - "labels": { - "entry": token, - "beyond": token, - ... - }, -The instructions array is a list of instructions and labels. - - instructions: [ -A statement label: - - "entry", -go to beyond: - - ["jump", "beyond"], -assign slot 8: pi / 2 - - ["access", 13, {"kind": "name", "name": "pi", "make": "intrinsic", ⸳⸳⸳}], - ["int", 14, 2], - ["divide", 8, 13, 14], - ⸳⸳⸳ - "beyond" - ⸳⸳⸳ - ] -} - -## Pitcode instructions -This is a register based machine. - -### Arithmetic -Arithmetic instructions perform operations on numbers. All other cases disrupt (except add, which is polymorphic). - -"add", dest, left, right // dest, left, and right are numbers designating slots in the current activation frame. Also works on texts (concatenation). - -"subtract", dest, left, right - -"multiply", dest, left, right - -"divide", dest, left, right - -"integer_divide", dest, left, right - -"modulo", dest, left, right - -"remainder", dest, left, right - -"max", dest, left, right - -"min", dest, left, right - -"abs", dest, right - -"neg", dest, right - -"sign", dest, right - -"fraction", dest, right - -"integer", dest, right - -"ceiling", dest, right, place - -"floor", dest, right, place - -"round", dest, right, place - -"trunc", dest, right, place - -### Text -Text instructions perform operations on texts. All other cases disrupt. - -"concat", dest, left, right // concat two texts - -"concat_space", dest, left, right - -"character", dest, right - -"codepoint", dest, right - -"length", dest, right - -"lower", dest, right - -"upper", dest, right - -"append", pretext, right - -Append the right text to the pretext, forwarding and growing its capacity if necessary. - -### Comparison -Comparison instructions perform operations on texts or numbers. All other cases disrupt. - -"eq", dest, left, right - -"ne", dest, left, right - -"lt", dest, left, right - -"le", dest, left, right - -"gt", dest, left, right - -"ge", dest, left, right - -### Logical - -"not", dest, right - -### Bitwise -Bitwise instructions convert operands to 32-bit integers. Non-numbers disrupt. - -"bitand", dest, left, right - -"bitor", dest, left, right - -"bitxor", dest, left, right - -"bitnot", dest, right - -"shl", dest, left, right - -"shr", dest, left, right - -"ushr", dest, left, right - -### Function - -"frame", dest, func, nr_args - -Prepare to invoke the func object. If the nr_args is too large, disrupt. Allocate the new activation frame. Put the current frame pointer into it. - -"goframe", dest, func, nr_args - -Same as frame, except that the current frame is reused if it is large enough. - -"invoke", frame - -Store the next instruction address in the current frame. Make the new frame the current frame. Jump to the entry point. - -"goinvoke", frame - -"apply", func, array - -"return", value - -"return_value", dest - -"setarg", frame, slot, value // set the slot of frame to value - -### Branching - -"jump", label - -"jump_true", slot, label - -If the value in the slot is true, jump to the label. Otherwise, continue with the next instruction. - -"jump_false": slot, label - -If the value in the slot is false, jump to the label. Otherwise, continue with the next instruction. - -"jump_null": slot, label - -"jump_empty": slot, label - -"wary_true", slot, label - -If the value in the slot is true, jump to the label. If the value is false, continue with the next instruction. Otherwise disrupt because of a type error. - -"wary_false": slot, label - -If the value in the slot is false, jump to the label. If the value is true, continue with the next instruction. Otherwise disrupt because of a type error. - -### Sensory - -Does the right slot contain a value of the indicated type? - -"array?", dest, right - -"blob?", dest, right - -"character?", dest, right - -"data?", dest, right - -"digit?", dest, right - -"false?", dest, right - -"fit?", dest, right - -"function?", dest, right - -"integer?", dest, right - -"letter?", dest, right - -"logical?", dest, right - -"null?", dest, right - -"pattern?", dest, right - -"record?", dest, right - -"stone?", dest, right - -"text?", dest, right - -"true?", dest, right - -"upper?", dest, right - -"whitespace?", dest, right - -### Potpourri - -"stone", dest, right // stone an object - -"true", dest - -"false", dest - -"null", dest - -"move", dest, right - -"int", dest, small_int - -"access", dest, literal - -This is used to access values (numbers, texts) from the program's immutable memory. The literal is a number or text. - -"load", dest, object, subscript - -This is used to load values from records and arrays. - -"store", dest, object, subscript - -This is used to store values into records and arrays. - -"delete", object, subscript - -used to delete a field from a record. - -"get", dest, slot, level - -This is used to get values from slots in outer frames. - -"put", value, slot, level - -This is used to store values into slots in outer frames. - -"push", array, slot - -Append to a mutable array. - -"pop", dest, array - -Remove the last element of a mutable array, putting the element into dest. - -"disrupt" - -### Make - -"array", dest, nr_elements - -"blob", dest, nr_bits - -"function", dest, code_name_text - -"pretext", dest, nr_characters - -A pretext must be converted to text by stone before it can leave the function scope. - -"record", dest, nr_elements - diff --git a/docs/requestors.md b/docs/requestors.md new file mode 100644 index 00000000..2dc18ab2 --- /dev/null +++ b/docs/requestors.md @@ -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 +var my_requestor = function(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 +var fetch_data = function(callback, url) { + $contact(function(connection) { + $send(connection, {get: url}, function(response) { + callback(response) + }) + }, {host: url, port: 80}) + return function() { + // clean up if needed + } +} +``` + +A requestor that always succeeds immediately: + +```javascript +var constant = function(callback, value) { + callback(42) +} +``` + +A requestor that always fails: + +```javascript +var broken = function(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) { + print(reason) + } else { + print(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) { + print("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 +var ask_worker = function(callback, task) { + $send(worker, task, function(reply) { + callback(reply) + }) +} + +var pipeline = sequence([ + ask_worker, + process_result, + store_result +]) + +pipeline(function(stored) { + print("done") + $stop() +}, {type: "compute", data: [1, 2, 3]}) +``` diff --git a/docs/spec/dec64.md b/docs/spec/dec64.md new file mode 100644 index 00000000..8f253bd9 --- /dev/null +++ b/docs/spec/dec64.md @@ -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 +``` diff --git a/docs/spec/gc.md b/docs/spec/gc.md new file mode 100644 index 00000000..8094f247 --- /dev/null +++ b/docs/spec/gc.md @@ -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 (register VM and mcode) +- **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 diff --git a/docs/spec/mach.md b/docs/spec/mach.md new file mode 100644 index 00000000..479a84cd --- /dev/null +++ b/docs/spec/mach.md @@ -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. diff --git a/docs/spec/mcode.md b/docs/spec/mcode.md new file mode 100644 index 00000000..9f5f1712 --- /dev/null +++ b/docs/spec/mcode.md @@ -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. diff --git a/docs/spec/objects.md b/docs/spec/objects.md new file mode 100644 index 00000000..836cb652 --- /dev/null +++ b/docs/spec/objects.md @@ -0,0 +1,141 @@ +--- +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, register, or mcode) | +| 5 | `OBJ_CODE` | Compiled code | +| 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, register, or mcode + union { + struct { ... } cfunc; // C function pointer + 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), 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` | diff --git a/docs/spec/stone.md b/docs/spec/stone.md new file mode 100644 index 00000000..f46066a8 --- /dev/null +++ b/docs/spec/stone.md @@ -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. diff --git a/docs/spec/values.md b/docs/spec/values.md new file mode 100644 index 00000000..e06022e5 --- /dev/null +++ b/docs/spec/values.md @@ -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. diff --git a/docs/wota.md b/docs/wota.md new file mode 100644 index 00000000..3beff594 --- /dev/null +++ b/docs/wota.md @@ -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 +``` diff --git a/source/cell.c b/source/cell.c index dd669f9d..56367f2b 100644 --- a/source/cell.c +++ b/source/cell.c @@ -387,8 +387,34 @@ static int run_eval(const char *script_or_file, int print_bytecode, int use_boot return result; } +static void print_usage(const char *prog) +{ + printf("Usage: %s [options] +{{ end }} diff --git a/website/themes/knr/layouts/partials/footer.html b/website/themes/knr/layouts/partials/footer.html new file mode 100644 index 00000000..7f96f6e6 --- /dev/null +++ b/website/themes/knr/layouts/partials/footer.html @@ -0,0 +1,3 @@ +
+

ƿit — an actor-based scripting language

+
diff --git a/website/themes/knr/layouts/partials/head.html b/website/themes/knr/layouts/partials/head.html new file mode 100644 index 00000000..e40eee61 --- /dev/null +++ b/website/themes/knr/layouts/partials/head.html @@ -0,0 +1,9 @@ + + +{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }} + + + + + + diff --git a/website/themes/knr/layouts/partials/header.html b/website/themes/knr/layouts/partials/header.html new file mode 100644 index 00000000..0f2da581 --- /dev/null +++ b/website/themes/knr/layouts/partials/header.html @@ -0,0 +1,10 @@ + diff --git a/website/themes/knr/layouts/partials/nav-docs.html b/website/themes/knr/layouts/partials/nav-docs.html new file mode 100644 index 00000000..3d0d03d1 --- /dev/null +++ b/website/themes/knr/layouts/partials/nav-docs.html @@ -0,0 +1,16 @@ + diff --git a/website/themes/knr/layouts/spec/list.html b/website/themes/knr/layouts/spec/list.html new file mode 100644 index 00000000..11c4f1c1 --- /dev/null +++ b/website/themes/knr/layouts/spec/list.html @@ -0,0 +1,19 @@ +{{ define "main" }} +
+ +
+

{{ .Title }}

+ {{ range .Site.Data.spec_sections.sections }} +
+ {{ with $.Site.GetPage .page }} + {{ .Content }} + {{ end }} +
+ {{ end }} +
+
+ +{{ end }} diff --git a/website/themes/knr/layouts/standalone/list.html b/website/themes/knr/layouts/standalone/list.html new file mode 100644 index 00000000..b0ef2ee9 --- /dev/null +++ b/website/themes/knr/layouts/standalone/list.html @@ -0,0 +1,13 @@ +{{ define "main" }} +
+ +
+

{{ .Title }}

+ {{ .Content }} +
+
+ +{{ end }} diff --git a/website/themes/knr/static/css/main.css b/website/themes/knr/static/css/main.css new file mode 100644 index 00000000..361461da --- /dev/null +++ b/website/themes/knr/static/css/main.css @@ -0,0 +1,611 @@ +/* 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; + --header-height: 3rem; +} + +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); + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; +} + +body { + padding-top: var(--header-height); +} + +.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: var(--header-height); + max-height: calc(100vh - var(--header-height)); + 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: var(--header-height); + max-height: calc(100vh - var(--header-height)); + 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-group-header { + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-secondary); + margin-top: 0.8rem; + margin-bottom: 0.15rem; + list-style: none; +} + +.toc-group-header:first-child { + margin-top: 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; + } +} diff --git a/website/themes/knr/static/css/syntax.css b/website/themes/knr/static/css/syntax.css new file mode 100644 index 00000000..b1643cb3 --- /dev/null +++ b/website/themes/knr/static/css/syntax.css @@ -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; } diff --git a/website/themes/knr/static/fonts/charter-bold.woff b/website/themes/knr/static/fonts/charter-bold.woff new file mode 100644 index 00000000..6d7fd7af Binary files /dev/null and b/website/themes/knr/static/fonts/charter-bold.woff differ diff --git a/website/themes/knr/static/fonts/charter-regular.woff b/website/themes/knr/static/fonts/charter-regular.woff new file mode 100644 index 00000000..a19483d0 Binary files /dev/null and b/website/themes/knr/static/fonts/charter-regular.woff differ diff --git a/website/themes/knr/static/fonts/jetbrains-mono-regular.woff2 b/website/themes/knr/static/fonts/jetbrains-mono-regular.woff2 new file mode 100644 index 00000000..66c54672 Binary files /dev/null and b/website/themes/knr/static/fonts/jetbrains-mono-regular.woff2 differ diff --git a/website/themes/knr/static/fonts/junicode-regular.woff2 b/website/themes/knr/static/fonts/junicode-regular.woff2 new file mode 100644 index 00000000..b8e4d065 Binary files /dev/null and b/website/themes/knr/static/fonts/junicode-regular.woff2 differ diff --git a/website/themes/knr/static/js/toc.js b/website/themes/knr/static/js/toc.js new file mode 100644 index 00000000..5818190a --- /dev/null +++ b/website/themes/knr/static/js/toc.js @@ -0,0 +1,110 @@ +(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 + var lastGroup = null; + sections.forEach(function(sec) { + // Insert group header when group changes + var group = sec.dataset.tocGroup; + if (group && group !== lastGroup) { + var gh = document.createElement('li'); + gh.className = 'toc-group-header'; + gh.textContent = group; + tocList.appendChild(gh); + lastGroup = group; + } + + 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); }); + } +})();