Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f3b025c5 | ||
|
|
bae4e957e9 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -382,13 +382,13 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
|
||||
JSValue js_miniz_use(JSContext *js)
|
||||
{
|
||||
JS_NewClassID(&js_reader_class_id);
|
||||
JS_NewClass(js, js_reader_class_id, &js_reader_class);
|
||||
JS_NewClass(JS_GetRuntime(js), js_reader_class_id, &js_reader_class);
|
||||
JSValue reader_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_reader_class_id, reader_proto);
|
||||
|
||||
JS_NewClassID(&js_writer_class_id);
|
||||
JS_NewClass(js, js_writer_class_id, &js_writer_class);
|
||||
JS_NewClass(JS_GetRuntime(js), js_writer_class_id, &js_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_writer_class_id, writer_proto);
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
nav:
|
||||
- index.md
|
||||
- cellscript.md
|
||||
- actors.md
|
||||
- packages.md
|
||||
- cli.md
|
||||
- c-modules.md
|
||||
- Standard Library: library
|
||||
|
||||
74
docs/_index.md
Normal file
74
docs/_index.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: "Documentation"
|
||||
description: "ƿit language documentation"
|
||||
type: "docs"
|
||||
---
|
||||
|
||||

|
||||
|
||||
ƿit is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Actor Model** — isolated memory, message passing, no shared state
|
||||
- **Immutability** — `stone()` makes values permanently frozen
|
||||
- **Prototype Inheritance** — objects without classes
|
||||
- **C Integration** — seamlessly extend with native code
|
||||
- **Cross-Platform** — deploy to desktop, web, and embedded
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
// hello.ce - A simple actor
|
||||
log.console("Hello, ƿit!")
|
||||
$stop()
|
||||
```
|
||||
|
||||
```bash
|
||||
pit hello
|
||||
```
|
||||
|
||||
## Language
|
||||
|
||||
- [**ƿit Language**](/docs/language/) — syntax, types, and built-in functions
|
||||
- [**Actors and Modules**](/docs/actors/) — the execution model
|
||||
- [**Packages**](/docs/packages/) — code organization and sharing
|
||||
- [**Command Line**](/docs/cli/) — the `pit` tool
|
||||
- [**Writing C Modules**](/docs/c-modules/) — native extensions
|
||||
|
||||
## Reference
|
||||
|
||||
- [**Built-in Functions**](/docs/functions/) — intrinsics reference
|
||||
|
||||
## Standard Library
|
||||
|
||||
- [text](/docs/library/text/) — string manipulation
|
||||
- [number](/docs/library/number/) — numeric operations (functions are global: `floor()`, `max()`, etc.)
|
||||
- [array](/docs/library/array/) — array utilities
|
||||
- [object](/docs/library/object/) — object utilities
|
||||
- [blob](/docs/library/blob/) — binary data
|
||||
- [time](/docs/library/time/) — time and dates
|
||||
- [math](/docs/library/math/) — trigonometry and math
|
||||
- [json](/docs/library/json/) — JSON encoding/decoding
|
||||
- [random](/docs/library/random/) — random numbers
|
||||
|
||||
## Architecture
|
||||
|
||||
ƿit programs are organized into **packages**. Each package contains:
|
||||
|
||||
- **Modules** (`.cm`) — return a value, cached and frozen
|
||||
- **Actors** (`.ce`) — run independently, communicate via messages
|
||||
- **C files** (`.c`) — compiled to native libraries
|
||||
|
||||
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone and bootstrap
|
||||
git clone https://gitea.pockle.world/john/cell
|
||||
cd cell
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
The ƿit shop is stored at `~/.pit/`.
|
||||
@@ -1,10 +1,15 @@
|
||||
# Actors and Modules
|
||||
---
|
||||
title: "Actors and Modules"
|
||||
description: "The ƿit execution model"
|
||||
weight: 20
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Cell organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
|
||||
ƿit organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
|
||||
|
||||
## The Actor Model
|
||||
|
||||
Cell is built on the actor model of computation. Each actor:
|
||||
ƿit is built on the actor model of computation. Each actor:
|
||||
|
||||
- Has its own **isolated memory** — actors never share state
|
||||
- Runs to completion each **turn** — no preemption
|
||||
@@ -62,10 +67,10 @@ An actor is a script that **does not return a value**. It runs as an independent
|
||||
// worker.ce
|
||||
log.console("Worker started")
|
||||
|
||||
$on_message = function(msg) {
|
||||
$receiver(function(msg, reply) {
|
||||
log.console("Received:", msg)
|
||||
// Process message...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Key properties:**
|
||||
@@ -177,11 +182,11 @@ $time_limit(my_requestor, 10) // 10 second timeout
|
||||
|
||||
## Module Resolution
|
||||
|
||||
When you call `use('name')`, Cell searches:
|
||||
When you call `use('name')`, ƿit searches:
|
||||
|
||||
1. **Current package** — files relative to package root
|
||||
2. **Dependencies** — packages declared in `cell.toml`
|
||||
3. **Core** — built-in Cell modules
|
||||
2. **Dependencies** — packages declared in `pit.toml`
|
||||
3. **Core** — built-in ƿit modules
|
||||
|
||||
```javascript
|
||||
// From within package 'myapp':
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# Writing C Modules
|
||||
---
|
||||
title: "Writing C Modules"
|
||||
description: "Extending ƿit with native code"
|
||||
weight: 50
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Cell makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
|
||||
ƿit makes it easy to extend functionality with C code. C files in a package are compiled into a dynamic library and can be imported like any other module.
|
||||
|
||||
## Basic Structure
|
||||
|
||||
@@ -45,12 +50,12 @@ Where:
|
||||
- `<filename>` is the C file name without extension
|
||||
|
||||
Examples:
|
||||
- `mypackage/math.c` → `js_mypackage_math_use`
|
||||
- `gitea.pockle.world/john/lib/render.c` → `js_gitea_pockle_world_john_lib_render_use`
|
||||
- `mypackage/math.c` -> `js_mypackage_math_use`
|
||||
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use`
|
||||
|
||||
## Required Headers
|
||||
|
||||
Include `cell.h` for all Cell integration:
|
||||
Include `cell.h` for all ƿit integration:
|
||||
|
||||
```c
|
||||
#include "cell.h"
|
||||
@@ -63,7 +68,7 @@ This provides:
|
||||
|
||||
## Conversion Functions
|
||||
|
||||
### JavaScript ↔ C
|
||||
### JavaScript <-> C
|
||||
|
||||
```c
|
||||
// Numbers
|
||||
@@ -201,7 +206,7 @@ static const JSCFunctionListEntry js_funcs[] = {
|
||||
CELL_USE_FUNCS(js_funcs)
|
||||
```
|
||||
|
||||
Usage in Cell:
|
||||
Usage in ƿit:
|
||||
|
||||
```javascript
|
||||
var vector = use('vector')
|
||||
@@ -211,7 +216,7 @@ var n = vector.normalize(3, 4) // {x: 0.6, y: 0.8}
|
||||
var d = vector.dot(1, 0, 0, 1) // 0
|
||||
```
|
||||
|
||||
## Combining C and Cell
|
||||
## Combining C and ƿit
|
||||
|
||||
A common pattern is to have a C file provide low-level functions and a `.cm` file provide a higher-level API:
|
||||
|
||||
@@ -244,11 +249,11 @@ return Vector
|
||||
C files are automatically compiled when you run:
|
||||
|
||||
```bash
|
||||
cell build
|
||||
cell update
|
||||
pit build
|
||||
pit update
|
||||
```
|
||||
|
||||
The resulting dynamic library is placed in `~/.cell/lib/`.
|
||||
The resulting dynamic library is placed in `~/.pit/lib/`.
|
||||
|
||||
## Platform-Specific Code
|
||||
|
||||
@@ -260,7 +265,7 @@ audio_playdate.c # Playdate
|
||||
audio_emscripten.c # Web/Emscripten
|
||||
```
|
||||
|
||||
Cell selects the appropriate file based on the target platform.
|
||||
ƿit selects the appropriate file based on the target platform.
|
||||
|
||||
## Static Declarations
|
||||
|
||||
|
||||
105
docs/cli.md
105
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 <command> [arguments]
|
||||
pit <command> [arguments]
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### cell version
|
||||
### pit version
|
||||
|
||||
Display the Cell version.
|
||||
Display the ƿit version.
|
||||
|
||||
```bash
|
||||
cell version
|
||||
pit version
|
||||
# 0.1.0
|
||||
```
|
||||
|
||||
### cell install
|
||||
### pit install
|
||||
|
||||
Install a package to the shop.
|
||||
|
||||
```bash
|
||||
cell install gitea.pockle.world/john/prosperon
|
||||
cell install /Users/john/local/mypackage # local path
|
||||
pit install gitea.pockle.world/john/prosperon
|
||||
pit install /Users/john/local/mypackage # local path
|
||||
```
|
||||
|
||||
### cell update
|
||||
### pit update
|
||||
|
||||
Update packages from remote sources.
|
||||
|
||||
```bash
|
||||
cell update # update all packages
|
||||
cell update <package> # update specific package
|
||||
pit update # update all packages
|
||||
pit update <package> # update specific package
|
||||
```
|
||||
|
||||
### cell remove
|
||||
### pit remove
|
||||
|
||||
Remove a package from the shop.
|
||||
|
||||
```bash
|
||||
cell remove gitea.pockle.world/john/oldpackage
|
||||
pit remove gitea.pockle.world/john/oldpackage
|
||||
```
|
||||
|
||||
### cell list
|
||||
### pit list
|
||||
|
||||
List installed packages.
|
||||
|
||||
```bash
|
||||
cell list # list all installed packages
|
||||
cell list <package> # list dependencies of a package
|
||||
pit list # list all installed packages
|
||||
pit list <package> # list dependencies of a package
|
||||
```
|
||||
|
||||
### cell ls
|
||||
### pit ls
|
||||
|
||||
List modules and actors in a package.
|
||||
|
||||
```bash
|
||||
cell ls # list files in current project
|
||||
cell ls <package> # list files in specified package
|
||||
pit ls # list files in current project
|
||||
pit ls <package> # list files in specified package
|
||||
```
|
||||
|
||||
### cell build
|
||||
### pit build
|
||||
|
||||
Build the current package.
|
||||
|
||||
```bash
|
||||
cell build
|
||||
pit build
|
||||
```
|
||||
|
||||
### cell test
|
||||
### pit test
|
||||
|
||||
Run tests.
|
||||
|
||||
```bash
|
||||
cell test # run tests in current package
|
||||
cell test all # run all tests
|
||||
cell test <package> # run tests in specific package
|
||||
pit test # run tests in current package
|
||||
pit test all # run all tests
|
||||
pit test <package> # run tests in specific package
|
||||
```
|
||||
|
||||
### cell link
|
||||
### pit link
|
||||
|
||||
Manage local package links for development.
|
||||
|
||||
```bash
|
||||
cell link add <canonical> <local_path> # link a package
|
||||
cell link list # show all links
|
||||
cell link delete <canonical> # remove a link
|
||||
cell link clear # remove all links
|
||||
pit link add <canonical> <local_path> # link a package
|
||||
pit link list # show all links
|
||||
pit link delete <canonical> # remove a link
|
||||
pit link clear # remove all links
|
||||
```
|
||||
|
||||
### cell fetch
|
||||
### pit fetch
|
||||
|
||||
Fetch package sources without extracting.
|
||||
|
||||
```bash
|
||||
cell fetch <package>
|
||||
pit fetch <package>
|
||||
```
|
||||
|
||||
### cell upgrade
|
||||
### pit upgrade
|
||||
|
||||
Upgrade the Cell installation itself.
|
||||
Upgrade the ƿit installation itself.
|
||||
|
||||
```bash
|
||||
cell upgrade
|
||||
pit upgrade
|
||||
```
|
||||
|
||||
### cell clean
|
||||
### pit clean
|
||||
|
||||
Clean build artifacts.
|
||||
|
||||
```bash
|
||||
cell clean
|
||||
pit clean
|
||||
```
|
||||
|
||||
### cell help
|
||||
### pit help
|
||||
|
||||
Display help information.
|
||||
|
||||
```bash
|
||||
cell help
|
||||
cell help <command>
|
||||
pit help
|
||||
pit help <command>
|
||||
```
|
||||
|
||||
## Running Scripts
|
||||
|
||||
Any `.ce` file in the Cell core can be run as a command:
|
||||
Any `.ce` file in the ƿit core can be run as a command:
|
||||
|
||||
```bash
|
||||
cell version # runs version.ce
|
||||
cell build # runs build.ce
|
||||
cell test # runs test.ce
|
||||
pit version # runs version.ce
|
||||
pit build # runs build.ce
|
||||
pit test # runs test.ce
|
||||
```
|
||||
|
||||
## Package Locators
|
||||
@@ -143,16 +148,16 @@ Packages are identified by locators:
|
||||
- **Local**: `/absolute/path/to/package`
|
||||
|
||||
```bash
|
||||
cell install gitea.pockle.world/john/prosperon
|
||||
cell install /Users/john/work/mylib
|
||||
pit install gitea.pockle.world/john/prosperon
|
||||
pit install /Users/john/work/mylib
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Cell stores its data in `~/.cell/`:
|
||||
ƿit stores its data in `~/.pit/`:
|
||||
|
||||
```
|
||||
~/.cell/
|
||||
~/.pit/
|
||||
├── packages/ # installed packages
|
||||
├── lib/ # compiled dynamic libraries
|
||||
├── build/ # build cache
|
||||
@@ -163,7 +168,7 @@ Cell stores its data in `~/.cell/`:
|
||||
|
||||
## Environment
|
||||
|
||||
Cell reads the `HOME` environment variable to locate the shop directory.
|
||||
ƿit reads the `HOME` environment variable to locate the shop directory.
|
||||
|
||||
## Exit Codes
|
||||
|
||||
|
||||
1044
docs/functions.md
1044
docs/functions.md
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
# Cell
|
||||
|
||||

|
||||
|
||||
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Actor Model** — isolated memory, message passing, no shared state
|
||||
- **Immutability** — `stone()` makes values permanently frozen
|
||||
- **Prototype Inheritance** — objects without classes
|
||||
- **C Integration** — seamlessly extend with native code
|
||||
- **Cross-Platform** — deploy to desktop, web, and embedded
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
// hello.ce - A simple actor
|
||||
log.console("Hello, Cell!")
|
||||
$stop()
|
||||
```
|
||||
|
||||
```bash
|
||||
cell hello
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [**Cell Language**](cellscript.md) — syntax, types, and built-in functions
|
||||
- [**Actors and Modules**](actors.md) — the execution model
|
||||
- [**Packages**](packages.md) — code organization and sharing
|
||||
- [**Command Line**](cli.md) — the `cell` tool
|
||||
- [**Writing C Modules**](c-modules.md) — native extensions
|
||||
|
||||
## Standard Library
|
||||
|
||||
- [text](library/text.md) — string manipulation
|
||||
- [number](library/number.md) — numeric operations (functions are global: `floor()`, `max()`, etc.)
|
||||
- [array](library/array.md) — array utilities
|
||||
- [object](library/object.md) — object utilities
|
||||
- [blob](library/blob.md) — binary data
|
||||
- [time](library/time.md) — time and dates
|
||||
- [math](library/math.md) — trigonometry and math
|
||||
- [json](library/json.md) — JSON encoding/decoding
|
||||
- [random](library/random.md) — random numbers
|
||||
|
||||
## Architecture
|
||||
|
||||
Cell programs are organized into **packages**. Each package contains:
|
||||
|
||||
- **Modules** (`.cm`) — return a value, cached and frozen
|
||||
- **Actors** (`.ce`) — run independently, communicate via messages
|
||||
- **C files** (`.c`) — compiled to native libraries
|
||||
|
||||
Actors never share memory. They communicate by sending messages, which are automatically serialized. This makes concurrent programming safe and predictable.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone and bootstrap
|
||||
git clone https://gitea.pockle.world/john/cell
|
||||
cd cell
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
The Cell shop is stored at `~/.cell/`.
|
||||
94
docs/kim.md
Normal file
94
docs/kim.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: "Kim Encoding"
|
||||
description: "Compact character and count encoding"
|
||||
weight: 80
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Kim is a character and count encoding designed by Douglas Crockford. It encodes Unicode characters and variable-length integers using continuation bytes. Kim is simpler and more compact than UTF-8 for most text.
|
||||
|
||||
## Continuation Bytes
|
||||
|
||||
The fundamental idea in Kim is the continuation byte:
|
||||
|
||||
```
|
||||
C D D D D D D D
|
||||
```
|
||||
|
||||
- **C** — continue bit. If 1, read another byte. If 0, this is the last byte.
|
||||
- **D** (7 bits) — data bits.
|
||||
|
||||
To decode: shift the accumulator left by 7 bits, add the 7 data bits. If the continue bit is 1, repeat with the next byte. If 0, the value is complete.
|
||||
|
||||
To encode: take the value, emit 7 bits at a time from most significant to least significant, setting the continue bit on all bytes except the last.
|
||||
|
||||
## Character Encoding
|
||||
|
||||
Kim encodes Unicode codepoints directly as continuation byte sequences:
|
||||
|
||||
| Range | Bytes | Characters |
|
||||
|-------|-------|------------|
|
||||
| U+0000 to U+007F | 1 | ASCII |
|
||||
| U+0080 to U+3FFF | 2 | First quarter of BMP |
|
||||
| U+4000 to U+10FFFF | 3 | All other Unicode |
|
||||
|
||||
Unlike UTF-8, there is no need for surrogate pairs or escapement. Every Unicode character, including emoji and characters from extended planes, is encoded in at most 3 bytes.
|
||||
|
||||
### Examples
|
||||
|
||||
```
|
||||
'A' (U+0041) → 41
|
||||
'é' (U+00E9) → 81 69
|
||||
'💩' (U+1F4A9) → 87 E9 29
|
||||
```
|
||||
|
||||
## Count Encoding
|
||||
|
||||
Kim is also used for encoding counts (lengths, sizes). The same continuation byte format represents non-negative integers of arbitrary size:
|
||||
|
||||
| Range | Bytes |
|
||||
|-------|-------|
|
||||
| 0 to 127 | 1 |
|
||||
| 128 to 16383 | 2 |
|
||||
| 16384 to 2097151 | 3 |
|
||||
|
||||
## Comparison with UTF-8
|
||||
|
||||
| Property | Kim | UTF-8 |
|
||||
|----------|-----|-------|
|
||||
| ASCII | 1 byte | 1 byte |
|
||||
| BMP (first quarter) | 2 bytes | 2-3 bytes |
|
||||
| Full Unicode | 3 bytes | 3-4 bytes |
|
||||
| Self-synchronizing | No | Yes |
|
||||
| Sortable | No | Yes |
|
||||
| Simpler to implement | Yes | No |
|
||||
| Byte count for counts | Variable (7 bits/byte) | Not applicable |
|
||||
|
||||
Kim trades self-synchronization (the ability to find character boundaries from any position) for simplicity and compactness. In practice, Kim text is accessed sequentially, so self-synchronization is not needed.
|
||||
|
||||
## Usage in ƿit
|
||||
|
||||
Kim is used internally by blobs and by the Nota message format.
|
||||
|
||||
### In Blobs
|
||||
|
||||
The `blob.write_text` and `blob.read_text` functions use Kim to encode text into binary data:
|
||||
|
||||
```javascript
|
||||
var blob = use('blob')
|
||||
var b = blob.make()
|
||||
blob.write_text(b, "hello") // Kim-encoded length + characters
|
||||
stone(b)
|
||||
var text = blob.read_text(b, 0) // "hello"
|
||||
```
|
||||
|
||||
### In Nota
|
||||
|
||||
Nota uses Kim for two purposes:
|
||||
|
||||
1. **Counts** — array lengths, text lengths, blob sizes, record pair counts
|
||||
2. **Characters** — text content within Nota messages
|
||||
|
||||
The preamble byte of each Nota value incorporates the first few bits of a Kim-encoded count, with the continue bit indicating whether more bytes follow.
|
||||
|
||||
See [Nota Format](#nota) for the full specification.
|
||||
@@ -1,6 +1,11 @@
|
||||
# Cell Language
|
||||
---
|
||||
title: "ƿit Language"
|
||||
description: "Syntax, types, operators, and built-in functions"
|
||||
weight: 10
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Cell is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
|
||||
ƿit is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
|
||||
|
||||
## Basics
|
||||
|
||||
@@ -13,7 +18,7 @@ def PI = 3.14159 // constant (cannot be reassigned)
|
||||
|
||||
### Data Types
|
||||
|
||||
Cell has six fundamental types:
|
||||
ƿit has six fundamental types:
|
||||
|
||||
- **number** — DEC64 decimal floating point (no rounding errors)
|
||||
- **text** — Unicode strings
|
||||
@@ -49,7 +54,7 @@ null
|
||||
["a", "b", "c"]
|
||||
|
||||
// Objects
|
||||
{name: "cell", version: 1}
|
||||
{name: "pit", version: 1}
|
||||
{x: 10, y: 20}
|
||||
```
|
||||
|
||||
@@ -108,7 +113,7 @@ while (condition) {
|
||||
break
|
||||
continue
|
||||
return value
|
||||
throw "error message"
|
||||
disrupt
|
||||
```
|
||||
|
||||
### Functions
|
||||
@@ -266,7 +271,7 @@ log.error("problem") // error output
|
||||
|
||||
## Pattern Matching
|
||||
|
||||
Cell supports regex patterns in string functions, but not standalone regex objects.
|
||||
ƿit supports regex patterns in string functions, but not standalone regex objects.
|
||||
|
||||
```javascript
|
||||
text.search("hello world", /world/)
|
||||
@@ -275,14 +280,32 @@ replace("hello", /l/g, "L")
|
||||
|
||||
## Error Handling
|
||||
|
||||
```javascript
|
||||
try {
|
||||
riskyOperation()
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
ƿit uses `disrupt` and `disruption` for error handling. A `disrupt` signals that something went wrong. The `disruption` block attached to a function catches it.
|
||||
|
||||
throw "something went wrong"
|
||||
```javascript
|
||||
var safe_divide = function(a, b) {
|
||||
if (b == 0) disrupt
|
||||
return a / b
|
||||
} disruption {
|
||||
log.error("something went wrong")
|
||||
}
|
||||
```
|
||||
|
||||
If an actor has an uncaught error, it crashes.
|
||||
`disrupt` is a bare keyword — it does not carry a value. The `disruption` block knows that something went wrong, but not what.
|
||||
|
||||
To test whether an operation disrupts:
|
||||
|
||||
```javascript
|
||||
var should_disrupt = function(fn) {
|
||||
var caught = false
|
||||
var wrapper = function() {
|
||||
fn()
|
||||
} disruption {
|
||||
caught = true
|
||||
}
|
||||
wrapper()
|
||||
return caught
|
||||
}
|
||||
```
|
||||
|
||||
If an actor has an unhandled disruption, it crashes.
|
||||
@@ -1,10 +0,0 @@
|
||||
nav:
|
||||
- text.md
|
||||
- number.md
|
||||
- array.md
|
||||
- object.md
|
||||
- blob.md
|
||||
- time.md
|
||||
- math.md
|
||||
- json.md
|
||||
- random.md
|
||||
22
docs/library/_index.md
Normal file
22
docs/library/_index.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Standard Library"
|
||||
description: "ƿit standard library modules"
|
||||
weight: 90
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
ƿit includes a standard library of modules loaded with `use()`.
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| [text](/docs/library/text/) | String conversion and manipulation |
|
||||
| [number](/docs/library/number/) | Numeric conversion and operations |
|
||||
| [array](/docs/library/array/) | Array creation and manipulation |
|
||||
| [object](/docs/library/object/) | Object creation and manipulation |
|
||||
| [blob](/docs/library/blob/) | Binary data (bits, not bytes) |
|
||||
| [time](/docs/library/time/) | Time constants and conversions |
|
||||
| [math](/docs/library/math/) | Trigonometry, logarithms, roots |
|
||||
| [json](/docs/library/json/) | JSON encoding and decoding |
|
||||
| [random](/docs/library/random/) | Random number generation |
|
||||
|
||||
Most numeric functions (`floor`, `max`, `abs`, etc.) are global intrinsics and do not require `use`. See [Built-in Functions](/docs/functions/) for the full list.
|
||||
@@ -1,4 +1,9 @@
|
||||
# array
|
||||
---
|
||||
title: "array"
|
||||
description: "Array creation and manipulation"
|
||||
weight: 30
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
The `array` function and its methods handle array creation and manipulation.
|
||||
|
||||
@@ -59,8 +64,7 @@ array({a: 1, b: 2}) // ["a", "b"]
|
||||
Split text into grapheme clusters.
|
||||
|
||||
```javascript
|
||||
array("hello") // ["h", "e", "l", "l", "o"]
|
||||
array("👨👩👧") // ["👨👩👧"]
|
||||
array("hello") // ["h", "e", "l", "l", "o"]
|
||||
```
|
||||
|
||||
### array(text, separator)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# json
|
||||
---
|
||||
title: "json"
|
||||
description: "JSON encoding and decoding"
|
||||
weight: 80
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
JSON encoding and decoding.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# number
|
||||
---
|
||||
title: "number"
|
||||
description: "Numeric conversion and operations"
|
||||
weight: 20
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
The `number` function and its methods handle numeric conversion and operations.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# object
|
||||
---
|
||||
title: "object"
|
||||
description: "Object creation and manipulation"
|
||||
weight: 40
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
The `object` function and related utilities handle object creation and manipulation.
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# random
|
||||
---
|
||||
title: "random"
|
||||
description: "Random number generation"
|
||||
weight: 90
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Random number generation.
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# text
|
||||
---
|
||||
title: "text"
|
||||
description: "String conversion and manipulation"
|
||||
weight: 10
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
The `text` function and its methods handle string conversion and manipulation.
|
||||
|
||||
@@ -101,7 +106,7 @@ text.format("{0} + {1} = {2}", [1, 2, 3])
|
||||
Unicode normalize the text (NFC form).
|
||||
|
||||
```javascript
|
||||
text.normalize("café") // normalized form
|
||||
text.normalize("cafe\u0301") // normalized form
|
||||
```
|
||||
|
||||
### text.codepoint(text)
|
||||
@@ -109,8 +114,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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
248
docs/memory.md
248
docs/memory.md
@@ -1,248 +0,0 @@
|
||||
# Cell actor scripting language
|
||||
|
||||
Cell is a Misty [https://mistysystem.com](https://mistysystem.com) implementation.
|
||||
|
||||
## Memory
|
||||
Values are 32 bit for 32 bit builds and 64 bit for 64 bit builds.
|
||||
|
||||
### 32 bit value
|
||||
|
||||
LSB = 0
|
||||
payload is a 31 bit signed int
|
||||
|
||||
LSB = 01
|
||||
payload is a 30 bit pointer
|
||||
|
||||
LSB = 11
|
||||
next 3 bits = special tag. 27 bits of payload.
|
||||
|
||||
### 64 bit value
|
||||
LSB = 0
|
||||
payload is a 32 bit signed int, using high 32 bits
|
||||
|
||||
LSB = 01
|
||||
payload is a 61 bit pointer
|
||||
|
||||
LSB = 101
|
||||
Short float: a 61 bit double, with 3 less exponent bits
|
||||
|
||||
LSB = 11
|
||||
Special tag: next 3 bits. 5 bits total. 59 bits of payload. 8 total special tags.
|
||||
|
||||
Special tags:
|
||||
1: Bool. Payload is 0 or 1.
|
||||
2: null. payload is 0.
|
||||
3: exception.
|
||||
4: string.
|
||||
Immediate string. Next 3 low bits = length in bytes. Rest is string data. This allows for strings up to 7 ascii letters. Encoded in utf8.
|
||||
|
||||
## Numbers and math
|
||||
Cell can be compiled with different levels of exactness for numeracy. Any number which cannot be represented exactly becomes "null". Any numeric operation which includes "null" results in "null".
|
||||
|
||||
Using short floats in a 64 bit system means you have doubles in the range of +- 10^38, not the full range of double. If you create a number out of that range, it's null.
|
||||
|
||||
You can also compile a 64 bit system with full precision doubles, but this will use more memory and may be slower.
|
||||
|
||||
You can also compile a 64 bit system with 32 bit floats, stored as a 32 bit int is. Again, out of the 32 bit float range = null.
|
||||
|
||||
You can compile without floating point support at all; 32 bit ints are then used for fixed point calculations.
|
||||
|
||||
Or, you can compile using Dec64, which is a 64 bit decimal floating point format, for exact precision.
|
||||
|
||||
## Objects
|
||||
Objects are heap allocated, referenced by a pointer value. They are all preceded by an object header, the length of a word on the system.
|
||||
|
||||
### 64 bit build
|
||||
56 bits capacity
|
||||
1 bit memory reclamation flag: note that this obj has already been moved
|
||||
2 bit reserved (per object)
|
||||
1 bit stone: note that this obj is immutable
|
||||
3 bit type: note the type of the object
|
||||
1 bit: fwd: note that this obj is a forward linkage
|
||||
|
||||
Last bit ..1:
|
||||
The forward type indicates that the object (an array, blob, pretext, or record) has grown beyond its capacity and is now residing at a new address. The remaining 63 bits contain the address of the enlarged object. Forward linkages are cleaned up by the memory reclaimer.
|
||||
|
||||
Type 7: C light C object
|
||||
|
||||
Header
|
||||
Pointer
|
||||
|
||||
Capacity is an ID of a registered C type.
|
||||
Pointer is a pointer to the opaque C object.
|
||||
|
||||
Type 0: Array
|
||||
Header
|
||||
Length
|
||||
Element[]
|
||||
|
||||
Capacity is number of elements the array can hold. Length is number of elements in use. Number of words used by an array is capacity + 2.
|
||||
|
||||
Type 1: blob
|
||||
Header
|
||||
Length
|
||||
Bit[]
|
||||
Capacity is number of bits the blob can hold. Length is number of bits in use. Bits follow, from [0] to [capacity - 1], with [0] bit in the most significant position of word 2, and [63] in the least significant position of word 2. The last word is zero filled, if necessary.
|
||||
|
||||
Number of words used is (capacity + 63) // 64 + 2
|
||||
|
||||
Type 2: Text
|
||||
Text has two forms, depending on if it is stone or not, which changes the meaning of its length word.
|
||||
|
||||
Header
|
||||
Length(pretext) or Hash(text)
|
||||
Character[0] and character[1]
|
||||
|
||||
Capacity of pretex is the number of characters it can hold. During stoning and reclamation, capacity is set to the length.
|
||||
|
||||
The capacity of a text is its length.
|
||||
|
||||
The length of a pretext is the number of characters it contains; it is not greater than the capacity.
|
||||
|
||||
Hash of a text is used for organizing records. If the hash is zero, it's not been computed yet. All texts in the immutable memory have hashes.
|
||||
|
||||
A text object contains UTF32 characters, packed two per word. If the number of characters is odd, the least significant half of the last word is zero filled.
|
||||
|
||||
The number of words used by a text is (capacity + 1) // 2 + 2
|
||||
|
||||
Type 3: Record
|
||||
|
||||
A record is an array of fields represented as key/value pairs. Fields are located by hashes of texts, using open addressing with linear probing and lazy deletion. The load factor is less than 0.5.
|
||||
|
||||
Header
|
||||
Prototype
|
||||
Length
|
||||
Key[0]
|
||||
Value[0]
|
||||
Key[1]
|
||||
Value[1]
|
||||
...
|
||||
|
||||
The capacity is the number of fields the record can hold. It is a power of two minus one. It is at least twice the length.
|
||||
|
||||
The length is the number of fields that the record currently contains.
|
||||
|
||||
A field candidate number is identified by and(key.hash, capacity). In case of hash collision, advance to the next field. If this goes past the end, continue with field 1. Field 0 is reserved.
|
||||
|
||||
The "exception" special tag is used to mark deleted entries in the object map.
|
||||
|
||||
The number of words used by a record is (capacity + 1) * 2.
|
||||
|
||||
Prototypes are searched for for properties if one cannot be found on the record itself. Prototypes can have prototypes.
|
||||
|
||||
#### key[0] and value[0]
|
||||
These are reserved for internal use, and skipped over during key probing.
|
||||
|
||||
The first 32 bits of key are used as a 32 bit integer key, if this object has ever been used as a key itself.
|
||||
|
||||
The last 32 bits are used as an opaque C class key. C types can be registered with the system, and each are assigned a monotonically increasing number. In the case that this object has a C type, then the bottom 32 bits of key[0] are not 0. If that is the case, then a pointer to its C object is stored in value[0].
|
||||
|
||||
#### Valid keys & Hashing
|
||||
Keys are stored directly in object maps. There are three possibilities for a vaild key: an object text, an object record, or an immediate text.
|
||||
|
||||
In the case of an immediate text, the hash is computed on the fly using the fash64_hash_one function, before being used to look up the key in the object map. Direct value comparison is used to confirm the key.
|
||||
|
||||
For object texts (texts longer than 7 ascii chars), the hash is stored in the text object itself. When an object text is used as a key, a stone version is created and interned. Any program static texts reference this stoned, interned text. When looking up a heap text as a key, it is first discovered if it's in the interned table. If it's not, the key is not in the object (since all keys are interned). If it is, the interned version is returned to check against the object map. The hash of the interned text is used to look up the key in the object map, and then direct pointer comparison is used to confirm the key.
|
||||
|
||||
For record keys, these are unique; once a record is used as a key, it gets assigned a monotonically increasing 32 bit integer, stored in key[0]. When checking it in an object map, the integer is used directly as the key. If key[0] is 0, the record has not been used as a key yet. If it's not 0, fash64_hash_one is used to compute a hash of its ID, and then direct value pointer comparison is used to confirm.
|
||||
|
||||
### Text interning
|
||||
Texts that cannot fit in an immediate, and which are used as an object key, create a stoned and interned version (the pointer which is used as the key). Any text literals are also stoned and interned.
|
||||
|
||||
The interning table is an open addressed hash, with a load of 0.8, using a robin hood value. Probing is done using the text hash, confirmation is done using length, and then memcmp of the text.
|
||||
|
||||
When the GC run, a new interned text table is created. Each text literal, and each text used as a key, is added to the new table, as the live objects are copied. This keeps the interning table from becoming a graveyard. Interned values are never deleted until a GC.
|
||||
|
||||
Type 4: Function
|
||||
|
||||
Header
|
||||
Code
|
||||
Outer
|
||||
A function object has zero capacity and is always stone.
|
||||
|
||||
Code is a pointer to the code object that the function executes.
|
||||
|
||||
Outer is a pointer to the frame that created this function object.
|
||||
|
||||
Size is 3 words.
|
||||
|
||||
Type 5: Frame
|
||||
|
||||
Header
|
||||
Function
|
||||
Caller
|
||||
Return address
|
||||
|
||||
The activation frame is created when a function is invoked to hold its linkages and state.
|
||||
|
||||
The capacity is the number of slots, including the inputs, variables, temporaries, and the four words of overhead. A frame, unlike the other types, is never stone.
|
||||
|
||||
The function is the address of the function object being called.
|
||||
|
||||
The caller is the address of the frame that is invoking the function.
|
||||
|
||||
The return address is the address of the instruction in the code that should be executed upon return.
|
||||
|
||||
Next come the input arguments, if any.
|
||||
|
||||
Then the variables closed over by the inner functions.
|
||||
|
||||
Then the variables that are not closed over, followed by the temporaries.
|
||||
|
||||
When a function returns, the caller is set to zero. This is a signal to the memory reclaimer that the frame can be reduced.
|
||||
|
||||
Type 6: Code
|
||||
|
||||
Header
|
||||
Arity
|
||||
Size
|
||||
Closure size
|
||||
Entry point
|
||||
Disruption point
|
||||
|
||||
A code object exists in the actor's immutable memory. A code object never exists in mutable memory.
|
||||
|
||||
A code object has a zero capacity and is always stone.
|
||||
|
||||
The arity is the maximum number of inputs.
|
||||
|
||||
The size is the capacity of an activation frame that will execute this code.
|
||||
|
||||
The closure size is a reduced capacity for returned frames that survive memory reclamation.
|
||||
|
||||
The entry point is the address at which to begin execution.
|
||||
|
||||
The disruption point is the address of the disruption clause.
|
||||
|
||||
### opaque C objects
|
||||
Records can have opaque C data attached to them.
|
||||
|
||||
A C class can register a GC clean up, and a GC trace function. The trace function is called when the record is encountered in the live object graph; and it should mark any values it wants to keep alive in that function.
|
||||
|
||||
The system maintains an array of live opaque C objects. When such an object is encountered, it marks it as live in the array. When the GC completes, it iterates this array and calls the GC clean up function for each C object in the array with alive=0. Alive is then cleared for the next GC cycle.
|
||||
|
||||
## 32 bit build
|
||||
~3 bit type
|
||||
1 bit stone
|
||||
1 bit memory reclamation flag
|
||||
27 bit capacity
|
||||
|
||||
Key differences here are
|
||||
|
||||
blob max capacity is 2**27 bits = 2**24 bytes = 16 MB [this likely needs addressed]
|
||||
|
||||
fwd is type ...0, and the pointer is 31 bits
|
||||
other types are
|
||||
111 array
|
||||
101 object
|
||||
011 blob
|
||||
001
|
||||
|
||||
## Memory
|
||||
Cell uses a single block of memory that it doles out as needed to the actors in its system.
|
||||
|
||||
Actors are given a block of memory in standard sizes using a doubling buddy memory manager. An actor is given an immutable data section on birth, as well as a mutable data section. When its mutable data becomes full, it requests a new one. Actors utilize their mutable memory with a simple bump allocation. If there is not sufficient memory available, the actor suspends and its status changes to exhausted.
|
||||
|
||||
The smallest block size is determined per platform, but it can be as small as 4KB on 64 bit systems.
|
||||
|
||||
The actor is then given a new block of memory of the same size, and it runs a garbage collector to reclaim memory. It uses the cheney copying algorithm. If a disappointing amount of memory was reclaimed, it is noted, and the actor is given a larger block of memory on the next request.
|
||||
156
docs/nota.md
Normal file
156
docs/nota.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: "Nota Format"
|
||||
description: "Network Object Transfer Arrangement"
|
||||
weight: 85
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols.
|
||||
|
||||
Nota stands for Network Object Transfer Arrangement.
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
JSON had three design rules: minimal, textual, and subset of JavaScript. The textual and JavaScript rules are no longer necessary. Nota maintains JSON's philosophy of being at the intersection of most programming languages and most data types, but departs by using counts instead of brackets and binary encoding instead of text.
|
||||
|
||||
Nota uses Kim continuation bytes for counts and character encoding. See [Kim Encoding](#kim) for details.
|
||||
|
||||
## Type Summary
|
||||
|
||||
| Bits | Type |
|
||||
|------|------|
|
||||
| `000` | Blob |
|
||||
| `001` | Text |
|
||||
| `010` | Array |
|
||||
| `011` | Record |
|
||||
| `100` | Floating Point (positive exponent) |
|
||||
| `101` | Floating Point (negative exponent) |
|
||||
| `110` | Integer (zero exponent) |
|
||||
| `111` | Symbol |
|
||||
|
||||
## Preambles
|
||||
|
||||
Every Nota value starts with a preamble byte that is a Kim value with the three most significant bits used for type information.
|
||||
|
||||
Most types provide 3 or 4 data bits in the preamble. If the Kim encoding of the data fits in those bits, it is incorporated directly and the continue bit is off. Otherwise the continue bit is on and the continuation follows.
|
||||
|
||||
## Blob
|
||||
|
||||
```
|
||||
C 0 0 0 D D D D
|
||||
```
|
||||
|
||||
- **C** — continue the number of bits
|
||||
- **DDDD** — the number of bits
|
||||
|
||||
A blob is a string of bits. The data produces the number of bits. The number of bytes that follow: `floor((number_of_bits + 7) / 8)`. The final byte is padded with 0 if necessary.
|
||||
|
||||
Example: A blob containing 25 bits `1111000011100011001000001`:
|
||||
|
||||
```
|
||||
80 19 F0 E3 20 80
|
||||
```
|
||||
|
||||
## Text
|
||||
|
||||
```
|
||||
C 0 0 1 D D D D
|
||||
```
|
||||
|
||||
- **C** — continue the number of characters
|
||||
- **DDDD** — the number of characters
|
||||
|
||||
The data produces the number of characters. Kim-encoded characters follow. ASCII characters are 1 byte, first quarter BMP characters are 2 bytes, all other Unicode characters are 3 bytes. Unlike JSON, there is never a need for escapement.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
"" → 10
|
||||
"cat" → 13 63 61 74
|
||||
```
|
||||
|
||||
## Array
|
||||
|
||||
```
|
||||
C 0 1 0 D D D D
|
||||
```
|
||||
|
||||
- **C** — continue the number of elements
|
||||
- **DDDD** — the number of elements
|
||||
|
||||
An array is an ordered sequence of values. Following the preamble are the elements, each beginning with its own preamble. Nesting is encouraged.
|
||||
|
||||
## Record
|
||||
|
||||
```
|
||||
C 0 1 1 D D D D
|
||||
```
|
||||
|
||||
- **C** — continue the number of pairs
|
||||
- **DDDD** — the number of pairs
|
||||
|
||||
A record is an unordered collection of key/value pairs. Keys must be text and must be unique within the record. Values can be any Nota type.
|
||||
|
||||
## Floating Point
|
||||
|
||||
```
|
||||
C 1 0 E S D D D
|
||||
```
|
||||
|
||||
- **C** — continue the exponent
|
||||
- **E** — sign of the exponent
|
||||
- **S** — sign of the coefficient
|
||||
- **DDD** — three bits of the exponent
|
||||
|
||||
Nota floating point represents numbers as `coefficient * 10^exponent`. The coefficient must be an integer. The preamble may contain the first three bits of the exponent, followed by the continuation of the exponent (if any), followed by the coefficient.
|
||||
|
||||
Use the integer type when the exponent is zero.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
-1.01 → 5A 65
|
||||
98.6 → 51 87 5A
|
||||
-0.5772156649 → D8 0A 95 C0 B0 BD 69
|
||||
-10000000000000 → C8 0D 01
|
||||
```
|
||||
|
||||
## Integer
|
||||
|
||||
```
|
||||
C 1 1 0 S D D D
|
||||
```
|
||||
|
||||
- **C** — continue the integer
|
||||
- **S** — sign
|
||||
- **DDD** — three bits of the integer
|
||||
|
||||
Integers in the range -7 to 7 fit in a single byte. Integers in the range -1023 to 1023 fit in two bytes. Integers in the range -131071 to 131071 fit in three bytes.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
0 → 60
|
||||
2023 → E0 8F 67
|
||||
-1 → 69
|
||||
```
|
||||
|
||||
## Symbol
|
||||
|
||||
```
|
||||
0 1 1 1 D D D D
|
||||
```
|
||||
|
||||
- **DDDD** — the symbol
|
||||
|
||||
There are currently five symbols:
|
||||
|
||||
```
|
||||
null → 70
|
||||
false → 72
|
||||
true → 73
|
||||
private → 78
|
||||
system → 79
|
||||
```
|
||||
|
||||
The private prefix must be followed by a record containing a private process address. The system prefix must be followed by a record containing a system message. All other symbols are reserved.
|
||||
@@ -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.
|
||||
|
||||
287
docs/pitcode.md
287
docs/pitcode.md
@@ -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
|
||||
|
||||
176
docs/requestors.md
Normal file
176
docs/requestors.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: "Requestors"
|
||||
description: "Asynchronous work with requestors"
|
||||
weight: 25
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Requestors are functions that encapsulate asynchronous work. They provide a structured way to compose callbacks, manage cancellation, and coordinate concurrent operations between actors.
|
||||
|
||||
## What is a Requestor
|
||||
|
||||
A requestor is a function with this signature:
|
||||
|
||||
```javascript
|
||||
function my_requestor(callback, value) {
|
||||
// Do async work, then call callback with result
|
||||
// Return a cancel function
|
||||
}
|
||||
```
|
||||
|
||||
- **callback** — called when the work completes: `callback(value, reason)`
|
||||
- On success: `callback(result)` or `callback(result, null)`
|
||||
- On failure: `callback(null, reason)` where reason explains the failure
|
||||
- **value** — input passed from the previous step (or the initial caller)
|
||||
- **return** — a cancel function, or null if cancellation is not supported
|
||||
|
||||
The cancel function, when called, should abort the in-progress work.
|
||||
|
||||
## Writing a Requestor
|
||||
|
||||
```javascript
|
||||
function fetch_data(callback, url) {
|
||||
$contact(function(connection) {
|
||||
$send(connection, {get: url}, function(response) {
|
||||
callback(response)
|
||||
})
|
||||
}, {host: url, port: 80})
|
||||
return function cancel() {
|
||||
// clean up if needed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A requestor that always succeeds immediately:
|
||||
|
||||
```javascript
|
||||
function constant(callback, value) {
|
||||
callback(42)
|
||||
}
|
||||
```
|
||||
|
||||
A requestor that always fails:
|
||||
|
||||
```javascript
|
||||
function broken(callback, value) {
|
||||
callback(null, "something went wrong")
|
||||
}
|
||||
```
|
||||
|
||||
## Composing Requestors
|
||||
|
||||
ƿit provides four built-in functions for composing requestors into pipelines.
|
||||
|
||||
### sequence(requestor_array)
|
||||
|
||||
Run requestors one after another. Each result becomes the input to the next. The final result is passed to the callback.
|
||||
|
||||
```javascript
|
||||
var pipeline = sequence([
|
||||
fetch_user,
|
||||
validate_permissions,
|
||||
load_profile
|
||||
])
|
||||
|
||||
pipeline(function(profile, reason) {
|
||||
if (reason) {
|
||||
log.error(reason)
|
||||
} else {
|
||||
log.console(profile.name)
|
||||
}
|
||||
}, user_id)
|
||||
```
|
||||
|
||||
If any step fails, the remaining steps are skipped and the failure propagates.
|
||||
|
||||
### parallel(requestor_array, throttle, need)
|
||||
|
||||
Start all requestors concurrently. Results are collected into an array matching the input order.
|
||||
|
||||
```javascript
|
||||
var both = parallel([
|
||||
fetch_profile,
|
||||
fetch_settings
|
||||
])
|
||||
|
||||
both(function(results, reason) {
|
||||
var profile = results[0]
|
||||
var settings = results[1]
|
||||
}, user_id)
|
||||
```
|
||||
|
||||
- **throttle** — limit how many requestors run at once (null for no limit)
|
||||
- **need** — minimum number of successes required (default: all)
|
||||
|
||||
### race(requestor_array, throttle, need)
|
||||
|
||||
Like `parallel`, but returns as soon as the needed number of results arrive. Unfinished requestors are cancelled.
|
||||
|
||||
```javascript
|
||||
var fastest = race([
|
||||
fetch_from_cache,
|
||||
fetch_from_network,
|
||||
fetch_from_backup
|
||||
])
|
||||
|
||||
fastest(function(results) {
|
||||
// results[0] is whichever responded first
|
||||
}, request)
|
||||
```
|
||||
|
||||
Default need is 1. Useful for redundant operations where only one result matters.
|
||||
|
||||
### fallback(requestor_array)
|
||||
|
||||
Try each requestor in order. If one fails, try the next. Return the first success.
|
||||
|
||||
```javascript
|
||||
var resilient = fallback([
|
||||
fetch_from_primary,
|
||||
fetch_from_secondary,
|
||||
use_cached_value
|
||||
])
|
||||
|
||||
resilient(function(data, reason) {
|
||||
if (reason) {
|
||||
log.error("all sources failed")
|
||||
}
|
||||
}, key)
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
||||
Wrap any requestor with `$time_limit` to add a timeout:
|
||||
|
||||
```javascript
|
||||
var timed = $time_limit(fetch_data, 5) // 5 second timeout
|
||||
|
||||
timed(function(result, reason) {
|
||||
// reason will explain timeout if it fires
|
||||
}, url)
|
||||
```
|
||||
|
||||
If the requestor does not complete within the time limit, it is cancelled and the callback receives a failure.
|
||||
|
||||
## Requestors and Actors
|
||||
|
||||
Requestors are particularly useful with actor messaging. Since `$send` is callback-based, it fits naturally:
|
||||
|
||||
```javascript
|
||||
function ask_worker(callback, task) {
|
||||
$send(worker, task, function(reply) {
|
||||
callback(reply)
|
||||
})
|
||||
}
|
||||
|
||||
var pipeline = sequence([
|
||||
ask_worker,
|
||||
process_result,
|
||||
store_result
|
||||
])
|
||||
|
||||
pipeline(function(stored) {
|
||||
log.console("done")
|
||||
$stop()
|
||||
}, {type: "compute", data: [1, 2, 3]})
|
||||
```
|
||||
118
docs/spec/bytecode.md
Normal file
118
docs/spec/bytecode.md
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: "Bytecode VM"
|
||||
description: "Stack-based virtual machine"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The bytecode VM is a stack-based virtual machine. Instructions operate on an implicit operand stack, pushing and popping values. This is the original execution backend for ƿit.
|
||||
|
||||
## Compilation Pipeline
|
||||
|
||||
```
|
||||
Source → Tokenize → Parse (AST) → Bytecode → Link → Execute
|
||||
```
|
||||
|
||||
The compiler emits `JSFunctionBytecode` objects containing opcode sequences, constant pools, and debug information.
|
||||
|
||||
## Instruction Categories
|
||||
|
||||
### Value Loading
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `push_i32` | Push a 32-bit immediate integer |
|
||||
| `push_const` | Push a value from the constant pool |
|
||||
| `null` | Push null |
|
||||
| `push_false` | Push false |
|
||||
| `push_true` | Push true |
|
||||
|
||||
### Stack Manipulation
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `drop` | Remove top of stack |
|
||||
| `dup` | Duplicate top of stack |
|
||||
| `dup1` / `dup2` / `dup3` | Duplicate item at depth |
|
||||
| `swap` | Swap top two items |
|
||||
| `rot3l` / `rot3r` | Rotate top three items |
|
||||
| `insert2` / `insert3` | Insert top item deeper |
|
||||
| `nip` | Remove second item |
|
||||
|
||||
### Variable Access
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `get_var` | Load variable by name (pre-link) |
|
||||
| `put_var` | Store variable by name (pre-link) |
|
||||
| `get_loc` / `put_loc` | Access local variable by index |
|
||||
| `get_arg` / `put_arg` | Access function argument by index |
|
||||
| `get_env_slot` / `set_env_slot` | Access closure variable (post-link) |
|
||||
| `get_global_slot` / `set_global_slot` | Access global variable (post-link) |
|
||||
|
||||
Variable access opcodes are patched during linking. `get_var` instructions are rewritten to `get_loc`, `get_env_slot`, or `get_global_slot` depending on where the variable is resolved.
|
||||
|
||||
### Arithmetic
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `add` / `sub` / `mul` / `div` | Basic arithmetic |
|
||||
| `mod` / `pow` | Modulo and power |
|
||||
| `neg` / `inc` / `dec` | Unary operations |
|
||||
| `add_loc` / `inc_loc` / `dec_loc` | Optimized local variable update |
|
||||
|
||||
### Comparison and Logic
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `strict_eq` / `strict_neq` | Equality (ƿit uses strict only) |
|
||||
| `lt` / `lte` / `gt` / `gte` | Ordered comparison |
|
||||
| `not` / `lnot` | Logical / bitwise not |
|
||||
| `and` / `or` / `xor` | Bitwise operations |
|
||||
|
||||
### Control Flow
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `goto` | Unconditional jump |
|
||||
| `if_true` / `if_false` | Conditional jump |
|
||||
| `goto8` / `goto16` | Short jumps (size-optimized) |
|
||||
| `if_true8` / `if_false8` | Short conditional jumps |
|
||||
| `catch` | Set exception handler |
|
||||
|
||||
### Function Calls
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `call` | Call function with N arguments |
|
||||
| `tail_call` | Tail-call optimization |
|
||||
| `call_method` | Call method on object |
|
||||
| `return` | Return value from function |
|
||||
| `return_undef` | Return null from function |
|
||||
| `throw` | Throw exception (disrupt) |
|
||||
|
||||
### Property Access
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `get_field` | Get named property |
|
||||
| `put_field` | Set named property |
|
||||
| `get_array_el` | Get computed property |
|
||||
| `put_array_el` | Set computed property |
|
||||
| `define_field` | Define property during object literal |
|
||||
|
||||
### Object Creation
|
||||
|
||||
| Opcode | Description |
|
||||
|--------|-------------|
|
||||
| `object` | Create new empty object |
|
||||
| `array_from` | Create array from stack values |
|
||||
|
||||
## Bytecode Patching
|
||||
|
||||
During the link/integrate phase, symbolic variable references are resolved to concrete access instructions. This is a critical optimization — the interpreter does not perform name lookups at runtime.
|
||||
|
||||
A `get_var "x"` instruction becomes:
|
||||
- `get_loc 3` — if x is local variable at index 3
|
||||
- `get_env_slot 1, 5` — if x is captured from outer scope (depth 1, slot 5)
|
||||
- `get_global_slot 7` — if x is a global
|
||||
77
docs/spec/dec64.md
Normal file
77
docs/spec/dec64.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: "DEC64 Numbers"
|
||||
description: "Decimal floating point representation"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
ƿit uses DEC64 as its number format. DEC64 represents numbers as `coefficient * 10^exponent` in a 64-bit word. This eliminates the rounding errors that plague IEEE 754 binary floating point — `0.1 + 0.2` is exactly `0.3`.
|
||||
|
||||
DEC64 was designed by Douglas Crockford as a general-purpose number type suitable for both business and scientific computation.
|
||||
|
||||
## Format
|
||||
|
||||
A DEC64 number is a 64-bit value:
|
||||
|
||||
```
|
||||
[coefficient: 56 bits][exponent: 8 bits]
|
||||
```
|
||||
|
||||
- **Coefficient** — a 56-bit signed integer (two's complement)
|
||||
- **Exponent** — an 8-bit signed integer (range: -127 to 127)
|
||||
|
||||
The value of a DEC64 number is: `coefficient * 10^exponent`
|
||||
|
||||
### Examples
|
||||
|
||||
| Value | Coefficient | Exponent | Hex |
|
||||
|-------|------------|----------|-----|
|
||||
| `0` | 0 | 0 | `0000000000000000` |
|
||||
| `1` | 1 | 0 | `0000000000000100` |
|
||||
| `3.14159` | 314159 | -5 | `000000004CB2FFFB` |
|
||||
| `-1` | -1 | 0 | `FFFFFFFFFFFFFF00` |
|
||||
| `1000000` | 1 | 6 | `0000000000000106` |
|
||||
|
||||
## Special Values
|
||||
|
||||
### Null
|
||||
|
||||
The exponent `0x80` (-128) indicates null. This is the only special value — there is no infinity, no NaN, no negative zero. Operations that would produce undefined results (such as division by zero) return null.
|
||||
|
||||
```
|
||||
coefficient: any, exponent: 0x80 → null
|
||||
```
|
||||
|
||||
## Arithmetic Properties
|
||||
|
||||
- **Exact decimals**: All decimal fractions with up to 17 significant digits are represented exactly
|
||||
- **No rounding**: `0.1 + 0.2 == 0.3` is true
|
||||
- **Integer range**: Exact integers up to 2^55 (about 3.6 * 10^16)
|
||||
- **Normalized on demand**: The runtime normalizes coefficients to remove trailing zeros when needed for comparison
|
||||
|
||||
## Comparison with IEEE 754
|
||||
|
||||
| Property | DEC64 | IEEE 754 double |
|
||||
|----------|-------|----------------|
|
||||
| Decimal fractions | Exact | Approximate |
|
||||
| Significant digits | ~17 | ~15-16 |
|
||||
| Special values | null only | NaN, ±Infinity, -0 |
|
||||
| Rounding errors | None (decimal) | Common |
|
||||
| Financial arithmetic | Correct | Requires libraries |
|
||||
| Scientific range | ±10^127 | ±10^308 |
|
||||
|
||||
DEC64 trades a smaller exponent range for exact decimal arithmetic. Most applications never need exponents beyond ±127.
|
||||
|
||||
## In ƿit
|
||||
|
||||
All numbers in ƿit are DEC64. There is no separate integer type at the language level — the distinction is internal. The `is_integer` function checks whether a number has no fractional part.
|
||||
|
||||
```javascript
|
||||
var x = 42 // coefficient: 42, exponent: 0
|
||||
var y = 3.14 // coefficient: 314, exponent: -2
|
||||
var z = 1000000 // coefficient: 1, exponent: 6 (normalized)
|
||||
|
||||
is_integer(x) // true
|
||||
is_integer(y) // false
|
||||
1 / 0 // null
|
||||
```
|
||||
82
docs/spec/gc.md
Normal file
82
docs/spec/gc.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "Garbage Collection"
|
||||
description: "Cheney copying collector"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
ƿit uses a Cheney copying collector for automatic memory management. Each actor has its own independent heap — actors never share mutable memory, so garbage collection is per-actor with no global pauses.
|
||||
|
||||
## Algorithm
|
||||
|
||||
The Cheney algorithm is a two-space copying collector:
|
||||
|
||||
1. **Allocate new space** — a fresh memory block for the new heap
|
||||
2. **Copy roots** — copy all live root objects from old space to new space
|
||||
3. **Scan** — walk the new space, updating all internal references
|
||||
4. **Free old space** — the entire old heap is freed at once
|
||||
|
||||
### Copying and Forwarding
|
||||
|
||||
When an object is copied from old space to new space:
|
||||
|
||||
1. The object's data is copied to the next free position in new space
|
||||
2. The old object's header is overwritten with a **forwarding pointer** (`OBJ_FORWARD`) containing the new address
|
||||
3. Future references to the old address find the forwarding pointer and follow it to the new location
|
||||
|
||||
```
|
||||
Old space: New space:
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ OBJ_FORWARD ─┼────────> │ copied object│
|
||||
│ (new addr) │ │ │
|
||||
└──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### Scan Phase
|
||||
|
||||
After roots are copied, the collector scans new space linearly. For each object, it examines every JSValue field:
|
||||
|
||||
- If the field points to old space, copy the referenced object (or follow its forwarding pointer if already copied)
|
||||
- If the field points to stone memory, skip it (stone objects are permanent)
|
||||
- If the field is an immediate value (integer, boolean, null, immediate string), skip it
|
||||
|
||||
The scan continues until the scan pointer catches up with the allocation pointer — at that point, all live objects have been found and copied.
|
||||
|
||||
## Roots
|
||||
|
||||
The collector traces from these root sources:
|
||||
|
||||
- **Global object** — all global variables
|
||||
- **Class prototypes** — built-in type prototypes
|
||||
- **Exception** — the current exception value
|
||||
- **Value stack** — all values on the operand stack
|
||||
- **Frame stack** — all stack frames (bytecode and register VM)
|
||||
- **GC reference stack** — manually registered roots (via `JS_PUSH_VALUE` / `JS_POP_VALUE`)
|
||||
- **Parser constant pool** — during compilation, constants being built
|
||||
|
||||
## Per-Actor Heaps
|
||||
|
||||
Each actor maintains its own heap with independent collection:
|
||||
|
||||
- No stop-the-world pauses across actors
|
||||
- No synchronization between collectors
|
||||
- Each actor's GC runs at the end of a turn (between message deliveries)
|
||||
- Heap sizes adapt independently based on each actor's allocation patterns
|
||||
|
||||
## Heap Growth
|
||||
|
||||
The collector uses a buddy allocator for heap blocks. After each collection, if less than 20% of the heap was recovered, the next block size is doubled. The new space size is: `max(live_estimate + alloc_size, next_block_size)`.
|
||||
|
||||
All allocations within a heap block use bump allocation (advance a pointer), which is extremely fast.
|
||||
|
||||
## Alignment
|
||||
|
||||
All objects are aligned to 8-byte boundaries. Object sizes are rounded up to ensure this alignment, which guarantees that the low 3 bits of any heap pointer are always zero — available for JSValue tag bits.
|
||||
|
||||
## Interaction with Stone Memory
|
||||
|
||||
Stone memory objects (S bit set) are never copied by the collector. When the scanner encounters a pointer to stone memory, it leaves it unchanged. This means:
|
||||
|
||||
- Stone objects are effectively permanent GC roots
|
||||
- No overhead for tracing through immutable object graphs
|
||||
- Module return values and interned strings impose zero GC cost
|
||||
156
docs/spec/mach.md
Normal file
156
docs/spec/mach.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: "Register VM"
|
||||
description: "Register-based virtual machine (Mach)"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Mach VM is a register-based virtual machine using 32-bit instructions. It is modeled after Lua's register VM — operands are register indices rather than stack positions, reducing instruction count and improving performance.
|
||||
|
||||
## Instruction Formats
|
||||
|
||||
All instructions are 32 bits wide. Four encoding formats are used:
|
||||
|
||||
### iABC — Three-Register
|
||||
|
||||
```
|
||||
[op: 8][A: 8][B: 8][C: 8]
|
||||
```
|
||||
|
||||
Used for operations on three registers: `R(A) = R(B) op R(C)`.
|
||||
|
||||
### iABx — Register + Constant
|
||||
|
||||
```
|
||||
[op: 8][A: 8][Bx: 16]
|
||||
```
|
||||
|
||||
Used for loading constants: `R(A) = K(Bx)`.
|
||||
|
||||
### iAsBx — Register + Signed Offset
|
||||
|
||||
```
|
||||
[op: 8][A: 8][sBx: 16]
|
||||
```
|
||||
|
||||
Used for conditional jumps: if `R(A)` then jump by `sBx`.
|
||||
|
||||
### isJ — Signed Jump
|
||||
|
||||
```
|
||||
[op: 8][sJ: 24]
|
||||
```
|
||||
|
||||
Used for unconditional jumps with a 24-bit signed offset.
|
||||
|
||||
## Registers
|
||||
|
||||
Each function frame has a fixed number of register slots, determined at compile time. Registers hold:
|
||||
|
||||
- **R(0)** — `this` binding
|
||||
- **R(1)..R(arity)** — function arguments
|
||||
- **R(arity+1)..** — local variables and temporaries
|
||||
|
||||
## Instruction Set
|
||||
|
||||
### Loading
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `LOADK` | iABx | `R(A) = K(Bx)` — load from constant pool |
|
||||
| `LOADI` | iAsBx | `R(A) = sBx` — load small integer |
|
||||
| `LOADNULL` | iA | `R(A) = null` |
|
||||
| `LOADTRUE` | iA | `R(A) = true` |
|
||||
| `LOADFALSE` | iA | `R(A) = false` |
|
||||
| `MOVE` | iABC | `R(A) = R(B)` — register copy |
|
||||
|
||||
### Arithmetic
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `ADD` | iABC | `R(A) = R(B) + R(C)` |
|
||||
| `SUB` | iABC | `R(A) = R(B) - R(C)` |
|
||||
| `MUL` | iABC | `R(A) = R(B) * R(C)` |
|
||||
| `DIV` | iABC | `R(A) = R(B) / R(C)` |
|
||||
| `MOD` | iABC | `R(A) = R(B) % R(C)` |
|
||||
| `POW` | iABC | `R(A) = R(B) ^ R(C)` |
|
||||
| `NEG` | iABC | `R(A) = -R(B)` |
|
||||
| `INC` | iABC | `R(A) = R(B) + 1` |
|
||||
| `DEC` | iABC | `R(A) = R(B) - 1` |
|
||||
|
||||
### Comparison
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `EQ` | iABC | `R(A) = R(B) == R(C)` |
|
||||
| `NEQ` | iABC | `R(A) = R(B) != R(C)` |
|
||||
| `LT` | iABC | `R(A) = R(B) < R(C)` |
|
||||
| `LE` | iABC | `R(A) = R(B) <= R(C)` |
|
||||
| `GT` | iABC | `R(A) = R(B) > R(C)` |
|
||||
| `GE` | iABC | `R(A) = R(B) >= R(C)` |
|
||||
|
||||
### Property Access
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `GETFIELD` | iABC | `R(A) = R(B)[K(C)]` — named property |
|
||||
| `SETFIELD` | iABC | `R(A)[K(B)] = R(C)` — set named property |
|
||||
| `GETINDEX` | iABC | `R(A) = R(B)[R(C)]` — computed property |
|
||||
| `SETINDEX` | iABC | `R(A)[R(B)] = R(C)` — set computed property |
|
||||
|
||||
### Variable Resolution
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `GETNAME` | iABx | Unresolved variable (compiler placeholder) |
|
||||
| `GETINTRINSIC` | iABx | Global intrinsic / built-in |
|
||||
| `GETENV` | iABx | Module environment variable |
|
||||
| `GETUP` | iABC | `R(A) = UpFrame(B).slots[C]` — closure upvalue |
|
||||
| `SETUP` | iABC | `UpFrame(A).slots[B] = R(C)` — set closure upvalue |
|
||||
|
||||
### Control Flow
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `JMP` | isJ | Unconditional jump |
|
||||
| `JMPTRUE` | iAsBx | Jump if `R(A)` is true |
|
||||
| `JMPFALSE` | iAsBx | Jump if `R(A)` is false |
|
||||
| `JMPNULL` | iAsBx | Jump if `R(A)` is null |
|
||||
|
||||
### Function Calls
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `CALL` | iABC | Call `R(A)` with `B` args starting at `R(A+1)`, `C`=keep result |
|
||||
| `RETURN` | iA | Return `R(A)` |
|
||||
| `RETNIL` | — | Return null |
|
||||
| `CLOSURE` | iABx | Create closure from function pool entry `Bx` |
|
||||
|
||||
### Object / Array
|
||||
|
||||
| Opcode | Format | Description |
|
||||
|--------|--------|-------------|
|
||||
| `NEWOBJECT` | iA | `R(A) = {}` |
|
||||
| `NEWARRAY` | iABC | `R(A) = array(B)` |
|
||||
| `PUSH` | iABC | Push `R(B)` to array `R(A)` |
|
||||
|
||||
## JSCodeRegister
|
||||
|
||||
The compiled output for a function:
|
||||
|
||||
```c
|
||||
struct JSCodeRegister {
|
||||
uint16_t arity; // argument count
|
||||
uint16_t nr_slots; // total register count
|
||||
uint32_t cpool_count; // constant pool size
|
||||
JSValue *cpool; // constant pool
|
||||
uint32_t instr_count; // instruction count
|
||||
MachInstr32 *instructions; // 32-bit instruction array
|
||||
uint32_t func_count; // nested function count
|
||||
JSCodeRegister **functions; // nested function table
|
||||
JSValue name; // function name
|
||||
uint16_t disruption_pc; // exception handler offset
|
||||
};
|
||||
```
|
||||
|
||||
The constant pool holds all non-immediate values referenced by `LOADK` instructions: strings, large numbers, and other constants.
|
||||
90
docs/spec/mcode.md
Normal file
90
docs/spec/mcode.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "Mcode IR"
|
||||
description: "JSON-based intermediate representation"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Mcode is a JSON-based intermediate representation that can be interpreted directly. It represents the same operations as the Mach register VM but uses string-based instruction dispatch rather than binary opcodes. Mcode is intended as an intermediate step toward native code compilation.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Source → Tokenize → Parse (AST) → Mcode (JSON) → Interpret
|
||||
→ Compile to Mach (planned)
|
||||
→ Compile to native (planned)
|
||||
```
|
||||
|
||||
Mcode is produced by the `JS_Mcode` compiler pass, which emits a cJSON tree. The mcode interpreter walks this tree directly, dispatching on instruction name strings.
|
||||
|
||||
## JSMCode Structure
|
||||
|
||||
```c
|
||||
struct JSMCode {
|
||||
uint16_t nr_args; // argument count
|
||||
uint16_t nr_slots; // register count
|
||||
cJSON **instrs; // pre-flattened instruction array
|
||||
uint32_t instr_count; // number of instructions
|
||||
|
||||
struct {
|
||||
const char *name; // label name
|
||||
uint32_t index; // instruction index
|
||||
} *labels;
|
||||
uint32_t label_count;
|
||||
|
||||
struct JSMCode **functions; // nested functions
|
||||
uint32_t func_count;
|
||||
|
||||
cJSON *json_root; // keeps JSON alive
|
||||
const char *name; // function name
|
||||
const char *filename; // source file
|
||||
uint16_t disruption_pc; // exception handler offset
|
||||
};
|
||||
```
|
||||
|
||||
## Instruction Format
|
||||
|
||||
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands:
|
||||
|
||||
```json
|
||||
["LOADK", 0, 42]
|
||||
["ADD", 2, 0, 1]
|
||||
["JMPFALSE", 3, "else_label"]
|
||||
["CALL", 0, 2, 1]
|
||||
```
|
||||
|
||||
The instruction set mirrors the Mach VM opcodes — same operations, same register semantics, but with string dispatch instead of numeric opcodes.
|
||||
|
||||
## Labels
|
||||
|
||||
Control flow uses named labels instead of numeric offsets:
|
||||
|
||||
```json
|
||||
["LABEL", "loop_start"]
|
||||
["ADD", 1, 1, 2]
|
||||
["JMPFALSE", 3, "loop_end"]
|
||||
["JMP", "loop_start"]
|
||||
["LABEL", "loop_end"]
|
||||
```
|
||||
|
||||
Labels are collected into a name-to-index map during loading, enabling O(1) jump resolution.
|
||||
|
||||
## Differences from Mach
|
||||
|
||||
| Property | Mcode | Mach |
|
||||
|----------|-------|------|
|
||||
| Instructions | cJSON arrays | 32-bit binary |
|
||||
| Dispatch | String comparison | Switch on opcode byte |
|
||||
| Constants | Inline in JSON | Separate constant pool |
|
||||
| Jump targets | Named labels | Numeric offsets |
|
||||
| Memory | Heap (cJSON nodes) | Off-heap (malloc) |
|
||||
|
||||
## Purpose
|
||||
|
||||
Mcode serves as an inspectable, debuggable intermediate format:
|
||||
|
||||
- **Human-readable** — the JSON representation can be printed and examined
|
||||
- **Language-independent** — any tool that produces the correct JSON can target the ƿit runtime
|
||||
- **Compilation target** — the Mach compiler can consume mcode as input, and future native code generators can work from the same representation
|
||||
|
||||
The cost of string-based dispatch makes mcode slower than the binary Mach VM, so it is primarily useful during development and as a compilation intermediate rather than for production execution.
|
||||
142
docs/spec/objects.md
Normal file
142
docs/spec/objects.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: "Object Types"
|
||||
description: "Heap object header format and types"
|
||||
---
|
||||
|
||||
## Object Header
|
||||
|
||||
Every heap-allocated object begins with a 64-bit header word (`objhdr_t`):
|
||||
|
||||
```
|
||||
[capacity: 56 bits][flags: 5 bits][type: 3 bits]
|
||||
```
|
||||
|
||||
### Type Field (bits 0-2)
|
||||
|
||||
| Value | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| 0 | `OBJ_ARRAY` | Dynamic array of JSValues |
|
||||
| 1 | `OBJ_BLOB` | Binary data (bits) |
|
||||
| 2 | `OBJ_TEXT` | Unicode text string |
|
||||
| 3 | `OBJ_RECORD` | Key-value object with prototype chain |
|
||||
| 4 | `OBJ_FUNCTION` | Function (C, bytecode, register, or mcode) |
|
||||
| 5 | `OBJ_CODE` | Compiled bytecode |
|
||||
| 6 | `OBJ_FRAME` | Stack frame for closures |
|
||||
| 7 | `OBJ_FORWARD` | Forwarding pointer (GC) |
|
||||
|
||||
### Flags (bits 3-7)
|
||||
|
||||
- **Bit 3 (S)** — Stone flag. If set, the object is immutable and excluded from GC.
|
||||
- **Bit 4 (P)** — Properties flag.
|
||||
- **Bit 5 (A)** — Array flag.
|
||||
- **Bit 7 (R)** — Reserved.
|
||||
|
||||
### Capacity (bits 8-63)
|
||||
|
||||
The interpretation of the 56-bit capacity field depends on the object type.
|
||||
|
||||
## Array
|
||||
|
||||
```c
|
||||
struct JSArray {
|
||||
objhdr_t header; // type=0, capacity=element slots
|
||||
word_t len; // current number of elements
|
||||
JSValue values[]; // inline flexible array
|
||||
};
|
||||
```
|
||||
|
||||
Capacity is the number of JSValue slots allocated. Length is the number currently in use. Arrays grow by reallocating with a larger capacity.
|
||||
|
||||
## Blob
|
||||
|
||||
```c
|
||||
struct JSBlob {
|
||||
objhdr_t header; // type=1, capacity=allocated bits
|
||||
word_t length; // length in bits
|
||||
uint8_t bits[]; // bit-packed data
|
||||
};
|
||||
```
|
||||
|
||||
Blobs are bit-addressable. The length field tracks the exact number of bits written. A blob starts as antestone (mutable) for writing, then becomes stone (immutable) for reading.
|
||||
|
||||
## Text
|
||||
|
||||
```c
|
||||
struct JSText {
|
||||
objhdr_t header; // type=2, capacity=character slots
|
||||
word_t length; // length in codepoints (or hash if stoned)
|
||||
word_t packed[]; // two UTF-32 chars per 64-bit word
|
||||
};
|
||||
```
|
||||
|
||||
Text is stored as UTF-32, with two 32-bit codepoints packed per 64-bit word. When a text object is stoned, the length field is repurposed to cache the hash value (computed via `fash64`), since stoned text is immutable and the hash never changes.
|
||||
|
||||
## Record
|
||||
|
||||
```c
|
||||
struct JSRecord {
|
||||
objhdr_t header; // type=3, capacity=hash table slots
|
||||
JSRecord *proto; // prototype chain pointer
|
||||
word_t len; // number of entries
|
||||
slot slots[]; // key-value pairs (hash table)
|
||||
};
|
||||
```
|
||||
|
||||
Records use a hash table with linear probing. Slot 0 is reserved for internal metadata (class ID and record ID). Empty slots use `JS_NULL` as the key; deleted slots use `JS_EXCEPTION` as a tombstone.
|
||||
|
||||
The prototype chain is a linked list of JSRecord pointers, traversed during property lookup.
|
||||
|
||||
## Function
|
||||
|
||||
```c
|
||||
struct JSFunction {
|
||||
objhdr_t header; // type=4
|
||||
JSValue name; // function name
|
||||
int16_t length; // arity (-1 for variadic)
|
||||
uint8_t kind; // C, bytecode, register, or mcode
|
||||
union {
|
||||
struct { ... } cfunc; // C function pointer
|
||||
struct { ... } bytecode; // bytecode + frame
|
||||
struct { ... } regvm; // register VM code
|
||||
struct { ... } mcode; // mcode IR
|
||||
} u;
|
||||
};
|
||||
```
|
||||
|
||||
The kind field selects which union variant is active. Functions can be implemented in C (native), bytecode (stack VM), register code (mach VM), or mcode (JSON interpreter).
|
||||
|
||||
## Frame
|
||||
|
||||
```c
|
||||
struct JSFrame {
|
||||
objhdr_t header; // type=6, capacity=slot count
|
||||
JSValue function; // owning function
|
||||
JSValue caller; // parent frame
|
||||
uint32_t return_pc; // return address
|
||||
JSValue slots[]; // [this][args][captured][locals][temps]
|
||||
};
|
||||
```
|
||||
|
||||
Frames capture the execution context for closures. The slots array contains the function's `this` binding, arguments, captured upvalues, local variables, and temporaries. Frames are linked via the caller field for upvalue resolution across closure depth.
|
||||
|
||||
## Forwarding Pointer
|
||||
|
||||
```
|
||||
[pointer: 61 bits][111]
|
||||
```
|
||||
|
||||
During garbage collection, when an object is copied to the new heap, the old header is replaced with a forwarding pointer to the new location. This is type 7 (`OBJ_FORWARD`) and stores the new address in bits 3-63. See [Garbage Collection](#gc) for details.
|
||||
|
||||
## Object Sizing
|
||||
|
||||
All objects are aligned to 8 bytes. The total size in bytes for each type:
|
||||
|
||||
| Type | Size |
|
||||
|------|------|
|
||||
| Array | `8 + 8 + capacity * 8` |
|
||||
| Blob | `8 + 8 + ceil(capacity / 8)` |
|
||||
| Text | `8 + 8 + ceil(capacity / 2) * 8` |
|
||||
| Record | `8 + 8 + 8 + (capacity + 1) * 16` |
|
||||
| Function | `sizeof(JSFunction)` (fixed) |
|
||||
| Code | `sizeof(JSFunctionBytecode)` (fixed) |
|
||||
| Frame | `8 + 8 + 8 + 4 + capacity * 8` |
|
||||
82
docs/spec/stone.md
Normal file
82
docs/spec/stone.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: "Stone Memory"
|
||||
description: "Immutable arena allocation"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Stone memory is a separate allocation arena for immutable values. Objects in stone memory are permanent — they are never moved, never freed, and never touched by the garbage collector.
|
||||
|
||||
The `stone()` function in ƿit petrifies a value, deeply freezing it and all its descendants. Stoned objects have the S bit set in their object header.
|
||||
|
||||
## The Stone Arena
|
||||
|
||||
Stone memory uses bump allocation from a contiguous arena:
|
||||
|
||||
```
|
||||
stone_base ──────── stone_free ──────── stone_end
|
||||
[allocated objects] [free space ]
|
||||
```
|
||||
|
||||
Allocation advances `stone_free` forward. When the arena is exhausted, overflow pages are allocated via the system allocator and linked together:
|
||||
|
||||
```c
|
||||
struct StonePage {
|
||||
struct StonePage *next;
|
||||
size_t size;
|
||||
uint8_t data[];
|
||||
};
|
||||
```
|
||||
|
||||
## The S Bit
|
||||
|
||||
Bit 3 of the object header is the stone flag. When set:
|
||||
|
||||
- The object is **immutable** — writes disrupt
|
||||
- The object is **excluded from GC** — the collector skips it entirely
|
||||
- For text objects, the length field caches the **hash** instead of the character count (since the text cannot change, the hash is computed once and reused)
|
||||
|
||||
## What Gets Stoned
|
||||
|
||||
When `stone(value)` is called:
|
||||
|
||||
1. If the value is already stone, return immediately
|
||||
2. Recursively walk all nested values (array elements, record fields, etc.)
|
||||
3. Copy each mutable object into the stone arena
|
||||
4. Set the S bit on each copied object
|
||||
5. Return the stoned value
|
||||
|
||||
The operation is deep — an entire object graph becomes permanently immutable.
|
||||
|
||||
## Text Interning
|
||||
|
||||
The stone arena maintains a hash table for text interning. When a text value is stoned, it is looked up in the intern table. If an identical string already exists in stone memory, the existing one is reused. This deduplicates strings and makes equality comparison O(1) for stoned text.
|
||||
|
||||
The hash is computed with `fash64` over the packed UTF-32 words.
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Module Return Values
|
||||
|
||||
Every module's return value is automatically stoned:
|
||||
|
||||
```javascript
|
||||
// config.cm
|
||||
return {
|
||||
debug: true,
|
||||
timeout: 30
|
||||
}
|
||||
// The returned object is stone — shared safely between actors
|
||||
```
|
||||
|
||||
### Message Passing
|
||||
|
||||
Messages between actors are stoned before delivery, ensuring actors never share mutable state.
|
||||
|
||||
### Constants
|
||||
|
||||
Literal objects and arrays that can be determined at compile time may be allocated directly in stone memory.
|
||||
|
||||
## Relationship to GC
|
||||
|
||||
The Cheney copying collector only operates on the mutable heap. During collection, when the collector encounters a pointer to stone memory (S bit set), it skips it — stone objects are roots that never move. This means stone memory acts as a permanent root set with zero GC overhead.
|
||||
96
docs/spec/values.md
Normal file
96
docs/spec/values.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: "Value Representation"
|
||||
description: "JSValue tagging and encoding"
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Every value in ƿit is a 64-bit word called a JSValue. The runtime uses LSB (least significant bit) tagging to pack type information directly into the value, avoiding heap allocation for common types.
|
||||
|
||||
## Tag Encoding
|
||||
|
||||
The lowest bits of a JSValue determine its type:
|
||||
|
||||
| LSB Pattern | Type | Payload |
|
||||
|-------------|------|---------|
|
||||
| `xxxxxxx0` | Integer | 31-bit signed integer in upper bits |
|
||||
| `xxxxx001` | Pointer | 61-bit aligned heap pointer |
|
||||
| `xxxxx101` | Short float | 8-bit exponent + 52-bit mantissa |
|
||||
| `xxxxx011` | Special | 5-bit tag selects subtype |
|
||||
|
||||
### Integers
|
||||
|
||||
If the least significant bit is 0, the value is an immediate 31-bit signed integer. The integer is stored in the upper bits, extracted via `v >> 1`.
|
||||
|
||||
```
|
||||
[integer: 31 bits][0]
|
||||
```
|
||||
|
||||
Range: -1073741824 to 1073741823. Numbers outside this range are stored as short floats or heap-allocated.
|
||||
|
||||
### Pointers
|
||||
|
||||
If the lowest 3 bits are `001`, the value is a pointer to a heap object. The pointer is 8-byte aligned, so the low 3 bits are available for the tag. The actual address is extracted by clearing the low 3 bits.
|
||||
|
||||
```
|
||||
[pointer: 61 bits][001]
|
||||
```
|
||||
|
||||
All heap objects (arrays, records, blobs, text, functions, etc.) are referenced through pointer-tagged JSValues.
|
||||
|
||||
### Short Floats
|
||||
|
||||
If the lowest 3 bits are `101`, the value encodes a floating-point number directly. The format uses an 8-bit exponent (bias 127) and 52-bit mantissa, similar to IEEE 754 but with reduced range.
|
||||
|
||||
```
|
||||
[sign: 1][exponent: 8][mantissa: 52][101]
|
||||
```
|
||||
|
||||
Range: approximately ±3.4 * 10^38. Numbers outside this range fall back to null. Zero is always positive zero.
|
||||
|
||||
### Specials
|
||||
|
||||
If the lowest 2 bits are `11`, the next 3 bits select a special type:
|
||||
|
||||
| 5-bit Tag | Value |
|
||||
|-----------|-------|
|
||||
| `00011` | Boolean (true/false in upper bits) |
|
||||
| `00111` | Null |
|
||||
| `01111` | Exception marker |
|
||||
| `10111` | Uninitialized |
|
||||
| `11011` | Immediate string |
|
||||
| `11111` | Catch offset |
|
||||
|
||||
## Immediate Strings
|
||||
|
||||
Short ASCII strings (up to 7 characters) are packed directly into the JSValue without heap allocation:
|
||||
|
||||
```
|
||||
[char6][char5][char4][char3][char2][char1][char0][length: 3][11011]
|
||||
```
|
||||
|
||||
Each character occupies 8 bits. The length (0-7) is stored in bits 5-7. Only ASCII characters (0-127) qualify — any non-ASCII character forces heap allocation.
|
||||
|
||||
```javascript
|
||||
var s = "hello" // 5 chars, fits in immediate string
|
||||
var t = "" // immediate (length 0)
|
||||
var u = "longtext" // 8 chars, heap-allocated
|
||||
```
|
||||
|
||||
## Null
|
||||
|
||||
Null is encoded as a special-tagged value with tag `00111`. There is no `undefined` in ƿit — only null.
|
||||
|
||||
```javascript
|
||||
var x = null // special tag null
|
||||
var y = 1 / 0 // also null (division by zero)
|
||||
var z = {}.missing // null (missing field)
|
||||
```
|
||||
|
||||
## Boolean
|
||||
|
||||
True and false are encoded as specials with tag `00011`, distinguished by a bit in the upper payload.
|
||||
|
||||
## Summary
|
||||
|
||||
The tagging scheme ensures that the most common values — small integers, booleans, null, and short strings — require zero heap allocation. This significantly reduces GC pressure and improves cache locality.
|
||||
119
docs/wota.md
Normal file
119
docs/wota.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: "Wota Format"
|
||||
description: "Word Object Transfer Arrangement"
|
||||
weight: 86
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume.
|
||||
|
||||
Wota stands for Word Object Transfer Arrangement.
|
||||
|
||||
## Type Summary
|
||||
|
||||
| Byte | Type |
|
||||
|------|------|
|
||||
| `00` | Integer |
|
||||
| `01` | Floating Point |
|
||||
| `02` | Array |
|
||||
| `03` | Record |
|
||||
| `04` | Blob |
|
||||
| `05` | Text |
|
||||
| `07` | Symbol |
|
||||
|
||||
## Preambles
|
||||
|
||||
Every Wota value starts with a preamble word. The least significant byte contains the type. The remaining 56 bits contain type-specific data.
|
||||
|
||||
## Blob
|
||||
|
||||
A blob is a string of bits. The remaining field contains the number of bits. The number of words that follow: `floor((number_of_bits + 63) / 64)`. The first bit of the blob goes into the most significant bit of the first word. The final word is padded with 0.
|
||||
|
||||
Example: A blob containing 25 bits `111100001110001100100001`:
|
||||
|
||||
```
|
||||
0000000000001904 # preamble: 25 bits, type blob
|
||||
F0E3208000000000 # data (padded to 64 bits)
|
||||
```
|
||||
|
||||
## Text
|
||||
|
||||
The text is a string of UTF-32 characters packed 2 per word. The remaining field contains the number of characters. The number of words that follow: `floor((number_of_characters + 1) / 2)`. The final word is padded with 0.
|
||||
|
||||
Example: `"cat"`:
|
||||
|
||||
```
|
||||
0000000000000305 # preamble: 3 characters, type text
|
||||
0000006300000061 # 'c' and 'a'
|
||||
0000007400000000 # 't' and padding
|
||||
```
|
||||
|
||||
## Array
|
||||
|
||||
An array is an ordered sequence of values. The remaining field contains the number of elements. Following the preamble are the elements, each beginning with its own preamble. Nesting is encouraged. Cyclic structures are not allowed.
|
||||
|
||||
Example: `["duck", "dragon"]`:
|
||||
|
||||
```
|
||||
0000000000000202 # preamble: 2 elements, type array
|
||||
0000000000000405 # text "duck": 4 chars
|
||||
0000006400000074 # 'd' 't' (reversed pair order)
|
||||
000000630000006B # 'c' 'k'
|
||||
0000000000000605 # text "dragon": 6 chars
|
||||
0000006400000072 # 'd' 'r'
|
||||
0000006100000067 # 'a' 'g'
|
||||
0000006F0000006E # 'o' 'n'
|
||||
```
|
||||
|
||||
## Record
|
||||
|
||||
A record is a set of key/value pairs. Keys must be text. The remaining field contains the number of pairs.
|
||||
|
||||
Example: `{"ox": ["O", "X"]}`:
|
||||
|
||||
```
|
||||
0000000000000103 # preamble: 1 pair, type record
|
||||
0000000000000205 # key "ox": 2 chars
|
||||
0000006F00000078 # 'o' 'x'
|
||||
0000000000000202 # value: array of 2
|
||||
0000000000000105 # "O": 1 char
|
||||
0000004F00000000 # 'O'
|
||||
0000000000000105 # "X": 1 char
|
||||
0000005800000000 # 'X'
|
||||
```
|
||||
|
||||
## Number
|
||||
|
||||
Numbers are represented as DEC64. To arrange an integer, shift the integer up 8 bits. The number is incorporated directly into the preamble.
|
||||
|
||||
Example: `7`:
|
||||
|
||||
```
|
||||
0000000000000700 # integer 7 as DEC64
|
||||
```
|
||||
|
||||
To arrange a floating point number, place the number in the word following the floating point preamble.
|
||||
|
||||
Example: `4.25`:
|
||||
|
||||
```
|
||||
0000000000000001 # preamble: type floating point
|
||||
000000000001A9FE # DEC64 encoding of 4.25
|
||||
```
|
||||
|
||||
Care must be taken when decoding that the least significant byte of the number is not `80` (the null exponent).
|
||||
|
||||
## Symbol
|
||||
|
||||
The remaining field contains the symbol.
|
||||
|
||||
Example: `[null, false, true, private, system]`:
|
||||
|
||||
```
|
||||
0000000000000502 # array of 5
|
||||
0000000000000007 # null
|
||||
0000000000000207 # false
|
||||
0000000000000307 # true
|
||||
0000000000000807 # private
|
||||
0000000000000907 # system
|
||||
```
|
||||
@@ -1,21 +1,17 @@
|
||||
(function engine() {
|
||||
// Hidden vars (os, actorsym, init, core_path) come from env
|
||||
var ACTORDATA = actorsym
|
||||
var SYSYM = '__SYSTEM__'
|
||||
|
||||
var _cell = {}
|
||||
var need_stop = false
|
||||
|
||||
var dylib_ext
|
||||
|
||||
var cases = {
|
||||
Windows: '.dll',
|
||||
macOS: '.dylib',
|
||||
Linux: '.so'
|
||||
}
|
||||
|
||||
print(os.platform())
|
||||
|
||||
dylib_ext = cases[os.platform()]
|
||||
switch(os.platform()) {
|
||||
case 'Windows': dylib_ext = '.dll'; break;
|
||||
case 'macOS': dylib_ext = '.dylib'; break;
|
||||
case 'Linux': dylib_ext = '.so'; break;
|
||||
}
|
||||
|
||||
var MOD_EXT = '.cm'
|
||||
var ACTOR_EXT = '.ce'
|
||||
@@ -49,26 +45,20 @@ function ends_with(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
print("engine: loading js")
|
||||
var js = use_embed('js')
|
||||
print("engine: loading fd")
|
||||
var fd = use_embed('fd')
|
||||
print("engine: loaded fd")
|
||||
|
||||
print("engine: getting home")
|
||||
// Get the shop path from HOME environment
|
||||
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
|
||||
if (!home) {
|
||||
os.print('Could not determine home directory\n')
|
||||
os.exit(1)
|
||||
throw Error('Could not determine home directory')
|
||||
}
|
||||
var shop_path = home + '/.cell'
|
||||
var packages_path = shop_path + '/packages'
|
||||
var core_path = packages_path + '/core'
|
||||
|
||||
if (!fd.is_dir(core_path)) {
|
||||
os.print('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.\n')
|
||||
os.exit(1)
|
||||
throw Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
|
||||
}
|
||||
|
||||
var use_cache = {}
|
||||
@@ -80,49 +70,34 @@ function use_core(path) {
|
||||
if (use_cache[cache_key])
|
||||
return use_cache[cache_key];
|
||||
|
||||
print(`engine: use_core('${path}') - embed`)
|
||||
var sym = use_embed(replace(path, '/', '_'))
|
||||
|
||||
// Core scripts are in packages/core/
|
||||
var file_path = core_path + '/' + path + MOD_EXT
|
||||
|
||||
if (fd.is_file(file_path)) {
|
||||
print(`engine: use_core('${path}') - loading script`)
|
||||
var script_blob = fd.slurp(file_path)
|
||||
var script = text(script_blob)
|
||||
var mod = `(function setup_module(use){${script}})`
|
||||
var fn = mach_eval('core:' + path, mod)
|
||||
if (fn == null) {
|
||||
print(`engine: use_core('${path}') - mach_eval returned null!`)
|
||||
return null
|
||||
}
|
||||
print(`engine: use_core('${path}') - calling (fn type: ${typeof(fn)})`)
|
||||
var fn = js.eval('core:' + path, mod)
|
||||
var result = call(fn,sym, [use_core])
|
||||
print(`engine: use_core('${path}') - done`)
|
||||
use_cache[cache_key] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
print(`engine: use_core('${path}') - using embed directly`)
|
||||
use_cache[cache_key] = sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
print("engine: loading blob")
|
||||
var blob = use_core('blob')
|
||||
print("engine: loaded blob")
|
||||
|
||||
function actor() {
|
||||
|
||||
}
|
||||
|
||||
print("engine: loading actor")
|
||||
var actor_mod = use_core('actor')
|
||||
print("engine: loading wota")
|
||||
var wota = use_core('wota')
|
||||
print("engine: loading nota")
|
||||
var nota = use_core('nota')
|
||||
print("engine: loaded nota")
|
||||
|
||||
function is_actor(value) {
|
||||
return is_object(value) && value[ACTORDATA]
|
||||
@@ -158,27 +133,32 @@ function log(name, args) {
|
||||
var caller = caller_data(1)
|
||||
var msg = args[0]
|
||||
|
||||
if (name == 'console') {
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
} else if (name == 'error') {
|
||||
if (msg == null) msg = Error()
|
||||
if (is_proto(msg, Error))
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
} else if (name == 'system') {
|
||||
msg = "[SYSTEM] " + msg
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
} else {
|
||||
log.console(`unknown log type: ${name}`)
|
||||
switch(name) {
|
||||
case 'console':
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
case 'error':
|
||||
msg = msg ?? Error()
|
||||
if (is_proto(msg, Error))
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
case 'system':
|
||||
msg = "[SYSTEM] " + msg
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
default:
|
||||
log.console(`unknown log type: ${name}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function actor_die(err)
|
||||
function disrupt(err)
|
||||
{
|
||||
if (err && is_function(err.toString)) {
|
||||
os.print(err.toString())
|
||||
os.print("\n")
|
||||
if (err.stack) os.print(err.stack)
|
||||
if (is_function(err.toString)) {
|
||||
os.print(err.toString())
|
||||
os.print("\n")
|
||||
os.print(err.stack)
|
||||
}
|
||||
|
||||
if (overling) {
|
||||
@@ -205,20 +185,15 @@ function actor_die(err)
|
||||
log.console(err.stack)
|
||||
}
|
||||
|
||||
actor_mod["disrupt"]()
|
||||
actor_mod.disrupt()
|
||||
}
|
||||
|
||||
|
||||
|
||||
print("engine: setting up actor_die")
|
||||
actor_mod.on_exception(actor_die)
|
||||
actor_mod.on_exception(disrupt)
|
||||
|
||||
print("engine: setting cell args")
|
||||
print(`engine: init is ${init}`)
|
||||
_cell.args = init != null ? init : {}
|
||||
print("engine: args set")
|
||||
_cell.args = init ?? {}
|
||||
_cell.id = "newguy"
|
||||
print("engine: id set")
|
||||
|
||||
function create_actor(desc = {id:guid()}) {
|
||||
var actor = {}
|
||||
@@ -226,25 +201,19 @@ function create_actor(desc = {id:guid()}) {
|
||||
return actor
|
||||
}
|
||||
|
||||
print("engine: creating $_")
|
||||
var $_ = {}
|
||||
print("engine: creating self actor")
|
||||
$_.self = create_actor()
|
||||
print("engine: setting os props")
|
||||
|
||||
os.use_cache = use_cache
|
||||
os.global_shop_path = shop_path
|
||||
os.$_ = $_
|
||||
print("engine: os props set")
|
||||
|
||||
print("engine: loading shop")
|
||||
var shop = use_core('internal/shop')
|
||||
print("engine: loaded shop, loading json")
|
||||
|
||||
var json = use_core('json')
|
||||
var time = use_core('time')
|
||||
|
||||
print("engine: loading pronto")
|
||||
var pronto = use_core('pronto')
|
||||
print("engine: loaded pronto")
|
||||
var fallback = pronto.fallback
|
||||
var parallel = pronto.parallel
|
||||
var race = pronto.race
|
||||
@@ -272,15 +241,11 @@ os.runtime_env = runtime_env
|
||||
|
||||
$_.time_limit = function(requestor, seconds)
|
||||
{
|
||||
if (!pronto.is_requestor(requestor)) {
|
||||
log.error('time_limit: first argument must be a requestor')
|
||||
disrupt
|
||||
}
|
||||
if (!is_number(seconds) || seconds <= 0) {
|
||||
log.error('time_limit: seconds must be a positive number')
|
||||
disrupt
|
||||
}
|
||||
|
||||
if (!pronto.is_requestor(requestor))
|
||||
throw Error('time_limit: first argument must be a requestor');
|
||||
if (!is_number(seconds) || seconds <= 0)
|
||||
throw Error('time_limit: seconds must be a positive number');
|
||||
|
||||
return function time_limit_requestor(callback, value) {
|
||||
pronto.check_callback(callback, 'time_limit')
|
||||
var finished = false
|
||||
@@ -295,14 +260,7 @@ $_.time_limit = function(requestor, seconds)
|
||||
timer_cancel = null
|
||||
}
|
||||
if (requestor_cancel) {
|
||||
requestor_cancel(reason)
|
||||
requestor_cancel = null
|
||||
}
|
||||
}
|
||||
|
||||
function safe_cancel_requestor(reason) {
|
||||
if (requestor_cancel) {
|
||||
requestor_cancel(reason)
|
||||
try { pronto.requestor_cancel(reason) } catch (_) {}
|
||||
requestor_cancel = null
|
||||
}
|
||||
}
|
||||
@@ -310,12 +268,15 @@ $_.time_limit = function(requestor, seconds)
|
||||
timer_cancel = $_.delay(function() {
|
||||
if (finished) return
|
||||
def reason = make_reason(factory, 'Timeout.', seconds)
|
||||
safe_cancel_requestor(reason)
|
||||
if (requestor_cancel) {
|
||||
try { requestor_cancel(reason) } catch (_) {}
|
||||
requestor_cancel = null
|
||||
}
|
||||
finished = true
|
||||
callback(null, reason)
|
||||
}, seconds)
|
||||
|
||||
function do_request() {
|
||||
try {
|
||||
requestor_cancel = requestor(function(val, reason) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
@@ -325,14 +286,16 @@ $_.time_limit = function(requestor, seconds)
|
||||
}
|
||||
callback(val, reason)
|
||||
}, value)
|
||||
} disruption {
|
||||
cancel(Error('requestor failed'))
|
||||
callback(null, Error('requestor failed'))
|
||||
} catch (ex) {
|
||||
cancel(ex)
|
||||
callback(null, ex)
|
||||
}
|
||||
do_request()
|
||||
|
||||
return function(reason) {
|
||||
safe_cancel_requestor(reason)
|
||||
if (requestor_cancel) {
|
||||
try { requestor_cancel(reason) } catch (_) {}
|
||||
requestor_cancel = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,54 +408,52 @@ var portal_fn = null
|
||||
|
||||
// takes a function input value that will eventually be called with the current time in number form.
|
||||
$_.portal = function(fn, port) {
|
||||
if (portal) {
|
||||
log.error(`Already started a portal listening on ${portal.port}`)
|
||||
disrupt
|
||||
}
|
||||
if (!port) {
|
||||
log.error("Requires a valid port.")
|
||||
disrupt
|
||||
}
|
||||
if (portal) throw Error(`Already started a portal listening on ${portal.port}`)
|
||||
if (!port) throw Error("Requires a valid port.")
|
||||
log.system(`starting a portal on port ${port}`)
|
||||
portal = enet.create_host({address: "any", port})
|
||||
portal_fn = fn
|
||||
}
|
||||
|
||||
function handle_host(e) {
|
||||
if (e.type == "connect") {
|
||||
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
|
||||
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
|
||||
var queue = peer_queue.get(e.peer)
|
||||
if (queue) {
|
||||
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
|
||||
log.system(`sent ${msg} out of queue`)
|
||||
peer_queue.delete(e.peer)
|
||||
}
|
||||
} else if (e.type == "disconnect") {
|
||||
peer_queue.delete(e.peer)
|
||||
arrfor(array(peers), function(id, index) {
|
||||
if (peers[id] == e.peer) delete peers[id]
|
||||
})
|
||||
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
|
||||
} else if (e.type == "receive") {
|
||||
var data = nota.decode(e.data)
|
||||
if (data.replycc && !data.replycc.address) {
|
||||
data.replycc[ACTORDATA].address = e.peer.address
|
||||
data.replycc[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
function populate_actor_addresses(obj) {
|
||||
if (!is_object(obj)) return
|
||||
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
||||
obj[ACTORDATA].address = e.peer.address
|
||||
obj[ACTORDATA].port = e.peer.port
|
||||
switch (e.type) {
|
||||
case "connect":
|
||||
log.system(`connected a new peer: ${e.peer.address}:${e.peer.port}`)
|
||||
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
|
||||
var queue = peer_queue.get(e.peer)
|
||||
if (queue) {
|
||||
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
|
||||
log.system(`sent ${msg} out of queue`)
|
||||
peer_queue.delete(e.peer)
|
||||
}
|
||||
arrfor(array(obj), function(key, index) {
|
||||
if (key in obj)
|
||||
populate_actor_addresses(obj[key])
|
||||
break
|
||||
case "disconnect":
|
||||
peer_queue.delete(e.peer)
|
||||
arrfor(array(peers), function(id, index) {
|
||||
if (peers[id] == e.peer) delete peers[id]
|
||||
})
|
||||
}
|
||||
if (data.data) populate_actor_addresses(data.data)
|
||||
turn(data)
|
||||
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
|
||||
break
|
||||
case "receive":
|
||||
var data = nota.decode(e.data)
|
||||
if (data.replycc && !data.replycc.address) {
|
||||
data.replycc[ACTORDATA].address = e.peer.address
|
||||
data.replycc[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
function populate_actor_addresses(obj) {
|
||||
if (!is_object(obj)) return
|
||||
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
||||
obj[ACTORDATA].address = e.peer.address
|
||||
obj[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
arrfor(array(obj), function(key, index) {
|
||||
if (key in obj)
|
||||
populate_actor_addresses(obj[key])
|
||||
})
|
||||
}
|
||||
if (data.data) populate_actor_addresses(data.data)
|
||||
turn(data)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,14 +487,10 @@ $_.stop = function stop(actor) {
|
||||
need_stop = true
|
||||
return
|
||||
}
|
||||
if (!is_actor(actor)) {
|
||||
log.error('Can only call stop on an actor.')
|
||||
disrupt
|
||||
}
|
||||
if (is_null(underlings[actor[ACTORDATA].id])) {
|
||||
log.error('Can only call stop on an underling or self.')
|
||||
disrupt
|
||||
}
|
||||
if (!is_actor(actor))
|
||||
throw Error('Can only call stop on an actor.')
|
||||
if (is_null(underlings[actor[ACTORDATA].id]))
|
||||
throw Error('Can only call stop on an underling or self.')
|
||||
|
||||
sys_msg(actor, {kind:"stop"})
|
||||
}
|
||||
@@ -570,22 +527,20 @@ function actor_prep(actor, send) {
|
||||
|
||||
// Send a message immediately without queuing
|
||||
function actor_send_immediate(actor, send) {
|
||||
actor_send(actor, send)
|
||||
try {
|
||||
actor_send(actor, send);
|
||||
} catch (err) {
|
||||
log.error("Failed to send immediate message:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function actor_send(actor, message) {
|
||||
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
|
||||
return
|
||||
|
||||
if (!is_actor(actor) && !is_actor(actor.replycc)) {
|
||||
log.error(`Must send to an actor object. Attempted send to ${actor}`)
|
||||
disrupt
|
||||
}
|
||||
|
||||
if (!is_object(message)) {
|
||||
log.error('Must send an object record.')
|
||||
disrupt
|
||||
}
|
||||
if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`)
|
||||
|
||||
if (!is_object(message)) throw Error('Must send an object record.')
|
||||
|
||||
// message to self
|
||||
if (actor[ACTORDATA].id == _cell.id) {
|
||||
@@ -628,10 +583,12 @@ function actor_send(actor, message) {
|
||||
// Holds all messages queued during the current turn.
|
||||
var message_queue = []
|
||||
|
||||
function send_messages() {
|
||||
var need_stop = false
|
||||
|
||||
function send_messages() {
|
||||
// if we've been flagged to stop, bail out before doing anything
|
||||
if (need_stop) {
|
||||
actor_die()
|
||||
disrupt()
|
||||
message_queue = []
|
||||
return
|
||||
}
|
||||
@@ -651,26 +608,19 @@ function send_messages() {
|
||||
var replies = {}
|
||||
|
||||
function send(actor, message, reply) {
|
||||
if (!is_object(actor)) {
|
||||
log.error(`Must send to an actor object. Provided: ${actor}`)
|
||||
disrupt
|
||||
}
|
||||
if (!is_object(actor))
|
||||
throw Error(`Must send to an actor object. Provided: ${actor}`);
|
||||
|
||||
if (!is_object(message)) {
|
||||
log.error('Message must be an object')
|
||||
disrupt
|
||||
}
|
||||
if (!is_object(message))
|
||||
throw Error('Message must be an object')
|
||||
var send_msg = {type:"user", data: message}
|
||||
var target = actor
|
||||
|
||||
if (actor[HEADER] && actor[HEADER].replycc) {
|
||||
var header = actor[HEADER]
|
||||
if (!header.replycc || !is_actor(header.replycc)) {
|
||||
log.error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
|
||||
disrupt
|
||||
}
|
||||
if (!header.replycc || !is_actor(header.replycc))
|
||||
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
|
||||
|
||||
target = header.replycc
|
||||
actor = header.replycc
|
||||
send_msg.return = header.reply
|
||||
}
|
||||
|
||||
@@ -688,7 +638,7 @@ function send(actor, message, reply) {
|
||||
}
|
||||
|
||||
// Instead of sending immediately, queue it
|
||||
actor_prep(target, send_msg);
|
||||
actor_prep(actor, send_msg);
|
||||
}
|
||||
|
||||
stone(send)
|
||||
@@ -719,7 +669,7 @@ overling = _cell.args.overling
|
||||
$_.overling = overling
|
||||
|
||||
root = _cell.args.root
|
||||
if (root == null) root = $_.self
|
||||
root ??= $_.self
|
||||
|
||||
if (overling) {
|
||||
$_.couple(overling) // auto couple to overling
|
||||
@@ -755,35 +705,36 @@ function handle_actor_disconnect(id) {
|
||||
delete greeters[id]
|
||||
}
|
||||
log.system(`actor ${id} disconnected`)
|
||||
if (!is_null(couplings[id])) actor_die("coupled actor died") // couplings now disrupts instead of stop
|
||||
if (!is_null(couplings[id])) disrupt("coupled actor died") // couplings now disrupts instead of stop
|
||||
}
|
||||
|
||||
function handle_sysym(msg)
|
||||
{
|
||||
var from
|
||||
if (msg.kind == 'stop') {
|
||||
actor_die("got stop message")
|
||||
} else if (msg.kind == 'underling') {
|
||||
from = msg.from
|
||||
var greeter = greeters[from[ACTORDATA].id]
|
||||
if (greeter) greeter(msg.message)
|
||||
if (msg.message.type == 'disrupt')
|
||||
delete underlings[from[ACTORDATA].id]
|
||||
} else if (msg.kind == 'contact') {
|
||||
if (portal_fn) {
|
||||
var letter2 = msg.data
|
||||
letter2[HEADER] = msg
|
||||
delete msg.data
|
||||
portal_fn(letter2)
|
||||
} else {
|
||||
log.error('Got a contact message, but no portal is established.')
|
||||
disrupt
|
||||
}
|
||||
} else if (msg.kind == 'couple') {
|
||||
// from must be notified when we die
|
||||
from = msg.from
|
||||
underlings[from[ACTORDATA].id] = true
|
||||
log.system(`actor ${from} is coupled to me`)
|
||||
switch(msg.kind) {
|
||||
case 'stop':
|
||||
disrupt("got stop message")
|
||||
break
|
||||
case 'underling':
|
||||
from = msg.from
|
||||
var greeter = greeters[from[ACTORDATA].id]
|
||||
if (greeter) greeter(msg.message)
|
||||
if (msg.message.type == 'disrupt')
|
||||
delete underlings[from[ACTORDATA].id]
|
||||
break
|
||||
case 'contact':
|
||||
if (portal_fn) {
|
||||
var letter2 = msg.data
|
||||
letter2[HEADER] = msg
|
||||
delete msg.data
|
||||
portal_fn(letter2)
|
||||
} else throw Error('Got a contact message, but no portal is established.')
|
||||
break
|
||||
case 'couple': // from must be notified when we die
|
||||
from = msg.from
|
||||
underlings[from[ACTORDATA].id] = true
|
||||
log.system(`actor ${from} is coupled to me`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,27 +744,30 @@ function handle_message(msg) {
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.type == "user") {
|
||||
var letter = msg.data // what the sender really sent
|
||||
_ObjectDefineProperty(letter, HEADER, {
|
||||
value: msg, enumerable: false
|
||||
})
|
||||
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
|
||||
value: { reply: msg.reply }, enumerable: false
|
||||
})
|
||||
|
||||
if (msg.return) {
|
||||
var fn = replies[msg.return]
|
||||
if (fn) fn(letter)
|
||||
delete replies[msg.return]
|
||||
switch (msg.type) {
|
||||
case "user":
|
||||
var letter = msg.data // what the sender really sent
|
||||
_ObjectDefineProperty(letter, HEADER, {
|
||||
value: msg, enumerable: false
|
||||
})
|
||||
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
|
||||
value: { reply: msg.reply }, enumerable: false
|
||||
})
|
||||
|
||||
if (msg.return) {
|
||||
var fn = replies[msg.return]
|
||||
if (fn) fn(letter)
|
||||
delete replies[msg.return]
|
||||
return
|
||||
}
|
||||
|
||||
if (receive_fn) receive_fn(letter)
|
||||
return
|
||||
}
|
||||
|
||||
if (receive_fn) receive_fn(letter)
|
||||
} else if (msg.type == "stopped") {
|
||||
handle_actor_disconnect(msg.id)
|
||||
case "stopped":
|
||||
handle_actor_disconnect(msg.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function enet_check()
|
||||
{
|
||||
@@ -838,10 +792,8 @@ if (!locator) {
|
||||
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg)
|
||||
}
|
||||
|
||||
if (!locator) {
|
||||
os.print(`Main program ${_cell.args.program} could not be found\n`)
|
||||
os.exit(1)
|
||||
}
|
||||
if (!locator)
|
||||
throw Error(`Main program ${_cell.args.program} could not be found`)
|
||||
|
||||
$_.clock(_ => {
|
||||
// Get capabilities for the main program
|
||||
@@ -866,6 +818,7 @@ $_.clock(_ => {
|
||||
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
|
||||
|
||||
if (val)
|
||||
log.error('Program must not return anything')
|
||||
disrupt
|
||||
throw Error('Program must not return anything');
|
||||
})
|
||||
|
||||
})()
|
||||
@@ -75,11 +75,7 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
|
||||
|
||||
JSValue js_kim_use(JSContext *js)
|
||||
{
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_kim_funcs, countof(js_kim_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
|
||||
return mod;
|
||||
}
|
||||
@@ -577,13 +577,9 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JS_NewClassID(&js_dylib_class_id);
|
||||
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
|
||||
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
JS_NewClass(JS_GetRuntime(js), js_dylib_class_id, &js_dylib_class);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
|
||||
return mod;
|
||||
}
|
||||
|
||||
@@ -172,11 +172,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
|
||||
return mod;
|
||||
}
|
||||
|
||||
@@ -172,11 +172,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
|
||||
return mod;
|
||||
}
|
||||
|
||||
116
internal/shop.cm
116
internal/shop.cm
@@ -1,6 +1,5 @@
|
||||
print("shop: starting")
|
||||
var toml = use('toml')
|
||||
print("shop: loaded toml")
|
||||
|
||||
var json = use('json')
|
||||
var fd = use('fd')
|
||||
var http = use('http')
|
||||
@@ -143,10 +142,8 @@ function package_in_shop(package) {
|
||||
|
||||
function abs_path_to_package(package_dir)
|
||||
{
|
||||
if (!fd.is_file(package_dir + '/cell.toml')) {
|
||||
log.error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
disrupt
|
||||
}
|
||||
if (!fd.is_file(package_dir + '/cell.toml'))
|
||||
throw Error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
|
||||
var packages_prefix = get_packages_dir() + '/'
|
||||
var core_dir = packages_prefix + core_package
|
||||
@@ -178,15 +175,14 @@ function abs_path_to_package(package_dir)
|
||||
return package_dir
|
||||
|
||||
// For local directories (e.g., linked targets), read the package name from cell.toml
|
||||
var toml_pkg = null
|
||||
var read_toml = function() {
|
||||
try {
|
||||
var content = text(fd.slurp(package_dir + '/cell.toml'))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.package)
|
||||
toml_pkg = cfg.package
|
||||
} disruption {}
|
||||
read_toml()
|
||||
if (toml_pkg) return toml_pkg
|
||||
return cfg.package
|
||||
} catch (e) {
|
||||
// Fall through
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -303,14 +299,12 @@ Shop.resolve_package_info = function(pkg) {
|
||||
|
||||
// Verify if a package name is valid and return status
|
||||
Shop.verify_package_name = function(pkg) {
|
||||
if (!pkg) { log.error("Empty package name"); disrupt }
|
||||
if (pkg == 'local') { log.error("local is not a valid package name"); disrupt }
|
||||
if (pkg == 'core') { log.error("core is not a valid package name"); disrupt }
|
||||
|
||||
if (search(pkg, '://') != null) {
|
||||
log.error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
|
||||
disrupt
|
||||
}
|
||||
if (!pkg) throw Error("Empty package name")
|
||||
if (pkg == 'local') throw Error("local is not a valid package name")
|
||||
if (pkg == 'core') throw Error("core is not a valid package name")
|
||||
|
||||
if (search(pkg, '://') != null)
|
||||
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
|
||||
}
|
||||
|
||||
// Convert module package to download URL
|
||||
@@ -447,7 +441,7 @@ ${script}
|
||||
// Resolve module function, hashing it in the process
|
||||
// path is the exact path to the script file
|
||||
function resolve_mod_fn(path, pkg) {
|
||||
if (!fd.is_file(path)) { log.error(`path ${path} is not a file`); disrupt }
|
||||
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`)
|
||||
|
||||
var file_info = Shop.file_info(path)
|
||||
var file_pkg = file_info.package
|
||||
@@ -582,38 +576,32 @@ Shop.open_package_dylib = function(pkg) {
|
||||
|
||||
var toml_path = pkg_dir + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
var read_toml_disrupted = false
|
||||
var do_read_toml = function() {
|
||||
try {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
var open_dep = function() {
|
||||
try {
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
} disruption {}
|
||||
open_dep()
|
||||
} catch (dep_e) {
|
||||
// Dependency dylib load failed, continue with others
|
||||
}
|
||||
})
|
||||
}
|
||||
} disruption {
|
||||
read_toml_disrupted = true
|
||||
} catch (e) {
|
||||
// Error reading toml, continue
|
||||
}
|
||||
do_read_toml()
|
||||
}
|
||||
|
||||
var dl_path = get_lib_path(pkg)
|
||||
if (fd.is_file(dl_path)) {
|
||||
if (!open_dls[dl_path]) {
|
||||
var open_disrupted = false
|
||||
var do_open = function() {
|
||||
try {
|
||||
open_dls[dl_path] = os.dylib_open(dl_path)
|
||||
} disruption {
|
||||
open_disrupted = true
|
||||
}
|
||||
do_open()
|
||||
if (open_disrupted) {
|
||||
} catch (e) {
|
||||
dylib_visited[pkg] = false
|
||||
disrupt
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -848,14 +836,14 @@ function execute_module(info)
|
||||
// C only
|
||||
used = call_c_module(c_resolve)
|
||||
} else {
|
||||
log.error(`Module ${info.path} could not be found`); disrupt
|
||||
throw Error(`Module ${info.path} could not be found`)
|
||||
}
|
||||
|
||||
// if (is_function(used))
|
||||
// throw Error('C module loader returned a function; did you forget to call it?')
|
||||
|
||||
if (!used)
|
||||
log.error(`Module ${info} returned null`); disrupt
|
||||
throw Error(`Module ${info} returned null`)
|
||||
|
||||
// stone(used)
|
||||
return used
|
||||
@@ -864,20 +852,16 @@ function execute_module(info)
|
||||
function get_module(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
|
||||
if (!info) {
|
||||
log.error(`Module ${path} could not be found in ${package_context}`)
|
||||
disrupt
|
||||
}
|
||||
if (!info)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
|
||||
return execute_module(info)
|
||||
}
|
||||
|
||||
Shop.use = function use(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
if (!info) {
|
||||
log.error(`Module ${path} could not be found in ${package_context}`)
|
||||
disrupt
|
||||
}
|
||||
if (!info)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
|
||||
if (use_cache[info.cache_key])
|
||||
return use_cache[info.cache_key]
|
||||
@@ -905,20 +889,13 @@ function fetch_remote_hash(pkg) {
|
||||
if (!api_url) return null
|
||||
|
||||
|
||||
var result = null
|
||||
var fetch_disrupted = false
|
||||
var do_fetch = function() {
|
||||
try {
|
||||
var resp = http.fetch(api_url)
|
||||
result = Shop.extract_commit_hash(pkg, text(resp))
|
||||
} disruption {
|
||||
fetch_disrupted = true
|
||||
}
|
||||
do_fetch()
|
||||
if (fetch_disrupted) {
|
||||
return Shop.extract_commit_hash(pkg, text(resp))
|
||||
} catch (e) {
|
||||
log.console("Warning: Could not check for updates for " + pkg)
|
||||
return null
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Download a zip for a package at a specific commit and cache it
|
||||
@@ -932,20 +909,14 @@ function download_zip(pkg, commit_hash) {
|
||||
return null
|
||||
}
|
||||
|
||||
var zip_blob = null
|
||||
var dl_disrupted = false
|
||||
var do_download = function() {
|
||||
zip_blob = http.fetch(download_url)
|
||||
try {
|
||||
var zip_blob = http.fetch(download_url)
|
||||
fd.slurpwrite(cache_path, zip_blob)
|
||||
} disruption {
|
||||
dl_disrupted = true
|
||||
}
|
||||
do_download()
|
||||
if (dl_disrupted) {
|
||||
log.error("Download failed for " + pkg)
|
||||
return zip_blob
|
||||
} catch (e) {
|
||||
log.error("Download failed for " + pkg + ": " + e)
|
||||
return null
|
||||
}
|
||||
return zip_blob
|
||||
}
|
||||
|
||||
// Get zip from cache, returns null if not cached
|
||||
@@ -1056,7 +1027,8 @@ Shop.extract = function(pkg) {
|
||||
|
||||
var zip_blob = get_package_zip(pkg)
|
||||
|
||||
if (!zip_blob) { log.error("No zip blob available for " + pkg); disrupt }
|
||||
if (!zip_blob)
|
||||
throw Error("No zip blob available for " + pkg)
|
||||
|
||||
// Extract zip for remote package
|
||||
install_zip(zip_blob, target_dir)
|
||||
@@ -1141,7 +1113,7 @@ Shop.update = function(pkg) {
|
||||
|
||||
function install_zip(zip_blob, target_dir) {
|
||||
var zip = miniz.read(zip_blob)
|
||||
if (!zip) { log.error("Failed to read zip archive"); disrupt }
|
||||
if (!zip) throw Error("Failed to read zip archive")
|
||||
|
||||
if (fd.is_link(target_dir)) fd.unlink(target_dir)
|
||||
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
|
||||
@@ -1193,14 +1165,14 @@ Shop.get = function(pkg) {
|
||||
if (!lock[pkg]) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
if (!info) {
|
||||
log.error("Invalid package: " + pkg); disrupt
|
||||
throw Error("Invalid package: " + pkg)
|
||||
}
|
||||
|
||||
var commit = null
|
||||
if (info != 'local') {
|
||||
commit = fetch_remote_hash(pkg)
|
||||
if (!commit) {
|
||||
log.error("Could not resolve commit for " + pkg); disrupt
|
||||
throw Error("Could not resolve commit for " + pkg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,12 @@ function is_valid_package(dir) {
|
||||
// Get current package name from cell.toml or null
|
||||
function get_current_package_name() {
|
||||
if (!is_valid_package('.')) return null
|
||||
var pkg_name = 'local'
|
||||
var do_load = function() {
|
||||
try {
|
||||
var config = pkg.load_config(null)
|
||||
if (config.package) pkg_name = config.package
|
||||
} disruption {}
|
||||
do_load()
|
||||
return pkg_name
|
||||
return config.package || 'local'
|
||||
} catch (e) {
|
||||
return 'local'
|
||||
}
|
||||
}
|
||||
|
||||
// Get the directory for a package
|
||||
|
||||
81
link.cm
81
link.cm
@@ -65,18 +65,13 @@ Link.load = function() {
|
||||
link_cache = {}
|
||||
return link_cache
|
||||
}
|
||||
|
||||
var load_disrupted = false
|
||||
var do_load = function() {
|
||||
|
||||
try {
|
||||
var content = text(fd.slurp(path))
|
||||
var cfg = toml.decode(content)
|
||||
link_cache = cfg.links || {}
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console("Warning: Failed to load link.toml")
|
||||
} catch (e) {
|
||||
log.console("Warning: Failed to load link.toml: " + e)
|
||||
link_cache = {}
|
||||
}
|
||||
return link_cache
|
||||
@@ -95,16 +90,14 @@ Link.add = function(canonical, target, shop) {
|
||||
// Validate canonical package exists in shop
|
||||
var lock = shop.load_lock()
|
||||
if (!lock[canonical]) {
|
||||
log.error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
disrupt
|
||||
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
}
|
||||
|
||||
// Validate target is a valid package
|
||||
if (starts_with(target, '/')) {
|
||||
// Local path - must have cell.toml
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
log.error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
disrupt
|
||||
throw Error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
}
|
||||
} else {
|
||||
// Remote package target - ensure it's installed
|
||||
@@ -123,8 +116,7 @@ Link.add = function(canonical, target, shop) {
|
||||
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
|
||||
var toml_path = target_path + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
var read_deps_disrupted = false
|
||||
var do_read_deps = function() {
|
||||
try {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
@@ -136,24 +128,16 @@ Link.add = function(canonical, target, shop) {
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
var install_disrupted = false
|
||||
var do_install = function() {
|
||||
try {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} disruption {
|
||||
install_disrupted = true
|
||||
}
|
||||
do_install()
|
||||
if (install_disrupted) {
|
||||
log.console(` Warning: Could not install dependency ${dep_locator}`)
|
||||
} catch (e) {
|
||||
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`)
|
||||
log.error(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
} disruption {
|
||||
read_deps_disrupted = true
|
||||
}
|
||||
do_read_deps()
|
||||
if (read_deps_disrupted) {
|
||||
} catch (e) {
|
||||
log.console(` Warning: Could not read dependencies from ${toml_path}`)
|
||||
}
|
||||
}
|
||||
@@ -165,14 +149,14 @@ Link.add = function(canonical, target, shop) {
|
||||
Link.remove = function(canonical) {
|
||||
var links = Link.load()
|
||||
if (!links[canonical]) return false
|
||||
|
||||
|
||||
// Remove the symlink if it exists
|
||||
var target_dir = get_package_abs_dir(canonical)
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
log.console("Removed symlink at " + target_dir)
|
||||
}
|
||||
|
||||
|
||||
delete links[canonical]
|
||||
Link.save(links)
|
||||
log.console("Unlinked " + canonical)
|
||||
@@ -188,7 +172,7 @@ Link.clear = function() {
|
||||
fd.unlink(target_dir)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Link.save({})
|
||||
log.console("Cleared all links")
|
||||
return true
|
||||
@@ -202,25 +186,25 @@ Link.sync_one = function(canonical, target, shop) {
|
||||
// Ensure parent directories exist
|
||||
var parent = fd.dirname(target_dir)
|
||||
ensure_dir(parent)
|
||||
|
||||
|
||||
// Check current state
|
||||
var current_link = null
|
||||
if (fd.is_link(target_dir)) {
|
||||
current_link = fd.readlink(target_dir)
|
||||
}
|
||||
|
||||
|
||||
// If already correctly linked, nothing to do
|
||||
if (current_link == link_target) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Remove existing file/dir/link
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
} else if (fd.is_dir(target_dir)) {
|
||||
fd.rmdir(target_dir, 1)
|
||||
}
|
||||
|
||||
|
||||
// Create symlink
|
||||
fd.symlink(link_target, target_dir)
|
||||
return true
|
||||
@@ -234,9 +218,7 @@ Link.sync_all = function(shop) {
|
||||
|
||||
arrfor(array(links), function(canonical) {
|
||||
var target = links[canonical]
|
||||
var sync_disrupted = false
|
||||
var sync_error_msg = ""
|
||||
var do_sync = function() {
|
||||
try {
|
||||
// Validate target exists
|
||||
var link_target = resolve_link_target(target)
|
||||
if (!fd.is_dir(link_target)) {
|
||||
@@ -252,8 +234,7 @@ Link.sync_all = function(shop) {
|
||||
|
||||
// Install dependencies of the linked package
|
||||
var toml_path = link_target + '/cell.toml'
|
||||
var read_deps_disrupted = false
|
||||
var do_read_deps = function() {
|
||||
try {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
@@ -264,25 +245,21 @@ Link.sync_all = function(shop) {
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
var install_dep = function() {
|
||||
try {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} disruption {}
|
||||
install_dep()
|
||||
} catch (e) {
|
||||
// Silently continue - dependency may already be installed
|
||||
}
|
||||
})
|
||||
}
|
||||
} disruption {
|
||||
read_deps_disrupted = true
|
||||
} catch (e) {
|
||||
// Could not read dependencies - continue anyway
|
||||
}
|
||||
do_read_deps()
|
||||
|
||||
count++
|
||||
} disruption {
|
||||
sync_disrupted = true
|
||||
}
|
||||
do_sync()
|
||||
if (sync_disrupted) {
|
||||
push(errors, canonical + ': sync failed')
|
||||
} catch (e) {
|
||||
push(errors, canonical + ': ' + e.message)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -571,13 +571,13 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
|
||||
JSValue js_enet_use(JSContext *ctx)
|
||||
{
|
||||
JS_NewClassID(&enet_host_id);
|
||||
JS_NewClass(ctx, enet_host_id, &enet_host);
|
||||
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
|
||||
JSValue host_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
|
||||
JS_SetClassProto(ctx, enet_host_id, host_proto);
|
||||
|
||||
JS_NewClassID(&enet_peer_class_id);
|
||||
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
|
||||
JS_NewClass(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
|
||||
JSValue peer_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
|
||||
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
|
||||
|
||||
16
package.cm
16
package.cm
@@ -51,8 +51,7 @@ package.load_config = function(name)
|
||||
return config_cache[config_path]
|
||||
|
||||
if (!fd.is_file(config_path)) {
|
||||
log.error(`${config_path} does not exist`)
|
||||
disrupt
|
||||
throw Error(`${config_path} does not exist`)
|
||||
}
|
||||
|
||||
var content = text(fd.slurp(config_path))
|
||||
@@ -159,20 +158,19 @@ package.split_alias = function(name, path)
|
||||
var parts = array(path, '/')
|
||||
var first_part = parts[0]
|
||||
|
||||
var split_result = null
|
||||
var do_split = function() {
|
||||
try {
|
||||
var config = package.load_config(name)
|
||||
if (!config) return
|
||||
if (!config) return null
|
||||
|
||||
var deps = config.dependencies
|
||||
if (deps && deps[first_part]) {
|
||||
var dep_locator = deps[first_part]
|
||||
var remaining_path = text(array(parts, 1), '/')
|
||||
split_result = { package: dep_locator, path: remaining_path }
|
||||
return { package: dep_locator, path: remaining_path }
|
||||
}
|
||||
} disruption {}
|
||||
do_split()
|
||||
if (split_result) return split_result
|
||||
} catch (e) {
|
||||
// Config doesn't exist or couldn't be loaded
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
95
pronto.cm
95
pronto.cm
@@ -3,8 +3,6 @@
|
||||
// Based on Douglas Crockford's parseq, adapted for Cell.
|
||||
// Time is in seconds.
|
||||
|
||||
function safe_call(fn, arg) { fn(arg) } disruption {}
|
||||
|
||||
function make_reason(factory, excuse, evidence) {
|
||||
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||
reason.evidence = evidence
|
||||
@@ -17,12 +15,12 @@ function is_requestor(fn) {
|
||||
|
||||
function check_requestors(list, factory) {
|
||||
if (!is_array(list) || some(list, r => !is_requestor(r)))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Bad requestor array.', list)
|
||||
}
|
||||
|
||||
function check_callback(cb, factory) {
|
||||
if (!is_function(cb) || length(cb) != 2)
|
||||
disrupt
|
||||
throw make_reason(factory, 'Not a callback.', cb)
|
||||
}
|
||||
|
||||
// fallback(requestor_array)
|
||||
@@ -30,7 +28,7 @@ function check_callback(cb, factory) {
|
||||
function fallback(requestor_array) {
|
||||
def factory = 'fallback'
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
disrupt
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
return function fallback_requestor(callback, value) {
|
||||
@@ -42,7 +40,7 @@ function fallback(requestor_array) {
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
if (current_cancel) {
|
||||
safe_call(current_cancel, reason)
|
||||
try { current_cancel(reason) } catch (_) {}
|
||||
current_cancel = null
|
||||
}
|
||||
}
|
||||
@@ -57,8 +55,7 @@ function fallback(requestor_array) {
|
||||
def requestor = requestor_array[index]
|
||||
index += 1
|
||||
|
||||
var requestor_disrupted = false
|
||||
var start_requestor = function() {
|
||||
try {
|
||||
current_cancel = requestor(function(val, reason) {
|
||||
if (cancelled) return
|
||||
current_cancel = null
|
||||
@@ -68,11 +65,7 @@ function fallback(requestor_array) {
|
||||
try_next()
|
||||
}
|
||||
}, value)
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
start_requestor()
|
||||
if (requestor_disrupted) {
|
||||
} catch (ex) {
|
||||
try_next()
|
||||
}
|
||||
}
|
||||
@@ -87,7 +80,7 @@ function fallback(requestor_array) {
|
||||
function parallel(requestor_array, throttle, need) {
|
||||
def factory = 'parallel'
|
||||
if (!is_array(requestor_array))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = length(requestor_array)
|
||||
@@ -96,10 +89,10 @@ function parallel(requestor_array, throttle, need) {
|
||||
|
||||
if (need == null) need = length
|
||||
if (!is_number(need) || need < 0 || need > length)
|
||||
disrupt
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
|
||||
return function parallel_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
@@ -114,8 +107,7 @@ function parallel(requestor_array, throttle, need) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
arrfor(cancel_list, c => {
|
||||
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
|
||||
do_cancel()
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,9 +117,7 @@ function parallel(requestor_array, throttle, need) {
|
||||
next_index += 1
|
||||
def requestor = requestor_array[idx]
|
||||
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
try {
|
||||
cancel_list[idx] = requestor(function(val, reason) {
|
||||
if (finished) return
|
||||
cancel_list[idx] = null
|
||||
@@ -152,15 +142,11 @@ function parallel(requestor_array, throttle, need) {
|
||||
|
||||
start_one()
|
||||
}, value)
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
} catch (ex) {
|
||||
failures += 1
|
||||
if (failures > length - need) {
|
||||
cancel(requestor_ex)
|
||||
callback(null, requestor_ex)
|
||||
cancel(ex)
|
||||
callback(null, ex)
|
||||
return
|
||||
}
|
||||
start_one()
|
||||
@@ -180,16 +166,16 @@ function parallel(requestor_array, throttle, need) {
|
||||
function race(requestor_array, throttle, need) {
|
||||
def factory = 'race'
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
disrupt
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = length(requestor_array)
|
||||
if (need == null) need = 1
|
||||
if (!is_number(need) || need < 1 || need > length)
|
||||
disrupt
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
|
||||
return function race_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
@@ -204,8 +190,7 @@ function race(requestor_array, throttle, need) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
arrfor(cancel_list, c => {
|
||||
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
|
||||
do_cancel()
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,9 +200,7 @@ function race(requestor_array, throttle, need) {
|
||||
next_index += 1
|
||||
def requestor = requestor_array[idx]
|
||||
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
try {
|
||||
cancel_list[idx] = requestor(function(val, reason) {
|
||||
if (finished) return
|
||||
cancel_list[idx] = null
|
||||
@@ -245,15 +228,11 @@ function race(requestor_array, throttle, need) {
|
||||
|
||||
start_one()
|
||||
}, value)
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
} catch (ex) {
|
||||
failures += 1
|
||||
if (failures > length - need) {
|
||||
cancel(requestor_ex)
|
||||
callback(null, requestor_ex)
|
||||
cancel(ex)
|
||||
callback(null, ex)
|
||||
return
|
||||
}
|
||||
start_one()
|
||||
@@ -272,7 +251,7 @@ function race(requestor_array, throttle, need) {
|
||||
function sequence(requestor_array) {
|
||||
def factory = 'sequence'
|
||||
if (!is_array(requestor_array))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
if (length(requestor_array) == 0)
|
||||
@@ -287,7 +266,7 @@ function sequence(requestor_array) {
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
if (current_cancel) {
|
||||
safe_call(current_cancel, reason)
|
||||
try { current_cancel(reason) } catch (_) {}
|
||||
current_cancel = null
|
||||
}
|
||||
}
|
||||
@@ -302,9 +281,7 @@ function sequence(requestor_array) {
|
||||
def requestor = requestor_array[index]
|
||||
index += 1
|
||||
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
try {
|
||||
current_cancel = requestor(function(result, reason) {
|
||||
if (cancelled) return
|
||||
current_cancel = null
|
||||
@@ -314,12 +291,8 @@ function sequence(requestor_array) {
|
||||
run_next(result)
|
||||
}
|
||||
}, val)
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
callback(null, requestor_ex)
|
||||
} catch (ex) {
|
||||
callback(null, ex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,21 +306,15 @@ function sequence(requestor_array) {
|
||||
function requestorize(unary) {
|
||||
def factory = 'requestorize'
|
||||
if (!is_function(unary))
|
||||
disrupt
|
||||
throw make_reason(factory, 'Not a function.', unary)
|
||||
|
||||
return function requestorized(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
var call_disrupted = false
|
||||
var call_ex = null
|
||||
var do_call = function() {
|
||||
try {
|
||||
def result = unary(value)
|
||||
callback(result == null ? true : result)
|
||||
} disruption {
|
||||
call_disrupted = true
|
||||
}
|
||||
do_call()
|
||||
if (call_disrupted) {
|
||||
callback(null, call_ex)
|
||||
} catch (ex) {
|
||||
callback(null, ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
qop.c
4
qop.c
@@ -458,13 +458,13 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||
|
||||
JSValue js_qop_use(JSContext *js) {
|
||||
JS_NewClassID(&js_qop_archive_class_id);
|
||||
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
|
||||
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
|
||||
JSValue archive_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
|
||||
|
||||
JS_NewClassID(&js_qop_writer_class_id);
|
||||
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
|
||||
JS_NewClass(JS_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
|
||||
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);
|
||||
|
||||
210
source/cell.c
210
source/cell.c
@@ -26,7 +26,6 @@ static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *core_path = NULL;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
|
||||
// Get the home directory
|
||||
static const char* get_home_dir(void) {
|
||||
@@ -106,44 +105,33 @@ static char* load_core_file(const char *filename, size_t *out_size) {
|
||||
return data;
|
||||
}
|
||||
|
||||
static int print_tree_errors(cJSON *root) {
|
||||
static int print_json_errors(const char *json) {
|
||||
if (!json) return 0;
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
if (!root) return 0;
|
||||
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
|
||||
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0)
|
||||
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) {
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
const char *filename = "<unknown>";
|
||||
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
|
||||
if (cJSON_IsString(fname))
|
||||
filename = fname->valuestring;
|
||||
int prev_line = -1;
|
||||
const char *prev_msg = NULL;
|
||||
cJSON *e;
|
||||
cJSON_ArrayForEach(e, errors) {
|
||||
const char *msg = cJSON_GetStringValue(
|
||||
cJSON_GetObjectItemCaseSensitive(e, "message"));
|
||||
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
|
||||
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column");
|
||||
int cur_line = cJSON_IsNumber(line) ? (int)line->valuedouble : -1;
|
||||
if (prev_msg && msg && cur_line == prev_line && strcmp(msg, prev_msg) == 0)
|
||||
continue;
|
||||
prev_line = cur_line;
|
||||
prev_msg = msg;
|
||||
if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
|
||||
fprintf(stderr, "%s:%d:%d: error: %s\n",
|
||||
filename, (int)line->valuedouble, (int)col->valuedouble, msg);
|
||||
else if (msg)
|
||||
fprintf(stderr, "%s: error: %s\n", filename, msg);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int print_json_errors(const char *json) {
|
||||
if (!json) return 0;
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
if (!root) return 0;
|
||||
int result = print_tree_errors(root);
|
||||
cJSON_Delete(root);
|
||||
return result;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the core path for use by scripts
|
||||
@@ -166,31 +154,17 @@ JSValue js_wota_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
{
|
||||
if (!g_runtime) {
|
||||
g_runtime = JS_NewRuntime();
|
||||
}
|
||||
JSContext *js = JS_NewContext(g_runtime);
|
||||
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
|
||||
JSContext *js = JS_NewContext(rt);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
prt->context = js;
|
||||
|
||||
/* Register all GCRef fields so the Cheney GC can relocate them. */
|
||||
JS_AddGCRef(js, &prt->idx_buffer_ref);
|
||||
JS_AddGCRef(js, &prt->on_exception_ref);
|
||||
JS_AddGCRef(js, &prt->message_handle_ref);
|
||||
JS_AddGCRef(js, &prt->unneeded_ref);
|
||||
JS_AddGCRef(js, &prt->actor_sym_ref);
|
||||
prt->idx_buffer_ref.val = JS_NULL;
|
||||
prt->on_exception_ref.val = JS_NULL;
|
||||
prt->message_handle_ref.val = JS_NULL;
|
||||
prt->unneeded_ref.val = JS_NULL;
|
||||
prt->actor_sym_ref.val = JS_NULL;
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JS_FreeValue(js, js_blob_use(js));
|
||||
|
||||
// Load and parse engine.cm to AST
|
||||
// Load and compile engine.cm
|
||||
size_t engine_size;
|
||||
char *data = load_core_file(ENGINE, &engine_size);
|
||||
if (!data) {
|
||||
@@ -198,71 +172,42 @@ void script_startup(cell_rt *prt)
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(data, engine_size, ENGINE);
|
||||
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE);
|
||||
free(data);
|
||||
if (!ast) {
|
||||
printf("ERROR: Failed to parse %s\n", ENGINE);
|
||||
if (JS_IsException(bytecode)) {
|
||||
uncaught_exception(js, bytecode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
return;
|
||||
}
|
||||
// Create hidden environment
|
||||
JSValue hidden_env = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
|
||||
|
||||
// Create hidden environment (GC-protected to survive allocations)
|
||||
JSGCRef env_ref;
|
||||
JS_PushGCRef(js, &env_ref);
|
||||
env_ref.val = JS_NewObject(js);
|
||||
|
||||
// Create modules with GC rooting
|
||||
JSGCRef os_ref, json_ref, nota_ref, wota_ref;
|
||||
JS_PushGCRef(js, &os_ref);
|
||||
JS_PushGCRef(js, &json_ref);
|
||||
JS_PushGCRef(js, ¬a_ref);
|
||||
JS_PushGCRef(js, &wota_ref);
|
||||
|
||||
os_ref.val = js_os_use(js);
|
||||
json_ref.val = js_json_use(js);
|
||||
nota_ref.val = js_nota_use(js);
|
||||
wota_ref.val = js_wota_use(js);
|
||||
|
||||
// Set properties on env (SetPropertyStr internally roots its args)
|
||||
JS_SetPropertyStr(js, env_ref.val, "os", os_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", json_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "nota", nota_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "wota", wota_ref.val);
|
||||
|
||||
JS_PopGCRef(js, &wota_ref);
|
||||
JS_PopGCRef(js, ¬a_ref);
|
||||
JS_PopGCRef(js, &json_ref);
|
||||
JS_PopGCRef(js, &os_ref);
|
||||
|
||||
crt->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
crt->actor_sym = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym));
|
||||
|
||||
// Always set init (even if null)
|
||||
if (crt->init_wota) {
|
||||
JSValue init_val = wota2value(js, crt->init_wota);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", init_val);
|
||||
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
} else {
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
||||
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
|
||||
}
|
||||
|
||||
if (core_path) {
|
||||
JSValue path_val = JS_NewString(js, core_path);
|
||||
JS_SetPropertyStr(js, env_ref.val, "core_path", path_val);
|
||||
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
|
||||
}
|
||||
|
||||
// Stone the environment
|
||||
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
||||
JS_PopGCRef(js, &env_ref);
|
||||
hidden_env = JS_Stone(js, hidden_env);
|
||||
|
||||
// Run through MACH VM
|
||||
// Integrate and run
|
||||
crt->state = ACTOR_RUNNING;
|
||||
JSValue v = JS_RunMachTree(js, ast, hidden_env);
|
||||
cJSON_Delete(ast);
|
||||
JSValue v = JS_Integrate(js, bytecode, hidden_env);
|
||||
uncaught_exception(js, v);
|
||||
crt->state = ACTOR_IDLE;
|
||||
set_actor_state(crt);
|
||||
@@ -454,13 +399,11 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (ast) {
|
||||
int has_errors = print_tree_errors(ast);
|
||||
char *pretty = cJSON_Print(ast);
|
||||
cJSON_Delete(ast);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
char *json = JS_AST(script, strlen(script), filename);
|
||||
if (json) {
|
||||
int has_errors = print_json_errors(json);
|
||||
printf("%s\n", json);
|
||||
free(json);
|
||||
free(allocated_script);
|
||||
return has_errors ? 1 : 0;
|
||||
} else {
|
||||
@@ -502,14 +445,8 @@ int cell_init(int argc, char **argv)
|
||||
char *json = JS_Tokenize(script, strlen(script), filename);
|
||||
if (json) {
|
||||
int has_errors = print_json_errors(json);
|
||||
cJSON *root = cJSON_Parse(json);
|
||||
printf("%s\n", json);
|
||||
free(json);
|
||||
if (root) {
|
||||
char *pretty = cJSON_Print(root);
|
||||
cJSON_Delete(root);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
}
|
||||
free(allocated_script);
|
||||
return has_errors ? 1 : 0;
|
||||
} else {
|
||||
@@ -548,32 +485,30 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON *mcode = JS_McodeTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
char *mcode_json = JS_Mcode(ast_json);
|
||||
free(ast_json);
|
||||
|
||||
if (!mcode) {
|
||||
if (!mcode_json) {
|
||||
printf("Failed to generate MCODE\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *pretty = cJSON_Print(mcode);
|
||||
cJSON_Delete(mcode);
|
||||
printf("%s\n", pretty);
|
||||
free(pretty);
|
||||
printf("%s\n", mcode_json);
|
||||
free(mcode_json);
|
||||
|
||||
free(allocated_script);
|
||||
return 0;
|
||||
@@ -601,39 +536,40 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cJSON *mcode = JS_McodeTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
char *mcode_json = JS_Mcode(ast_json);
|
||||
free(ast_json);
|
||||
|
||||
if (!mcode) {
|
||||
if (!mcode_json) {
|
||||
printf("Failed to generate MCODE\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_tree_errors(mcode)) {
|
||||
cJSON_Delete(mcode); free(allocated_script);
|
||||
if (print_json_errors(mcode_json)) {
|
||||
free(mcode_json); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Use a larger heap context for execution */
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(mcode); free(allocated_script); return 1; }
|
||||
if (!rt) { printf("Failed to create JS runtime\n"); free(mcode_json); free(allocated_script); return 1; }
|
||||
JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024);
|
||||
if (!ctx) { printf("Failed to create execution context\n"); cJSON_Delete(mcode); JS_FreeRuntime(rt); free(allocated_script); return 1; }
|
||||
if (!ctx) { printf("Failed to create execution context\n"); free(mcode_json); JS_FreeRuntime(rt); free(allocated_script); return 1; }
|
||||
|
||||
JSValue result = JS_CallMcodeTree(ctx, mcode); /* takes ownership of mcode */
|
||||
JSValue result = JS_CallMcode(ctx, mcode_json);
|
||||
free(mcode_json);
|
||||
|
||||
if (JS_IsException(result)) {
|
||||
JSValue exc = JS_GetException(ctx);
|
||||
@@ -693,15 +629,15 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
@@ -709,18 +645,18 @@ int cell_init(int argc, char **argv)
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
free(ast_json); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
|
||||
free(ast_json); JS_FreeRuntime(rt); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JS_DumpMachTree(ctx, ast, JS_NULL);
|
||||
cJSON_Delete(ast);
|
||||
JS_DumpMach(ctx, ast_json, JS_NULL);
|
||||
free(ast_json);
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
@@ -757,15 +693,15 @@ int cell_init(int argc, char **argv)
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
cJSON *ast = JS_ASTTree(script, strlen(script), filename);
|
||||
if (!ast) {
|
||||
char *ast_json = JS_AST(script, strlen(script), filename);
|
||||
if (!ast_json) {
|
||||
printf("Failed to parse AST\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (print_tree_errors(ast)) {
|
||||
cJSON_Delete(ast);
|
||||
if (print_json_errors(ast_json)) {
|
||||
free(ast_json);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
@@ -773,18 +709,18 @@ int cell_init(int argc, char **argv)
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
cJSON_Delete(ast); free(allocated_script);
|
||||
free(ast_json); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
JSContext *ctx = JS_NewContext(rt);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script);
|
||||
free(ast_json); JS_FreeRuntime(rt); free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSValue result = JS_RunMachTree(ctx, ast, JS_NULL);
|
||||
cJSON_Delete(ast);
|
||||
JSValue result = JS_RunMach(ctx, ast_json, JS_NULL);
|
||||
free(ast_json);
|
||||
|
||||
int exit_code = 0;
|
||||
if (JS_IsException(result)) {
|
||||
|
||||
@@ -144,7 +144,7 @@ JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYP
|
||||
|
||||
#define QJSCLASSPREP_NO_FUNCS(TYPE) \
|
||||
JS_NewClassID(&js_##TYPE##_id);\
|
||||
JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
|
||||
JS_NewClass(JS_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\
|
||||
JSValue TYPE##_proto = JS_NewObject(js); \
|
||||
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
|
||||
|
||||
|
||||
@@ -24,26 +24,21 @@ typedef struct letter {
|
||||
|
||||
typedef struct cell_rt {
|
||||
JSContext *context;
|
||||
|
||||
/* JSValues on the GC heap — each paired with a JSGCRef so the
|
||||
Cheney GC can relocate them during compaction. */
|
||||
JSGCRef idx_buffer_ref;
|
||||
JSGCRef on_exception_ref;
|
||||
JSGCRef message_handle_ref;
|
||||
JSGCRef unneeded_ref;
|
||||
JSGCRef actor_sym_ref;
|
||||
JSValue idx_buffer;
|
||||
JSValue on_exception;
|
||||
JSValue message_handle;
|
||||
|
||||
void *init_wota;
|
||||
|
||||
/* Protects JSContext usage */
|
||||
pthread_mutex_t *mutex; /* for everything else */
|
||||
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
|
||||
|
||||
|
||||
char *id;
|
||||
|
||||
int idx_count;
|
||||
|
||||
/* The "mailbox" for incoming messages + a dedicated lock for it: */
|
||||
/* The “mailbox” for incoming messages + a dedicated lock for it: */
|
||||
letter *letters;
|
||||
|
||||
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
||||
@@ -52,11 +47,14 @@ typedef struct cell_rt {
|
||||
int state;
|
||||
uint32_t ar; // timer for unneeded
|
||||
double ar_secs; // time for unneeded
|
||||
|
||||
JSValue unneeded; // fn to call before unneeded
|
||||
|
||||
int disrupt;
|
||||
int main_thread_only;
|
||||
int affinity;
|
||||
|
||||
|
||||
JSValue actor_sym;
|
||||
|
||||
const char *name; // human friendly name
|
||||
cell_hook trace_hook;
|
||||
} cell_rt;
|
||||
@@ -65,6 +63,8 @@ cell_rt *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_disrupt(cell_rt *actor);
|
||||
|
||||
JSValue actor_sym(cell_rt *actor);
|
||||
|
||||
const char *send_message(const char *id, void *msg);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
|
||||
|
||||
@@ -74,7 +74,7 @@ JSC_CCALL(os_register_actor,
|
||||
JS_ToFloat64(js, &ar, argv[3]);
|
||||
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
|
||||
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
|
||||
rt->message_handle_ref.val = argv[1];
|
||||
rt->message_handle = JS_DupValue(js, argv[1]);
|
||||
rt->context = js;
|
||||
JS_FreeCString(js, id);
|
||||
)
|
||||
@@ -106,7 +106,8 @@ JSC_SCALL(actor_setname,
|
||||
|
||||
JSC_CCALL(actor_on_exception,
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
rt->on_exception_ref.val = argv[0];
|
||||
JS_FreeValue(js, rt->on_exception);
|
||||
rt->on_exception = JS_DupValue(js,argv[0]);
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_clock,
|
||||
|
||||
4858
source/quickjs.c
4858
source/quickjs.c
File diff suppressed because it is too large
Load Diff
@@ -367,10 +367,10 @@ JSRuntime *JS_NewRuntime (void);
|
||||
void JS_SetRuntimeInfo (JSRuntime *rt, const char *info);
|
||||
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
|
||||
/* use 0 to disable maximum stack size check */
|
||||
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
|
||||
void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size);
|
||||
/* should be called when changing thread to update the stack top value
|
||||
used to check stack overflow. */
|
||||
void JS_UpdateStackTop (JSContext *ctx);
|
||||
void JS_UpdateStackTop (JSRuntime *rt);
|
||||
void JS_FreeRuntime (JSRuntime *rt);
|
||||
void *JS_GetRuntimeOpaque (JSRuntime *rt);
|
||||
void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque);
|
||||
@@ -429,9 +429,9 @@ JSClassID JS_NewClassID (JSClassID *pclass_id);
|
||||
/* Returns the class ID if `v` is an object, otherwise returns
|
||||
* JS_INVALID_CLASS_ID. */
|
||||
JSClassID JS_GetClassID (JSValue v);
|
||||
int JS_NewClass (JSContext *ctx, JSClassID class_id,
|
||||
int JS_NewClass (JSRuntime *rt, JSClassID class_id,
|
||||
const JSClassDef *class_def);
|
||||
int JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id);
|
||||
int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id);
|
||||
|
||||
extern JSClassID js_class_id_alloc;
|
||||
|
||||
@@ -740,7 +740,7 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
|
||||
|
||||
/* return != 0 if the JS code needs to be interrupted */
|
||||
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
|
||||
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
|
||||
void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb,
|
||||
void *opaque);
|
||||
/* select which debug info is stripped from the compiled code */
|
||||
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */
|
||||
@@ -1218,57 +1218,45 @@ CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_
|
||||
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
|
||||
CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename);
|
||||
|
||||
/* Parse source code and return AST as cJSON tree.
|
||||
Caller must call cJSON_Delete() on result. */
|
||||
struct cJSON *JS_ASTTree (const char *source, size_t len, const char *filename);
|
||||
|
||||
/* Parse source code and return AST as JSON string.
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error. */
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error.
|
||||
No JSContext needed — pure string transformation. */
|
||||
char *JS_AST (const char *source, size_t len, const char *filename);
|
||||
|
||||
/* Tokenize source code and return token array as JSON string.
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error. */
|
||||
Returns malloc'd JSON string (caller must free), or NULL on error.
|
||||
No JSContext needed — pure string transformation. */
|
||||
char *JS_Tokenize (const char *source, size_t len, const char *filename);
|
||||
|
||||
/* Compiled bytecode (context-free, serializable) */
|
||||
typedef struct MachCode MachCode;
|
||||
|
||||
/* Compile AST cJSON tree to context-free MachCode. */
|
||||
MachCode *JS_CompileMachTree(struct cJSON *ast);
|
||||
|
||||
/* Compile AST JSON string to context-free MachCode. */
|
||||
/* Compile AST JSON to context-free MachCode.
|
||||
Returns MachCode* (caller must free with JS_FreeMachCode), or NULL on error. */
|
||||
MachCode *JS_CompileMach(const char *ast_json);
|
||||
|
||||
/* Free a compiled MachCode tree. */
|
||||
void JS_FreeMachCode(MachCode *mc);
|
||||
|
||||
/* Load compiled MachCode into a JSContext, materializing JSValues. */
|
||||
/* Load compiled MachCode into a JSContext, materializing JSValues.
|
||||
Returns JSCodeRegister* linked and ready for execution. */
|
||||
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
|
||||
|
||||
/* Dump MACH bytecode to stdout. Takes AST cJSON tree. */
|
||||
void JS_DumpMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
|
||||
|
||||
/* Dump MACH bytecode to stdout. Takes AST JSON string. */
|
||||
/* Dump MACH bytecode to stdout for debugging. Takes AST JSON.
|
||||
Internally compiles, loads, and dumps binary bytecode. */
|
||||
void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
|
||||
|
||||
/* Compile and execute MACH bytecode from AST cJSON tree. */
|
||||
JSValue JS_RunMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
|
||||
|
||||
/* Compile and execute MACH bytecode from AST JSON string. */
|
||||
/* Compile and execute MACH bytecode. Takes AST JSON.
|
||||
Internally compiles, loads, and executes.
|
||||
Returns result of execution, or JS_EXCEPTION on error. */
|
||||
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
|
||||
|
||||
/* Compile AST cJSON tree to MCODE cJSON tree.
|
||||
Caller must call cJSON_Delete() on result. */
|
||||
struct cJSON *JS_McodeTree (struct cJSON *ast);
|
||||
|
||||
/* Compile AST JSON string to MCODE JSON string.
|
||||
Returns malloc'd JSON string, or NULL on error. Caller must free. */
|
||||
/* Compile AST JSON to MCODE JSON (string-based IR).
|
||||
Returns malloc'd JSON string, or NULL on error. Caller must free.
|
||||
No JSContext needed — pure string transformation. */
|
||||
char *JS_Mcode (const char *ast_json);
|
||||
|
||||
/* Execute MCODE from cJSON tree. Takes ownership of root. */
|
||||
JSValue JS_CallMcodeTree (JSContext *ctx, struct cJSON *root);
|
||||
|
||||
/* Parse and execute MCODE JSON string.
|
||||
/* Parse and execute MCODE JSON directly via the MCODE interpreter.
|
||||
Returns result of execution, or JS_EXCEPTION on error. */
|
||||
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
|
||||
|
||||
|
||||
@@ -266,11 +266,11 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
|
||||
JS_DeleteGCRef(js, &actor->on_exception_ref);
|
||||
JS_DeleteGCRef(js, &actor->message_handle_ref);
|
||||
JS_DeleteGCRef(js, &actor->unneeded_ref);
|
||||
JS_DeleteGCRef(js, &actor->actor_sym_ref);
|
||||
JS_FreeValue(js, actor->idx_buffer);
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeValue(js, actor->actor_sym);
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
@@ -289,8 +289,10 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_SetInterruptHandler(js, NULL, NULL);
|
||||
JSRuntime *rt = JS_GetRuntime(js);
|
||||
JS_SetInterruptHandler(rt, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
JS_FreeRuntime(rt);
|
||||
free(actor->id);
|
||||
|
||||
pthread_mutex_unlock(actor->mutex);
|
||||
@@ -417,8 +419,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
|
||||
actor->disrupt = 1;
|
||||
|
||||
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
if (!JS_IsNull(actor->unneeded)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, ret);
|
||||
}
|
||||
|
||||
@@ -432,13 +434,14 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
|
||||
JS_FreeValue(actor->context, actor->unneeded);
|
||||
|
||||
if (!JS_IsFunction(fn)) {
|
||||
actor->unneeded_ref.val = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
goto END;
|
||||
}
|
||||
|
||||
actor->unneeded_ref.val = fn;
|
||||
|
||||
actor->unneeded = JS_DupValue(actor->context, fn);
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
END:
|
||||
@@ -490,10 +493,11 @@ cell_rt *create_actor(void *wota)
|
||||
actor->heap = mi_heap_new();
|
||||
#endif
|
||||
actor->init_wota = wota;
|
||||
/* GCRef fields are registered after JSContext creation in script_startup.
|
||||
For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
|
||||
which is not a pointer so GC-safe). The actual JS_NULL assignment and
|
||||
JS_AddGCRef happen in script_startup. */
|
||||
actor->idx_buffer = JS_NULL;
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
@@ -583,7 +587,7 @@ void actor_turn(cell_rt *actor)
|
||||
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, (void*)blob_data(l.blob_data), size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
|
||||
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
|
||||
@@ -118,11 +118,11 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
JSContext *js = actor->context;
|
||||
|
||||
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
|
||||
JS_DeleteGCRef(js, &actor->on_exception_ref);
|
||||
JS_DeleteGCRef(js, &actor->message_handle_ref);
|
||||
JS_DeleteGCRef(js, &actor->unneeded_ref);
|
||||
JS_DeleteGCRef(js, &actor->actor_sym_ref);
|
||||
JS_FreeValue(js, actor->idx_buffer);
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeValue(js, actor->actor_sym);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
@@ -142,8 +142,10 @@ void actor_free(cell_rt *actor)
|
||||
|
||||
arrfree(actor->letters);
|
||||
|
||||
JS_SetInterruptHandler(js, NULL, NULL);
|
||||
JSRuntime *rt = JS_GetRuntime(js);
|
||||
JS_SetInterruptHandler(rt, NULL, NULL);
|
||||
JS_FreeContext(js);
|
||||
JS_FreeRuntime(rt);
|
||||
free(actor->id);
|
||||
|
||||
free(actor);
|
||||
@@ -155,13 +157,14 @@ void actor_free(cell_rt *actor)
|
||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
{
|
||||
if (actor->disrupt) return;
|
||||
|
||||
if (!JS_IsFunction(fn)) {
|
||||
actor->unneeded_ref.val = JS_NULL;
|
||||
JS_FreeValue(actor->context, actor->unneeded);
|
||||
|
||||
if (!JS_IsFunction(actor->context, fn)) {
|
||||
actor->unneeded = JS_NULL;
|
||||
goto END;
|
||||
}
|
||||
|
||||
actor->unneeded_ref.val = fn;
|
||||
|
||||
actor->unneeded = JS_DupValue(actor->context, fn);
|
||||
actor->ar_secs = seconds;
|
||||
|
||||
END:
|
||||
@@ -254,8 +257,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
||||
|
||||
actor->disrupt = 1;
|
||||
|
||||
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||
if (!JS_IsNull(actor->unneeded)) {
|
||||
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
|
||||
uncaught_exception(actor->context, ret);
|
||||
}
|
||||
|
||||
@@ -325,7 +328,11 @@ cell_rt *create_actor(void *wota)
|
||||
{
|
||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
||||
actor->init_wota = wota;
|
||||
/* GCRef fields are registered after JSContext creation in script_startup. */
|
||||
actor->idx_buffer = JS_NULL;
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
@@ -401,7 +408,7 @@ void actor_turn(cell_rt *actor)
|
||||
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
|
||||
blob_destroy(l.blob_data);
|
||||
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
|
||||
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg);
|
||||
uncaught_exception(actor->context, result);
|
||||
JS_FreeValue(actor->context, arg);
|
||||
} else if (l.type == LETTER_CALLBACK) {
|
||||
|
||||
@@ -2393,22 +2393,22 @@ TEST(ast_sem_nested_function_scope) {
|
||||
|
||||
TEST(mach_compile_basic) {
|
||||
const char *src = "var x = 1; x = x + 1";
|
||||
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
|
||||
char *ast_json = JS_AST(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
free(ast_json);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
|
||||
JS_FreeMachCode(mc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TEST(mach_compile_function) {
|
||||
const char *src = "function f(x) { return x + 1 }";
|
||||
cJSON *ast = JS_ASTTree(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast != NULL, "JS_ASTTree returned NULL");
|
||||
MachCode *mc = JS_CompileMachTree(ast);
|
||||
cJSON_Delete(ast);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMachTree returned NULL");
|
||||
char *ast_json = JS_AST(src, strlen(src), "<test>");
|
||||
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
|
||||
MachCode *mc = JS_CompileMach(ast_json);
|
||||
free(ast_json);
|
||||
ASSERT_MSG(mc != NULL, "JS_CompileMach returned NULL");
|
||||
JS_FreeMachCode(mc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
657
syntax_suite.ce
657
syntax_suite.ce
@@ -1,657 +0,0 @@
|
||||
// Syntax suite: covers every syntactic feature of cell script
|
||||
// Run: ./cell --mach-run syntax_suite.ce
|
||||
|
||||
var passed = 0
|
||||
var failed = 0
|
||||
var error_names = []
|
||||
var error_reasons = []
|
||||
var fail_msg = ""
|
||||
|
||||
for (var _i = 0; _i < 100; _i++) {
|
||||
error_names[] = null
|
||||
error_reasons[] = null
|
||||
}
|
||||
|
||||
var fail = function(msg) {
|
||||
fail_msg = msg
|
||||
disrupt
|
||||
}
|
||||
|
||||
var assert_eq = function(actual, expected, msg) {
|
||||
if (actual != expected) fail(msg + " (got=" + text(actual) + " expected=" + text(expected) + ")")
|
||||
}
|
||||
|
||||
var run = function(name, fn) {
|
||||
fail_msg = ""
|
||||
fn()
|
||||
passed = passed + 1
|
||||
} disruption {
|
||||
error_names[failed] = name
|
||||
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
|
||||
failed = failed + 1
|
||||
}
|
||||
|
||||
var should_disrupt = function(fn) {
|
||||
var caught = false
|
||||
var wrapper = function() {
|
||||
fn()
|
||||
} disruption {
|
||||
caught = true
|
||||
}
|
||||
wrapper()
|
||||
return caught
|
||||
}
|
||||
|
||||
// === LITERALS ===
|
||||
|
||||
run("number literals", function() {
|
||||
assert_eq(42, 42, "integer")
|
||||
assert_eq(3.14 > 3, true, "float")
|
||||
assert_eq(-5, -5, "negative")
|
||||
assert_eq(0, 0, "zero")
|
||||
assert_eq(1e3, 1000, "scientific")
|
||||
})
|
||||
|
||||
run("string literals", function() {
|
||||
assert_eq("hello", "hello", "double quote")
|
||||
assert_eq("", "", "empty string")
|
||||
assert_eq("line1\nline2" != "line1line2", true, "escape sequence")
|
||||
})
|
||||
|
||||
run("template literals", function() {
|
||||
var x = "world"
|
||||
assert_eq(`hello ${x}`, "hello world", "interpolation")
|
||||
assert_eq(`${1 + 2}`, "3", "expression interpolation")
|
||||
assert_eq(`plain`, "plain", "no interpolation")
|
||||
})
|
||||
|
||||
run("boolean literals", function() {
|
||||
assert_eq(true, true, "true")
|
||||
assert_eq(false, false, "false")
|
||||
})
|
||||
|
||||
run("null literal", function() {
|
||||
assert_eq(null, null, "null")
|
||||
})
|
||||
|
||||
run("array literal", function() {
|
||||
var a = [1, 2, 3]
|
||||
assert_eq(length(a), 3, "array length")
|
||||
assert_eq(a[0], 1, "first element")
|
||||
var e = []
|
||||
assert_eq(length(e), 0, "empty array")
|
||||
})
|
||||
|
||||
run("object literal", function() {
|
||||
var o = {a: 1, b: "two"}
|
||||
assert_eq(o.a, 1, "object prop")
|
||||
var e = {}
|
||||
assert_eq(e.x, null, "empty object missing prop")
|
||||
})
|
||||
|
||||
run("regex literal", function() {
|
||||
var r = /\d+/
|
||||
var result = extract("abc123", r)
|
||||
assert_eq(result[0], "123", "regex match")
|
||||
var ri = /hello/i
|
||||
var result2 = extract("Hello", ri)
|
||||
assert_eq(result2[0], "Hello", "regex flags")
|
||||
})
|
||||
|
||||
// === DECLARATIONS ===
|
||||
|
||||
run("var declaration", function() {
|
||||
var x = 5
|
||||
assert_eq(x, 5, "var init")
|
||||
})
|
||||
|
||||
run("var uninitialized", function() {
|
||||
var x
|
||||
assert_eq(x, null, "var defaults to null")
|
||||
})
|
||||
|
||||
run("var multiple declaration", function() {
|
||||
var a = 1, b = 2, c = 3
|
||||
assert_eq(a + b + c, 6, "multi var")
|
||||
})
|
||||
|
||||
run("def declaration", function() {
|
||||
def x = 42
|
||||
assert_eq(x, 42, "def const")
|
||||
})
|
||||
|
||||
// === ARITHMETIC OPERATORS ===
|
||||
|
||||
run("arithmetic operators", function() {
|
||||
assert_eq(2 + 3, 5, "add")
|
||||
assert_eq(5 - 3, 2, "sub")
|
||||
assert_eq(3 * 4, 12, "mul")
|
||||
assert_eq(12 / 4, 3, "div")
|
||||
assert_eq(10 % 3, 1, "mod")
|
||||
assert_eq(2 ** 3, 8, "exp")
|
||||
})
|
||||
|
||||
// === COMPARISON OPERATORS ===
|
||||
|
||||
run("comparison operators", function() {
|
||||
assert_eq(5 == 5, true, "eq")
|
||||
assert_eq(5 != 6, true, "neq")
|
||||
assert_eq(3 < 5, true, "lt")
|
||||
assert_eq(5 > 3, true, "gt")
|
||||
assert_eq(3 <= 3, true, "lte")
|
||||
assert_eq(5 >= 5, true, "gte")
|
||||
})
|
||||
|
||||
// === LOGICAL OPERATORS ===
|
||||
|
||||
run("logical operators", function() {
|
||||
assert_eq(true && true, true, "and")
|
||||
assert_eq(true && false, false, "and false")
|
||||
assert_eq(false || true, true, "or")
|
||||
assert_eq(false || false, false, "or false")
|
||||
assert_eq(!true, false, "not")
|
||||
assert_eq(!false, true, "not false")
|
||||
})
|
||||
|
||||
run("short circuit", function() {
|
||||
var called = false
|
||||
var fn = function() { called = true; return true }
|
||||
var r = false && fn()
|
||||
assert_eq(called, false, "and short circuit")
|
||||
r = true || fn()
|
||||
assert_eq(called, false, "or short circuit")
|
||||
})
|
||||
|
||||
// === BITWISE OPERATORS ===
|
||||
|
||||
run("bitwise operators", function() {
|
||||
assert_eq(5 & 3, 1, "and")
|
||||
assert_eq(5 | 3, 7, "or")
|
||||
assert_eq(5 ^ 3, 6, "xor")
|
||||
assert_eq(~0, -1, "not")
|
||||
assert_eq(1 << 3, 8, "lshift")
|
||||
assert_eq(8 >> 3, 1, "rshift")
|
||||
assert_eq(-1 >>> 1, 2147483647, "unsigned rshift")
|
||||
})
|
||||
|
||||
// === UNARY OPERATORS ===
|
||||
|
||||
run("unary operators", function() {
|
||||
assert_eq(+5, 5, "unary plus")
|
||||
assert_eq(-5, -5, "unary minus")
|
||||
assert_eq(-(-5), 5, "double negate")
|
||||
})
|
||||
|
||||
run("increment decrement", function() {
|
||||
var x = 5
|
||||
assert_eq(x++, 5, "postfix inc returns old")
|
||||
assert_eq(x, 6, "postfix inc side effect")
|
||||
x = 5
|
||||
assert_eq(++x, 6, "prefix inc returns new")
|
||||
x = 5
|
||||
assert_eq(x--, 5, "postfix dec returns old")
|
||||
assert_eq(x, 4, "postfix dec side effect")
|
||||
x = 5
|
||||
assert_eq(--x, 4, "prefix dec returns new")
|
||||
})
|
||||
|
||||
// === COMPOUND ASSIGNMENT ===
|
||||
|
||||
run("compound assignment", function() {
|
||||
var x = 10
|
||||
x += 3; assert_eq(x, 13, "+=")
|
||||
x -= 3; assert_eq(x, 10, "-=")
|
||||
x *= 2; assert_eq(x, 20, "*=")
|
||||
x /= 4; assert_eq(x, 5, "/=")
|
||||
x %= 3; assert_eq(x, 2, "%=")
|
||||
})
|
||||
|
||||
// === TERNARY OPERATOR ===
|
||||
|
||||
run("ternary operator", function() {
|
||||
var a = true ? 1 : 2
|
||||
assert_eq(a, 1, "ternary true")
|
||||
var b = false ? 1 : 2
|
||||
assert_eq(b, 2, "ternary false")
|
||||
var c = true ? (false ? 1 : 2) : 3
|
||||
assert_eq(c, 2, "ternary nested")
|
||||
})
|
||||
|
||||
// === COMMA OPERATOR ===
|
||||
|
||||
run("comma operator", function() {
|
||||
var x = (1, 2, 3)
|
||||
assert_eq(x, 3, "comma returns last")
|
||||
})
|
||||
|
||||
// === IN OPERATOR ===
|
||||
|
||||
run("in operator", function() {
|
||||
var o = {a: 1}
|
||||
assert_eq("a" in o, true, "key exists")
|
||||
assert_eq("b" in o, false, "key missing")
|
||||
})
|
||||
|
||||
// === DELETE OPERATOR ===
|
||||
|
||||
run("delete operator", function() {
|
||||
var o = {a: 1, b: 2}
|
||||
delete o.a
|
||||
assert_eq("a" in o, false, "delete removes key")
|
||||
assert_eq(o.b, 2, "delete leaves others")
|
||||
})
|
||||
|
||||
// === PROPERTY ACCESS ===
|
||||
|
||||
run("dot access", function() {
|
||||
var o = {x: 10}
|
||||
assert_eq(o.x, 10, "dot read")
|
||||
o.x = 20
|
||||
assert_eq(o.x, 20, "dot write")
|
||||
})
|
||||
|
||||
run("bracket access", function() {
|
||||
var o = {x: 10}
|
||||
assert_eq(o["x"], 10, "bracket read")
|
||||
var key = "x"
|
||||
assert_eq(o[key], 10, "computed bracket")
|
||||
o["y"] = 20
|
||||
assert_eq(o.y, 20, "bracket write")
|
||||
})
|
||||
|
||||
run("object-as-key", function() {
|
||||
var k = {}
|
||||
var o = {}
|
||||
o[k] = 42
|
||||
assert_eq(o[k], 42, "object key set/get")
|
||||
assert_eq(o[{}], null, "new object is different key")
|
||||
assert_eq(k in o, true, "object key in")
|
||||
delete o[k]
|
||||
assert_eq(k in o, false, "object key delete")
|
||||
})
|
||||
|
||||
run("chained access", function() {
|
||||
var d = {a: {b: [1, {c: 99}]}}
|
||||
assert_eq(d.a.b[1].c, 99, "mixed chain")
|
||||
})
|
||||
|
||||
// === ARRAY PUSH/POP SYNTAX ===
|
||||
|
||||
run("array push pop", function() {
|
||||
var a = [1, 2]
|
||||
a[] = 3
|
||||
assert_eq(length(a), 3, "push length")
|
||||
assert_eq(a[2], 3, "push value")
|
||||
var v = a[]
|
||||
assert_eq(v, 3, "pop value")
|
||||
assert_eq(length(a), 2, "pop length")
|
||||
})
|
||||
|
||||
// === CONTROL FLOW: IF/ELSE ===
|
||||
|
||||
run("if else", function() {
|
||||
var x = 0
|
||||
if (true) x = 1
|
||||
assert_eq(x, 1, "if true")
|
||||
if (false) x = 2 else x = 3
|
||||
assert_eq(x, 3, "if else")
|
||||
if (false) x = 4
|
||||
else if (true) x = 5
|
||||
else x = 6
|
||||
assert_eq(x, 5, "else if")
|
||||
})
|
||||
|
||||
// === CONTROL FLOW: WHILE ===
|
||||
|
||||
run("while loop", function() {
|
||||
var i = 0
|
||||
while (i < 5) i++
|
||||
assert_eq(i, 5, "while basic")
|
||||
})
|
||||
|
||||
run("while break continue", function() {
|
||||
var i = 0
|
||||
while (true) {
|
||||
if (i >= 3) break
|
||||
i++
|
||||
}
|
||||
assert_eq(i, 3, "while break")
|
||||
var sum = 0
|
||||
i = 0
|
||||
while (i < 5) {
|
||||
i++
|
||||
if (i % 2 == 0) continue
|
||||
sum += i
|
||||
}
|
||||
assert_eq(sum, 9, "while continue")
|
||||
})
|
||||
|
||||
// === CONTROL FLOW: FOR ===
|
||||
|
||||
run("for loop", function() {
|
||||
var sum = 0
|
||||
for (var i = 0; i < 5; i++) sum += i
|
||||
assert_eq(sum, 10, "for basic")
|
||||
})
|
||||
|
||||
run("for break continue", function() {
|
||||
var sum = 0
|
||||
for (var i = 0; i < 10; i++) {
|
||||
if (i == 5) break
|
||||
sum += i
|
||||
}
|
||||
assert_eq(sum, 10, "for break")
|
||||
sum = 0
|
||||
for (var i = 0; i < 5; i++) {
|
||||
if (i % 2 == 0) continue
|
||||
sum += i
|
||||
}
|
||||
assert_eq(sum, 4, "for continue")
|
||||
})
|
||||
|
||||
run("nested for", function() {
|
||||
var sum = 0
|
||||
for (var i = 0; i < 3; i++)
|
||||
for (var j = 0; j < 3; j++)
|
||||
sum++
|
||||
assert_eq(sum, 9, "nested for")
|
||||
})
|
||||
|
||||
// === BLOCK SCOPING ===
|
||||
|
||||
run("block scoping", function() {
|
||||
var x = 1
|
||||
{
|
||||
var x = 2
|
||||
assert_eq(x, 2, "inner block")
|
||||
}
|
||||
assert_eq(x, 1, "outer preserved")
|
||||
})
|
||||
|
||||
run("for iterator scope", function() {
|
||||
for (var i = 0; i < 1; i++) {}
|
||||
assert_eq(should_disrupt(function() { var y = i }), true, "for var does not leak")
|
||||
})
|
||||
|
||||
// === FUNCTIONS ===
|
||||
|
||||
run("function expression", function() {
|
||||
var fn = function(a, b) { return a + b }
|
||||
assert_eq(fn(2, 3), 5, "basic call")
|
||||
})
|
||||
|
||||
run("arrow function", function() {
|
||||
var double = x => x * 2
|
||||
assert_eq(double(5), 10, "arrow single param")
|
||||
var add = (a, b) => a + b
|
||||
assert_eq(add(2, 3), 5, "arrow multi param")
|
||||
var block = x => {
|
||||
var y = x * 2
|
||||
return y + 1
|
||||
}
|
||||
assert_eq(block(5), 11, "arrow block body")
|
||||
})
|
||||
|
||||
run("function no return", function() {
|
||||
var fn = function() { var x = 1 }
|
||||
assert_eq(fn(), null, "no return gives null")
|
||||
})
|
||||
|
||||
run("function early return", function() {
|
||||
var fn = function() { return 1; return 2 }
|
||||
assert_eq(fn(), 1, "early return")
|
||||
})
|
||||
|
||||
run("extra and missing args", function() {
|
||||
var fn = function(a, b) { return a + b }
|
||||
assert_eq(fn(1, 2, 3), 3, "extra args ignored")
|
||||
var fn2 = function(a, b) { return a }
|
||||
assert_eq(fn2(1), 1, "missing args ok")
|
||||
})
|
||||
|
||||
run("iife", function() {
|
||||
var r = (function(x) { return x * 2 })(21)
|
||||
assert_eq(r, 42, "immediately invoked")
|
||||
})
|
||||
|
||||
// === CLOSURES ===
|
||||
|
||||
run("closure", function() {
|
||||
var make = function(x) {
|
||||
return function(y) { return x + y }
|
||||
}
|
||||
var add5 = make(5)
|
||||
assert_eq(add5(3), 8, "closure captures")
|
||||
})
|
||||
|
||||
run("closure mutation", function() {
|
||||
var counter = function() {
|
||||
var n = 0
|
||||
return function() { n = n + 1; return n }
|
||||
}
|
||||
var c = counter()
|
||||
assert_eq(c(), 1, "first")
|
||||
assert_eq(c(), 2, "second")
|
||||
})
|
||||
|
||||
// === RECURSION ===
|
||||
|
||||
run("recursion", function() {
|
||||
var fact = function(n) {
|
||||
if (n <= 1) return 1
|
||||
return n * fact(n - 1)
|
||||
}
|
||||
assert_eq(fact(5), 120, "factorial")
|
||||
})
|
||||
|
||||
// === THIS BINDING ===
|
||||
|
||||
run("this binding", function() {
|
||||
var obj = {
|
||||
val: 10,
|
||||
get: function() { return this.val }
|
||||
}
|
||||
assert_eq(obj.get(), 10, "method this")
|
||||
})
|
||||
|
||||
// === DISRUPTION ===
|
||||
|
||||
run("disrupt keyword", function() {
|
||||
assert_eq(should_disrupt(function() { disrupt }), true, "bare disrupt")
|
||||
})
|
||||
|
||||
run("disruption handler", function() {
|
||||
var x = 0
|
||||
var fn = function() { x = 1 } disruption { x = 2 }
|
||||
fn()
|
||||
assert_eq(x, 1, "no disruption path")
|
||||
var fn2 = function() { disrupt } disruption { x = 3 }
|
||||
fn2()
|
||||
assert_eq(x, 3, "disruption caught")
|
||||
})
|
||||
|
||||
run("disruption re-raise", function() {
|
||||
var outer_caught = false
|
||||
var outer = function() {
|
||||
var inner = function() { disrupt } disruption { disrupt }
|
||||
inner()
|
||||
} disruption {
|
||||
outer_caught = true
|
||||
}
|
||||
outer()
|
||||
assert_eq(outer_caught, true, "re-raise propagates")
|
||||
})
|
||||
|
||||
// === PROTOTYPAL INHERITANCE ===
|
||||
|
||||
run("meme and proto", function() {
|
||||
var parent = {x: 10}
|
||||
var child = meme(parent)
|
||||
assert_eq(child.x, 10, "inherited prop")
|
||||
assert_eq(proto(child), parent, "proto returns parent")
|
||||
child.x = 20
|
||||
assert_eq(parent.x, 10, "override does not mutate parent")
|
||||
})
|
||||
|
||||
run("meme with mixins", function() {
|
||||
var p = {a: 1}
|
||||
var m1 = {b: 2}
|
||||
var m2 = {c: 3}
|
||||
var child = meme(p, [m1, m2])
|
||||
assert_eq(child.a, 1, "parent prop")
|
||||
assert_eq(child.b, 2, "mixin1")
|
||||
assert_eq(child.c, 3, "mixin2")
|
||||
})
|
||||
|
||||
// === STONE (FREEZE) ===
|
||||
|
||||
run("stone", function() {
|
||||
var o = {x: 1}
|
||||
assert_eq(is_stone(o), false, "not frozen")
|
||||
stone(o)
|
||||
assert_eq(is_stone(o), true, "frozen")
|
||||
assert_eq(should_disrupt(function() { o.x = 2 }), true, "write disrupts")
|
||||
})
|
||||
|
||||
// === FUNCTION PROXY ===
|
||||
|
||||
run("function proxy", function() {
|
||||
var proxy = function(name, args) {
|
||||
return `${name}:${length(args)}`
|
||||
}
|
||||
assert_eq(proxy.hello(), "hello:0", "proxy dot call")
|
||||
assert_eq(proxy.add(1, 2), "add:2", "proxy with args")
|
||||
assert_eq(proxy["method"](), "method:0", "proxy bracket call")
|
||||
var m = "dynamic"
|
||||
assert_eq(proxy[m](), "dynamic:0", "proxy computed name")
|
||||
})
|
||||
|
||||
run("non-proxy function prop access disrupts", function() {
|
||||
var fn = function() { return 1 }
|
||||
assert_eq(should_disrupt(function() { var x = fn.foo }), true, "prop read disrupts")
|
||||
assert_eq(should_disrupt(function() { fn.foo = 1 }), true, "prop write disrupts")
|
||||
})
|
||||
|
||||
// === TYPE CHECKING ===
|
||||
|
||||
run("is_* functions", function() {
|
||||
assert_eq(is_number(42), true, "is_number")
|
||||
assert_eq(is_text("hi"), true, "is_text")
|
||||
assert_eq(is_logical(true), true, "is_logical")
|
||||
assert_eq(is_object({}), true, "is_object")
|
||||
assert_eq(is_array([]), true, "is_array")
|
||||
assert_eq(is_function(function(){}), true, "is_function")
|
||||
assert_eq(is_null(null), true, "is_null")
|
||||
assert_eq(is_object([]), false, "array not object")
|
||||
assert_eq(is_array({}), false, "object not array")
|
||||
})
|
||||
|
||||
// === TRUTHINESS / FALSINESS ===
|
||||
|
||||
run("falsy values", function() {
|
||||
if (false) fail("false")
|
||||
if (0) fail("0")
|
||||
if ("") fail("empty string")
|
||||
if (null) fail("null")
|
||||
assert_eq(true, true, "all falsy passed")
|
||||
})
|
||||
|
||||
run("truthy values", function() {
|
||||
if (!1) fail("1")
|
||||
if (!"hi") fail("string")
|
||||
if (!{}) fail("object")
|
||||
if (![]) fail("array")
|
||||
if (!true) fail("true")
|
||||
assert_eq(true, true, "all truthy passed")
|
||||
})
|
||||
|
||||
// === VARIABLE SHADOWING ===
|
||||
|
||||
run("variable shadowing", function() {
|
||||
var x = 10
|
||||
var fn = function() {
|
||||
var x = 20
|
||||
return x
|
||||
}
|
||||
assert_eq(fn(), 20, "inner shadows")
|
||||
assert_eq(x, 10, "outer unchanged")
|
||||
})
|
||||
|
||||
// === OPERATOR PRECEDENCE ===
|
||||
|
||||
run("precedence", function() {
|
||||
assert_eq(2 + 3 * 4, 14, "mul before add")
|
||||
assert_eq((2 + 3) * 4, 20, "parens override")
|
||||
assert_eq(-2 * 3, -6, "unary before mul")
|
||||
})
|
||||
|
||||
// === CURRYING / HIGHER-ORDER ===
|
||||
|
||||
run("curried function", function() {
|
||||
var f = function(a) {
|
||||
return function(b) {
|
||||
return function(c) { return a + b + c }
|
||||
}
|
||||
}
|
||||
assert_eq(f(1)(2)(3), 6, "triple curry")
|
||||
})
|
||||
|
||||
// === SELF-REFERENCING STRUCTURES ===
|
||||
|
||||
run("self-referencing object", function() {
|
||||
var o = {name: "root"}
|
||||
o.self = o
|
||||
assert_eq(o.self.self.name, "root", "cycle access")
|
||||
})
|
||||
|
||||
// === IDENTIFIER ? AND ! ===
|
||||
|
||||
run("question mark in identifier", function() {
|
||||
var nil? = (x) => x == null
|
||||
assert_eq(nil?(null), true, "nil? null")
|
||||
assert_eq(nil?(42), false, "nil? 42")
|
||||
})
|
||||
|
||||
run("bang in identifier", function() {
|
||||
var set! = (x) => x + 1
|
||||
assert_eq(set!(5), 6, "set! call")
|
||||
})
|
||||
|
||||
run("question mark mid identifier", function() {
|
||||
var is?valid = (x) => x > 0
|
||||
assert_eq(is?valid(3), true, "is?valid true")
|
||||
assert_eq(is?valid(-1), false, "is?valid false")
|
||||
})
|
||||
|
||||
run("bang mid identifier", function() {
|
||||
var do!stuff = () => 42
|
||||
assert_eq(do!stuff(), 42, "do!stuff call")
|
||||
})
|
||||
|
||||
run("ternary after question ident", function() {
|
||||
var nil? = (x) => x == null
|
||||
var a = nil?(null) ? "yes" : "no"
|
||||
assert_eq(a, "yes", "ternary true branch")
|
||||
var b = nil?(42) ? "yes" : "no"
|
||||
assert_eq(b, "no", "ternary false branch")
|
||||
})
|
||||
|
||||
run("bang not confused with logical not", function() {
|
||||
assert_eq(!true, false, "logical not true")
|
||||
assert_eq(!false, true, "logical not false")
|
||||
})
|
||||
|
||||
run("inequality not confused with bang ident", function() {
|
||||
assert_eq(1 != 2, true, "inequality true")
|
||||
assert_eq(1 != 1, false, "inequality false")
|
||||
})
|
||||
|
||||
// === SUMMARY ===
|
||||
|
||||
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
|
||||
if (failed > 0) {
|
||||
print("")
|
||||
for (var _j = 0; _j < failed; _j++) {
|
||||
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
|
||||
}
|
||||
}
|
||||
92
test.ce
92
test.ce
@@ -29,13 +29,12 @@ function is_valid_package(dir) {
|
||||
// Get current package name from cell.toml or null
|
||||
function get_current_package_name() {
|
||||
if (!is_valid_package('.')) return null
|
||||
var pkg_name = 'local'
|
||||
var do_load = function() {
|
||||
try {
|
||||
var config = pkg.load_config(null)
|
||||
if (config.package) pkg_name = config.package
|
||||
} disruption {}
|
||||
do_load()
|
||||
return pkg_name
|
||||
return config.package || 'local'
|
||||
} catch (e) {
|
||||
return 'local'
|
||||
}
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
@@ -230,48 +229,21 @@ function spawn_actor_test(test_info) {
|
||||
actor: null
|
||||
}
|
||||
|
||||
var spawn_disrupted = false
|
||||
var do_spawn = function() {
|
||||
try {
|
||||
// Spawn the actor test - it should send back results
|
||||
var actor_path = text(test_info.path, 0, -3) // remove .ce
|
||||
entry.actor = $start(actor_path)
|
||||
push(pending_actor_tests, entry)
|
||||
} disruption {
|
||||
spawn_disrupted = true
|
||||
}
|
||||
do_spawn()
|
||||
if (spawn_disrupted) {
|
||||
} catch (e) {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: `Failed to spawn actor: ${test_name}` }
|
||||
entry.error = { message: `Failed to spawn actor: ${e}` }
|
||||
entry.duration_ns = 0
|
||||
push(actor_test_results, entry)
|
||||
log.console(` FAIL ${test_name}`)
|
||||
log.console(` FAIL ${test_name}: `)
|
||||
log.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Test runner with disruption support
|
||||
var test_passed = true
|
||||
var test_error_msg = ""
|
||||
var test_error_stack = ""
|
||||
|
||||
var run_test = function(fn) {
|
||||
test_passed = true
|
||||
test_error_msg = ""
|
||||
test_error_stack = ""
|
||||
var ret = fn()
|
||||
if (is_text(ret)) {
|
||||
test_passed = false
|
||||
test_error_msg = ret
|
||||
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
|
||||
test_passed = false
|
||||
test_error_msg = ret.message || text(ret)
|
||||
if (ret.stack) test_error_stack = ret.stack
|
||||
}
|
||||
} disruption {
|
||||
test_passed = false
|
||||
if (test_error_msg == "") test_error_msg = "test disrupted"
|
||||
}
|
||||
|
||||
function run_tests(package_name, specific_test) {
|
||||
var prefix = get_pkg_dir(package_name)
|
||||
var tests_dir = prefix + '/tests'
|
||||
@@ -321,9 +293,7 @@ function run_tests(package_name, specific_test) {
|
||||
failed: 0
|
||||
}
|
||||
|
||||
var load_disrupted = false
|
||||
var load_error_msg = ""
|
||||
var do_load = function() {
|
||||
try {
|
||||
var test_mod
|
||||
// For local packages (null), use the current directory as package context
|
||||
var use_pkg = package_name ? package_name : fd.realpath('.')
|
||||
@@ -352,23 +322,34 @@ function run_tests(package_name, specific_test) {
|
||||
}
|
||||
|
||||
var start_time = time.number()
|
||||
run_test(t.fn)
|
||||
try {
|
||||
var ret = t.fn()
|
||||
|
||||
if (is_text(ret)) {
|
||||
throw Error(ret)
|
||||
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
|
||||
throw ret
|
||||
}
|
||||
|
||||
if (test_passed) {
|
||||
test_entry.status = "passed"
|
||||
log.console(` PASS ${t.name}`)
|
||||
pkg_result.passed++
|
||||
file_result.passed++
|
||||
} else {
|
||||
} catch (e) {
|
||||
test_entry.status = "failed"
|
||||
test_entry.error = {
|
||||
message: test_error_msg
|
||||
message: e,
|
||||
stack: e.stack || ""
|
||||
}
|
||||
if (test_error_stack) test_entry.error.stack = test_error_stack
|
||||
if (e.name) test_entry.error.name = e.name
|
||||
|
||||
log.console(` FAIL ${t.name} ${test_error_msg}`)
|
||||
if (test_error_stack) {
|
||||
log.console(` ${text(array(test_error_stack, '\n'), '\n ')}`)
|
||||
if (is_object(e) && e.message) {
|
||||
test_entry.error.message = e.message
|
||||
}
|
||||
|
||||
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
|
||||
if (test_entry.error.stack) {
|
||||
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
|
||||
}
|
||||
|
||||
pkg_result.failed++
|
||||
@@ -384,18 +365,15 @@ function run_tests(package_name, specific_test) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console(` Error loading ${f}`)
|
||||
var test_entry = {
|
||||
|
||||
} catch (e) {
|
||||
log.console(` Error loading ${f}: ${e}`)
|
||||
var test_entry = {
|
||||
package: pkg_result.package,
|
||||
test: "load_module",
|
||||
status: "failed",
|
||||
duration_ns: 0,
|
||||
error: { message: `Error loading module: ${f}` }
|
||||
error: { message: `Error loading module: ${e}` }
|
||||
}
|
||||
push(file_result.tests, test_entry)
|
||||
pkg_result.failed++
|
||||
|
||||
352
tests/blob.cm
352
tests/blob.cm
@@ -4,65 +4,64 @@ var os = use('os');
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
return message || "Assertion failed"
|
||||
throw Error(message || "Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual != expected) {
|
||||
return message || "Expected " + expected + ", got " + actual
|
||||
throw Error(message || "Expected " + expected + ", got " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
function should_disrupt(fn) {
|
||||
var caught = false
|
||||
var wrapper = function() { fn() } disruption { caught = true }
|
||||
wrapper()
|
||||
return caught
|
||||
}
|
||||
|
||||
return {
|
||||
test_create_empty_blob: function() {
|
||||
var b = Blob();
|
||||
return assertEqual(length(b), 0, "Empty blob should have length 0")
|
||||
assertEqual(length(b), 0, "Empty blob should have length 0");
|
||||
},
|
||||
|
||||
|
||||
test_create_blob_with_capacity: function() {
|
||||
var b = Blob(100);
|
||||
return assertEqual(length(b), 0, "New blob with capacity should still have length 0")
|
||||
assertEqual(length(b), 0, "New blob with capacity should still have length 0");
|
||||
},
|
||||
|
||||
|
||||
test_write_and_read_single_bit: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_bit(false);
|
||||
b.write_bit(1);
|
||||
b.write_bit(0);
|
||||
var r = assertEqual(length(b), 4, "Should have 4 bits after writing")
|
||||
if (r) return r
|
||||
|
||||
assertEqual(length(b), 4, "Should have 4 bits after writing");
|
||||
|
||||
stone(b);
|
||||
r = assertEqual(b.read_logical(0), true, "First bit should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(1), false, "Second bit should be false")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(2), true, "Third bit should be true (1)")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)")
|
||||
assertEqual(b.read_logical(0), true, "First bit should be true");
|
||||
assertEqual(b.read_logical(1), false, "Second bit should be false");
|
||||
assertEqual(b.read_logical(2), true, "Third bit should be true (1)");
|
||||
assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)");
|
||||
},
|
||||
|
||||
|
||||
test_out_of_range_read_throws_error: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
if (!should_disrupt(function() { b.read_logical(100) }))
|
||||
return "Out of range read should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b.read_logical(-1) }))
|
||||
return "Negative index read should disrupt"
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_logical(100);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Out of range read should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_logical(-1);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Negative index read should throw");
|
||||
},
|
||||
|
||||
|
||||
test_write_and_read_numbers: function() {
|
||||
var b = Blob();
|
||||
b.write_number(3.14159);
|
||||
@@ -70,48 +69,41 @@ return {
|
||||
b.write_number(0);
|
||||
b.write_number(1e20);
|
||||
stone(b);
|
||||
|
||||
var r = assertEqual(b.read_number(0), 3.14159, "First number should match")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_number(64), -42, "Second number should match")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_number(128), 0, "Third number should match")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_number(192), 1e20, "Fourth number should match")
|
||||
|
||||
assertEqual(b.read_number(0), 3.14159, "First number should match");
|
||||
assertEqual(b.read_number(64), -42, "Second number should match");
|
||||
assertEqual(b.read_number(128), 0, "Third number should match");
|
||||
assertEqual(b.read_number(192), 1e20, "Fourth number should match");
|
||||
},
|
||||
|
||||
|
||||
test_write_and_read_text: function() {
|
||||
var b = Blob();
|
||||
b.write_text("Hello");
|
||||
b.write_text("World");
|
||||
b.write_text("🎉");
|
||||
stone(b);
|
||||
|
||||
return assertEqual(b.read_text(0), "Hello", "First text should match")
|
||||
|
||||
assertEqual(b.read_text(0), "Hello", "First text should match");
|
||||
},
|
||||
|
||||
|
||||
test_write_and_read_blobs: function() {
|
||||
var b1 = Blob();
|
||||
b1.write_bit(true);
|
||||
b1.write_bit(false);
|
||||
b1.write_bit(true);
|
||||
|
||||
|
||||
var b2 = Blob(10);
|
||||
b2.write_blob(b1);
|
||||
b2.write_bit(false);
|
||||
var r = assertEqual(length(b2), 4, "Combined blob should have 4 bits")
|
||||
if (r) return r
|
||||
|
||||
assertEqual(length(b2), 4, "Combined blob should have 4 bits");
|
||||
|
||||
stone(b2);
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(1), false)
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(2), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(3), false)
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(1), false);
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
assertEqual(b2.read_logical(3), false);
|
||||
},
|
||||
|
||||
|
||||
test_blob_copy_constructor: function() {
|
||||
var b1 = Blob();
|
||||
b1.write_bit(true);
|
||||
@@ -119,219 +111,249 @@ return {
|
||||
b1.write_bit(true);
|
||||
b1.write_bit(true);
|
||||
stone(b1);
|
||||
|
||||
|
||||
var b2 = Blob(b1);
|
||||
stone(b2);
|
||||
var r = assertEqual(length(b2), 4, "Copied blob should have same length")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(3), true)
|
||||
assertEqual(length(b2), 4, "Copied blob should have same length");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(3), true);
|
||||
},
|
||||
|
||||
|
||||
test_blob_partial_copy_constructor: function() {
|
||||
var b1 = Blob();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
b1.write_bit(i % 2 == 0);
|
||||
}
|
||||
stone(b1);
|
||||
|
||||
|
||||
var b2 = Blob(b1, 2, 7);
|
||||
stone(b2);
|
||||
var r = assertEqual(length(b2), 5, "Partial copy should have 5 bits")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(2), true)
|
||||
assertEqual(length(b2), 5, "Partial copy should have 5 bits");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
},
|
||||
|
||||
|
||||
test_create_blob_with_fill: function() {
|
||||
var b1 = Blob(8, true);
|
||||
var b2 = Blob(8, false);
|
||||
|
||||
|
||||
stone(b1);
|
||||
stone(b2);
|
||||
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
var r = assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false")
|
||||
if (r) return r
|
||||
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true");
|
||||
assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
test_create_blob_with_random_function: function() {
|
||||
var sequence = [true, false, true, true, false];
|
||||
var index = 0;
|
||||
|
||||
|
||||
var b = Blob(5, function() {
|
||||
return sequence[index++] ? 1 : 0;
|
||||
});
|
||||
|
||||
|
||||
stone(b);
|
||||
for (var i = 0; i < 5; i++) {
|
||||
var r = assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence")
|
||||
if (r) return r
|
||||
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
test_write_pad_and_check_padding: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_bit(false);
|
||||
b.write_bit(true);
|
||||
b.write_pad(8);
|
||||
|
||||
var r = assertEqual(length(b), 8, "Should be padded to 8 bits")
|
||||
if (r) return r
|
||||
|
||||
assertEqual(length(b), 8, "Should be padded to 8 bits");
|
||||
stone(b);
|
||||
|
||||
r = assert(b['pad?'](3, 8), "Should detect valid padding at position 3")
|
||||
if (r) return r
|
||||
return assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2")
|
||||
|
||||
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
|
||||
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2");
|
||||
},
|
||||
|
||||
|
||||
test_read_blob_from_stone_blob: function() {
|
||||
var b1 = Blob();
|
||||
for (var i = 0; i < 16; i++) {
|
||||
b1.write_bit(i % 3 == 0);
|
||||
}
|
||||
stone(b1);
|
||||
|
||||
|
||||
var b2 = b1.read_blob(4, 12);
|
||||
stone(b2);
|
||||
var r = assertEqual(length(b2), 8, "Read blob should have 8 bits")
|
||||
if (r) return r
|
||||
|
||||
r = assertEqual(b2.read_logical(2), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(5), true)
|
||||
assertEqual(length(b2), 8, "Read blob should have 8 bits");
|
||||
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
assertEqual(b2.read_logical(5), true);
|
||||
},
|
||||
|
||||
|
||||
test_stone_blob_is_immutable: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
if (!should_disrupt(function() { b.write_bit(false) }))
|
||||
return "Writing to stone blob should disrupt"
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.write_bit(false);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Writing to stone blob should throw error");
|
||||
},
|
||||
|
||||
|
||||
test_multiple_stone_calls_are_safe: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
var r = assert(!stone.p(b), "Blob should not be a stone before stone() call")
|
||||
if (r) return r
|
||||
assert(!stone.p(b), "Blob should not be a stone before stone() call");
|
||||
stone(b);
|
||||
r = assert(stone.p(b), "Blob should be a stone after stone() call")
|
||||
if (r) return r
|
||||
assert(stone.p(b), "Blob should be a stone after stone() call");
|
||||
stone(b);
|
||||
r = assertEqual(b.read_logical(0), true, "Blob data should remain intact")
|
||||
if (r) return r
|
||||
|
||||
return assert(b.stone == null, "blob.stone should not be available as a method")
|
||||
assertEqual(b.read_logical(0), true, "Blob data should remain intact");
|
||||
|
||||
assert(b.stone == null, "blob.stone should not be available as a method");
|
||||
},
|
||||
|
||||
|
||||
test_invalid_constructor_arguments: function() {
|
||||
if (!should_disrupt(function() { Blob("invalid") }))
|
||||
return "Invalid constructor arguments should disrupt"
|
||||
var threw = false;
|
||||
try {
|
||||
var b = Blob("invalid");
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Invalid constructor arguments should throw");
|
||||
},
|
||||
|
||||
|
||||
test_write_bit_validation: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(0);
|
||||
b.write_bit(1);
|
||||
|
||||
if (!should_disrupt(function() { b.write_bit(2) }))
|
||||
return "write_bit with value 2 should disrupt"
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.write_bit(2);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "write_bit with value 2 should throw");
|
||||
},
|
||||
|
||||
|
||||
test_complex_data_round_trip: function() {
|
||||
var b = Blob();
|
||||
|
||||
|
||||
b.write_text("Test");
|
||||
b.write_number(123.456);
|
||||
b.write_bit(true);
|
||||
b.write_bit(false);
|
||||
b.write_number(-999.999);
|
||||
|
||||
|
||||
var originalLength = length(b);
|
||||
stone(b);
|
||||
|
||||
|
||||
var b2 = Blob(b);
|
||||
stone(b2);
|
||||
var r = assertEqual(length(b2), originalLength, "Copy should have same length")
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_text(0), "Test", "First text should match")
|
||||
assertEqual(length(b2), originalLength, "Copy should have same length");
|
||||
assertEqual(b2.read_text(0), "Test", "First text should match");
|
||||
},
|
||||
|
||||
|
||||
test_zero_capacity_blob: function() {
|
||||
var b = Blob(0);
|
||||
var r = assertEqual(length(b), 0, "Zero capacity blob should have length 0")
|
||||
if (r) return r
|
||||
assertEqual(length(b), 0, "Zero capacity blob should have length 0");
|
||||
b.write_bit(true);
|
||||
return assertEqual(length(b), 1, "Should expand when writing")
|
||||
assertEqual(length(b), 1, "Should expand when writing");
|
||||
},
|
||||
|
||||
|
||||
test_large_blob_handling: function() {
|
||||
var b = Blob();
|
||||
var testSize = 1000;
|
||||
|
||||
|
||||
for (var i = 0; i < testSize; i++) {
|
||||
b.write_bit(i % 7 == 0);
|
||||
}
|
||||
|
||||
var r = assertEqual(length(b), testSize, "Should have " + testSize + " bits")
|
||||
if (r) return r
|
||||
|
||||
assertEqual(length(b), testSize, "Should have " + testSize + " bits");
|
||||
stone(b);
|
||||
|
||||
r = assertEqual(b.read_logical(0), true, "Bit 0 should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(7), true, "Bit 7 should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(14), true, "Bit 14 should be true")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_logical(15), false, "Bit 15 should be false")
|
||||
|
||||
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
|
||||
assertEqual(b.read_logical(7), true, "Bit 7 should be true");
|
||||
assertEqual(b.read_logical(14), true, "Bit 14 should be true");
|
||||
assertEqual(b.read_logical(15), false, "Bit 15 should be false");
|
||||
},
|
||||
|
||||
|
||||
test_non_stone_blob_read_methods_should_throw: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_number(42);
|
||||
b.write_text("test");
|
||||
|
||||
if (!should_disrupt(function() { b.read_logical(0) }))
|
||||
return "read_logical on non-stone blob should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b.read_number(0) }))
|
||||
return "read_number on non-stone blob should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b.read_text(0) }))
|
||||
return "read_text on non-stone blob should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b.read_blob(0, 10) }))
|
||||
return "read_blob on non-stone blob should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b['pad?'](0, 8) }))
|
||||
return "pad? on non-stone blob should disrupt"
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_logical(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_logical on non-stone blob should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_number(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_number on non-stone blob should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_text(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_text on non-stone blob should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_blob(0, 10);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_blob on non-stone blob should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b['pad?'](0, 8);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "pad? on non-stone blob should throw");
|
||||
},
|
||||
|
||||
|
||||
test_empty_text_write_and_read: function() {
|
||||
var b = Blob();
|
||||
b.write_text("");
|
||||
stone(b);
|
||||
return assertEqual(b.read_text(0), "", "Empty string should round-trip")
|
||||
assertEqual(b.read_text(0), "", "Empty string should round-trip");
|
||||
},
|
||||
|
||||
|
||||
test_invalid_read_positions: function() {
|
||||
var b = Blob();
|
||||
b.write_number(42);
|
||||
stone(b);
|
||||
|
||||
if (!should_disrupt(function() { b.read_number(-10) }))
|
||||
return "Negative position should disrupt"
|
||||
|
||||
if (!should_disrupt(function() { b.read_number(1000) }))
|
||||
return "Position beyond length should disrupt"
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_number(-10);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Negative position should throw");
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_number(1000);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Position beyond length should throw");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
return {
|
||||
test_disrupt: function() {
|
||||
disrupt
|
||||
throw 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,101 +2,85 @@ var fd = use("fd")
|
||||
var miniz = use("miniz")
|
||||
var utf8 = use("utf8")
|
||||
|
||||
function safe_unlink(p) { fd.unlink(p) } disruption {}
|
||||
|
||||
return {
|
||||
create_and_read_zip: function() {
|
||||
var ZIP_PATH = "miniz_test.zip"
|
||||
var SOURCE_PATH = "miniz_source.txt"
|
||||
var ENTRY_PATH = "sample/hello.txt"
|
||||
var PAYLOAD = "Miniz integration test payload."
|
||||
|
||||
|
||||
function write_text_file(path, text) {
|
||||
var handle = fd.open(path, "w")
|
||||
fd.write(handle, text)
|
||||
fd.close(handle)
|
||||
}
|
||||
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
|
||||
try {
|
||||
write_text_file(SOURCE_PATH, PAYLOAD)
|
||||
var source_blob = fd.slurp(SOURCE_PATH)
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
writer.add_file(ENTRY_PATH, source_blob)
|
||||
writer = null
|
||||
|
||||
|
||||
var zip_blob = fd.slurp(ZIP_PATH)
|
||||
var reader = miniz.read(zip_blob)
|
||||
|
||||
|
||||
if (!reader.exists(ENTRY_PATH))
|
||||
error_msg = "entry missing in archive"
|
||||
|
||||
if (!error_msg) {
|
||||
var extracted_blob = reader.slurp(ENTRY_PATH)
|
||||
var extracted_text = utf8.decode(extracted_blob)
|
||||
|
||||
if (extracted_text != PAYLOAD)
|
||||
error_msg = "extracted text mismatch"
|
||||
}
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
throw "entry missing in archive"
|
||||
|
||||
var extracted_blob = reader.slurp(ENTRY_PATH)
|
||||
var extracted_text = utf8.decode(extracted_blob)
|
||||
|
||||
if (extracted_text != PAYLOAD)
|
||||
throw "extracted text mismatch"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
try { fd.unlink(SOURCE_PATH) } catch(e) {}
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
safe_unlink(SOURCE_PATH)
|
||||
if (error_msg) return error_msg
|
||||
},
|
||||
|
||||
|
||||
list_and_count: function() {
|
||||
var ZIP_PATH = "miniz_list_test.zip"
|
||||
var ENTRY1 = "file1.txt"
|
||||
var ENTRY2 = "dir/file2.txt"
|
||||
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
|
||||
try {
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
writer.add_file(ENTRY1, utf8.encode("content1"))
|
||||
writer.add_file(ENTRY2, utf8.encode("content2"))
|
||||
writer = null
|
||||
|
||||
|
||||
var zip_blob = fd.slurp(ZIP_PATH)
|
||||
var reader = miniz.read(zip_blob)
|
||||
|
||||
|
||||
var listed = reader.list()
|
||||
if (length(listed) != reader.count())
|
||||
error_msg = "list/count mismatch"
|
||||
if (!error_msg && length(listed) != 2)
|
||||
error_msg = "unexpected entry count"
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
throw "list/count mismatch"
|
||||
if (length(listed) != 2)
|
||||
throw "unexpected entry count"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
if (error_msg) return error_msg
|
||||
},
|
||||
|
||||
|
||||
exists_check: function() {
|
||||
var ZIP_PATH = "miniz_exists_test.zip"
|
||||
var ENTRY_PATH = "existing.txt"
|
||||
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
|
||||
try {
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
writer.add_file(ENTRY_PATH, utf8.encode("data"))
|
||||
writer = null
|
||||
|
||||
|
||||
var zip_blob = fd.slurp(ZIP_PATH)
|
||||
var reader = miniz.read(zip_blob)
|
||||
|
||||
|
||||
if (!reader.exists(ENTRY_PATH))
|
||||
error_msg = "existing entry not found"
|
||||
if (!error_msg && reader.exists("nonexistent.txt"))
|
||||
error_msg = "nonexistent entry reported as existing"
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
throw "existing entry not found"
|
||||
if (reader.exists("nonexistent.txt"))
|
||||
throw "nonexistent entry reported as existing"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
if (error_msg) return error_msg
|
||||
}
|
||||
}
|
||||
|
||||
1820
tests/suite.cm
1820
tests/suite.cm
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
var cmds = {
|
||||
stop: $stop,
|
||||
disrupt: _ => {
|
||||
$delay(_ => { disrupt }, 0.5)
|
||||
$delay(_ => { throw Error() }, 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
tests/use.cm
11
tests/use.cm
@@ -1,10 +1,5 @@
|
||||
var load_disrupted = false
|
||||
var do_load = function() {
|
||||
try {
|
||||
var u = use('tests/use')
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console("use self-load disrupted")
|
||||
} catch(e) {
|
||||
log.console(e)
|
||||
}
|
||||
|
||||
202
vm_suite.ce
202
vm_suite.ce
@@ -4,33 +4,27 @@
|
||||
|
||||
var passed = 0
|
||||
var failed = 0
|
||||
var error_names = []
|
||||
var error_reasons = []
|
||||
var errors = []
|
||||
var fail_msg = ""
|
||||
|
||||
// pre-allocate 500 slots to avoid array growth during disruption handlers
|
||||
for (var _i = 0; _i < 5; _i++) {
|
||||
error_names[] = null
|
||||
error_reasons[] = null
|
||||
}
|
||||
|
||||
var fail = function(msg) {
|
||||
fail_msg = msg
|
||||
print("failed: " + msg)
|
||||
disrupt
|
||||
}
|
||||
|
||||
var assert_eq = function(actual, expected, msg) {
|
||||
if (actual != expected) fail(msg + " (got=" + text(actual) + ")")
|
||||
if (actual != expected) fail(msg + " (expected=" + text(expected) + " got=" + text(actual) + ")")
|
||||
}
|
||||
|
||||
var run = function(name, fn) {
|
||||
fail_msg = ""
|
||||
fn()
|
||||
passed = passed + 1
|
||||
print("passed " + name)
|
||||
} disruption {
|
||||
error_names[failed] = name
|
||||
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
|
||||
failed = failed + 1
|
||||
errors[] = name
|
||||
}
|
||||
|
||||
var should_disrupt = function(fn) {
|
||||
@@ -3369,190 +3363,14 @@ run("gc object from keys function under pressure", function() {
|
||||
}
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// DEFAULT PARAMETER TESTS
|
||||
// ============================================================================
|
||||
|
||||
run("default param constant", function() {
|
||||
var f = function(a, b=10) { return a + b }
|
||||
assert_eq(f(1), 11, "default param constant")
|
||||
})
|
||||
|
||||
run("default param overridden", function() {
|
||||
var f = function(a, b=10) { return a + b }
|
||||
assert_eq(f(1, 2), 3, "default param overridden")
|
||||
})
|
||||
|
||||
run("default param uses earlier param", function() {
|
||||
var f = function(a, b=a+1) { return b }
|
||||
assert_eq(f(5), 6, "default param uses earlier param")
|
||||
})
|
||||
|
||||
run("default param uses earlier param overridden", function() {
|
||||
var f = function(a, b=a+1) { return b }
|
||||
assert_eq(f(5, 20), 20, "default param uses earlier param overridden")
|
||||
})
|
||||
|
||||
run("multiple default params", function() {
|
||||
var f = function(a, b=10, c=a+1) { return a + b + c }
|
||||
assert_eq(f(1), 13, "multiple defaults f(1)")
|
||||
assert_eq(f(1, 2), 5, "multiple defaults f(1,2)")
|
||||
assert_eq(f(1, 2, 3), 6, "multiple defaults f(1,2,3)")
|
||||
})
|
||||
|
||||
run("arrow function default param", function() {
|
||||
var g = (x, y=100) => x + y
|
||||
assert_eq(g(5), 105, "arrow default param")
|
||||
assert_eq(g(5, 20), 25, "arrow default param overridden")
|
||||
})
|
||||
|
||||
run("default param null passed explicitly", function() {
|
||||
var f = function(a, b=10) { return b }
|
||||
assert_eq(f(1, null), 10, "explicit null triggers default")
|
||||
})
|
||||
|
||||
run("default param with string", function() {
|
||||
var f = function(a, b="hello") { return b }
|
||||
assert_eq(f(1), "hello", "default string param")
|
||||
assert_eq(f(1, "world"), "world", "default string overridden")
|
||||
})
|
||||
|
||||
run("default param first param has no default", function() {
|
||||
var f = function(a, b=42) { return a }
|
||||
assert_eq(f(7), 7, "first param no default")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// FUNCTINO TESTS
|
||||
// ============================================================================
|
||||
|
||||
run("functino +! addition", function() {
|
||||
assert_eq(+!(3, 4), 7, "+! addition")
|
||||
})
|
||||
|
||||
run("functino -! subtraction", function() {
|
||||
assert_eq(-!(10, 3), 7, "-! subtraction")
|
||||
})
|
||||
|
||||
run("functino *! multiplication", function() {
|
||||
assert_eq(*!(5, 6), 30, "*! multiplication")
|
||||
})
|
||||
|
||||
run("functino /! division", function() {
|
||||
assert_eq(/!(10, 2), 5, "/! division")
|
||||
})
|
||||
|
||||
run("functino %! modulo", function() {
|
||||
assert_eq(%!(10, 3), 1, "%! modulo")
|
||||
})
|
||||
|
||||
run("functino **! power", function() {
|
||||
assert_eq(**!(2, 10), 1024, "**! power")
|
||||
})
|
||||
|
||||
run("functino <! less than", function() {
|
||||
assert_eq(<!(3, 5), true, "<! true")
|
||||
assert_eq(<!(5, 3), false, "<! false")
|
||||
assert_eq(<!(3, 3), false, "<! equal")
|
||||
})
|
||||
|
||||
run("functino >! greater than", function() {
|
||||
assert_eq(>!(5, 3), true, ">! true")
|
||||
assert_eq(>!(3, 5), false, ">! false")
|
||||
assert_eq(>!(3, 3), false, ">! equal")
|
||||
})
|
||||
|
||||
run("functino <=! less or equal", function() {
|
||||
assert_eq(<=!(3, 5), true, "<=! less")
|
||||
assert_eq(<=!(3, 3), true, "<=! equal")
|
||||
assert_eq(<=!(5, 3), false, "<=! greater")
|
||||
})
|
||||
|
||||
run("functino >=! greater or equal", function() {
|
||||
assert_eq(>=!(5, 3), true, ">=! greater")
|
||||
assert_eq(>=!(3, 3), true, ">=! equal")
|
||||
assert_eq(>=!(3, 5), false, ">=! less")
|
||||
})
|
||||
|
||||
run("functino =! equality", function() {
|
||||
assert_eq(=!(5, 5), true, "=! true")
|
||||
assert_eq(=!(5, 3), false, "=! false")
|
||||
})
|
||||
|
||||
run("functino !=! inequality", function() {
|
||||
assert_eq(!=!(5, 3), true, "!=! true")
|
||||
assert_eq(!=!(5, 5), false, "!=! false")
|
||||
})
|
||||
|
||||
run("functino =! with tolerance", function() {
|
||||
assert_eq(=!(1.0, 1.0001, 0.001), true, "=! within tolerance")
|
||||
assert_eq(=!(1.0, 1.01, 0.001), false, "=! outside tolerance")
|
||||
})
|
||||
|
||||
run("functino !=! with tolerance", function() {
|
||||
assert_eq(!=!(1.0, 1.01, 0.001), true, "!=! outside tolerance")
|
||||
assert_eq(!=!(1.0, 1.0001, 0.001), false, "!=! within tolerance")
|
||||
})
|
||||
|
||||
run("functino &! bitwise and", function() {
|
||||
assert_eq(&!(0xff, 0x0f), 0x0f, "&! bitwise and")
|
||||
})
|
||||
|
||||
run("functino |! bitwise or", function() {
|
||||
assert_eq(|!(0xf0, 0x0f), 0xff, "|! bitwise or")
|
||||
})
|
||||
|
||||
run("functino ^! bitwise xor", function() {
|
||||
assert_eq(^!(0xff, 0x0f), 0xf0, "^! bitwise xor")
|
||||
})
|
||||
|
||||
run("functino <<! shift left", function() {
|
||||
assert_eq(<<!(1, 4), 16, "<<! shift left")
|
||||
})
|
||||
|
||||
run("functino >>! shift right", function() {
|
||||
assert_eq(>>!(16, 4), 1, ">>! shift right")
|
||||
})
|
||||
|
||||
run("functino ~! bitwise not", function() {
|
||||
assert_eq(~!(0), -1, "~! bitwise not 0")
|
||||
})
|
||||
|
||||
run("functino []! array index", function() {
|
||||
var arr = [10, 20, 30]
|
||||
assert_eq([]!(arr, 0), 10, "[]! index 0")
|
||||
assert_eq([]!(arr, 1), 20, "[]! index 1")
|
||||
assert_eq([]!(arr, 2), 30, "[]! index 2")
|
||||
})
|
||||
|
||||
run("functino &&! logical and", function() {
|
||||
assert_eq(&&!(true, true), true, "&&! true true")
|
||||
assert_eq(&&!(true, false), false, "&&! true false")
|
||||
assert_eq(&&!(false, true), false, "&&! false true")
|
||||
assert_eq(&&!(1, 2), 2, "&&! truthy returns right")
|
||||
assert_eq(&&!(0, 2), 0, "&&! falsy returns left")
|
||||
})
|
||||
|
||||
run("functino ||! logical or", function() {
|
||||
assert_eq(||!(false, true), true, "||! false true")
|
||||
assert_eq(||!(true, false), true, "||! true false")
|
||||
assert_eq(||!(false, false), false, "||! false false")
|
||||
assert_eq(||!(0, 5), 5, "||! falsy returns right")
|
||||
assert_eq(||!(3, 5), 3, "||! truthy returns left")
|
||||
})
|
||||
|
||||
run("functino >>>! unsigned shift right", function() {
|
||||
assert_eq(>>>!(-1, 28), 15, ">>>! unsigned shift right")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// SUMMARY
|
||||
// ============================================================================
|
||||
|
||||
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
|
||||
print(`\nResults: ${passed} passed, ${failed} failed out of ${passed + failed}`)
|
||||
if (failed > 0) {
|
||||
print("")
|
||||
for (var _j = 0; _j < failed; _j++) {
|
||||
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
|
||||
}
|
||||
print("Failed tests:")
|
||||
arrfor(errors, function(name) {
|
||||
print(` - ${name}`)
|
||||
})
|
||||
}
|
||||
|
||||
40
website/content/_index.md
Normal file
40
website/content/_index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: "ƿit"
|
||||
---
|
||||
|
||||
```javascript
|
||||
// hello.ce — a simple actor
|
||||
log.console("Hello, ƿit!")
|
||||
$stop()
|
||||
```
|
||||
|
||||
```bash
|
||||
pit hello
|
||||
```
|
||||
|
||||
## Why ƿit
|
||||
|
||||
- **Actors, not threads** — isolated memory, message passing, no shared state. Concurrent programs that are safe by default.
|
||||
- **Everything is stoned** — `stone()` makes values permanently immutable. Messages between actors are frozen automatically. No defensive copying.
|
||||
- **Prototypes, not classes** — objects inherit directly from other objects. No class hierarchies, no `new`, no `this` confusion.
|
||||
- **C when you need it** — drop a `.c` file in your package and it becomes a native module. No FFI bindings, no build scripts.
|
||||
- **Small and predictable** — DEC64 numbers with no rounding errors. No `undefined`. Strict equality only. A runtime that fits in your head.
|
||||
|
||||
<div class="home-art">
|
||||
<img src="/images/wizard.png" alt="ƿit wizard">
|
||||
</div>
|
||||
|
||||
<div class="home-links">
|
||||
<a href="/start/">Get Started</a>
|
||||
<a href="/manual/">Language Manual</a>
|
||||
</div>
|
||||
|
||||
<div class="donate-section">
|
||||
|
||||
## Support ƿit
|
||||
|
||||
ƿit is free and open source. If you find it useful, consider supporting its development.
|
||||
|
||||
Donation options coming soon.
|
||||
|
||||
</div>
|
||||
175
website/content/cli/_index.md
Normal file
175
website/content/cli/_index.md
Normal file
@@ -0,0 +1,175 @@
|
||||
---
|
||||
title: "Command Line Interface"
|
||||
description: "The pit tool"
|
||||
type: "standalone"
|
||||
---
|
||||
|
||||
ƿit provides a command-line interface for managing packages, running scripts, and building applications.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```bash
|
||||
pit <command> [arguments]
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### pit version
|
||||
|
||||
Display the ƿit version.
|
||||
|
||||
```bash
|
||||
pit version
|
||||
# 0.1.0
|
||||
```
|
||||
|
||||
### pit install
|
||||
|
||||
Install a package to the shop.
|
||||
|
||||
```bash
|
||||
pit install gitea.pockle.world/john/prosperon
|
||||
pit install /Users/john/local/mypackage # local path
|
||||
```
|
||||
|
||||
### pit update
|
||||
|
||||
Update packages from remote sources.
|
||||
|
||||
```bash
|
||||
pit update # update all packages
|
||||
pit update <package> # update specific package
|
||||
```
|
||||
|
||||
### pit remove
|
||||
|
||||
Remove a package from the shop.
|
||||
|
||||
```bash
|
||||
pit remove gitea.pockle.world/john/oldpackage
|
||||
```
|
||||
|
||||
### pit list
|
||||
|
||||
List installed packages.
|
||||
|
||||
```bash
|
||||
pit list # list all installed packages
|
||||
pit list <package> # list dependencies of a package
|
||||
```
|
||||
|
||||
### pit ls
|
||||
|
||||
List modules and actors in a package.
|
||||
|
||||
```bash
|
||||
pit ls # list files in current project
|
||||
pit ls <package> # list files in specified package
|
||||
```
|
||||
|
||||
### pit build
|
||||
|
||||
Build the current package.
|
||||
|
||||
```bash
|
||||
pit build
|
||||
```
|
||||
|
||||
### pit test
|
||||
|
||||
Run tests.
|
||||
|
||||
```bash
|
||||
pit test # run tests in current package
|
||||
pit test all # run all tests
|
||||
pit test <package> # run tests in specific package
|
||||
```
|
||||
|
||||
### pit link
|
||||
|
||||
Manage local package links for development.
|
||||
|
||||
```bash
|
||||
pit link add <canonical> <local_path> # link a package
|
||||
pit link list # show all links
|
||||
pit link delete <canonical> # remove a link
|
||||
pit link clear # remove all links
|
||||
```
|
||||
|
||||
### pit fetch
|
||||
|
||||
Fetch package sources without extracting.
|
||||
|
||||
```bash
|
||||
pit fetch <package>
|
||||
```
|
||||
|
||||
### pit upgrade
|
||||
|
||||
Upgrade the ƿit installation itself.
|
||||
|
||||
```bash
|
||||
pit upgrade
|
||||
```
|
||||
|
||||
### pit clean
|
||||
|
||||
Clean build artifacts.
|
||||
|
||||
```bash
|
||||
pit clean
|
||||
```
|
||||
|
||||
### pit help
|
||||
|
||||
Display help information.
|
||||
|
||||
```bash
|
||||
pit help
|
||||
pit help <command>
|
||||
```
|
||||
|
||||
## Running Scripts
|
||||
|
||||
Any `.ce` file in the ƿit core can be run as a command:
|
||||
|
||||
```bash
|
||||
pit version # runs version.ce
|
||||
pit build # runs build.ce
|
||||
pit test # runs test.ce
|
||||
```
|
||||
|
||||
## Package Locators
|
||||
|
||||
Packages are identified by locators:
|
||||
|
||||
- **Remote**: `gitea.pockle.world/user/repo`
|
||||
- **Local**: `/absolute/path/to/package`
|
||||
|
||||
```bash
|
||||
pit install gitea.pockle.world/john/prosperon
|
||||
pit install /Users/john/work/mylib
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
ƿit stores its data in `~/.pit/`:
|
||||
|
||||
```
|
||||
~/.pit/
|
||||
├── packages/ # installed packages
|
||||
├── lib/ # compiled dynamic libraries
|
||||
├── build/ # build cache
|
||||
├── cache/ # downloaded archives
|
||||
├── lock.toml # installed package versions
|
||||
└── link.toml # local development links
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
ƿit reads the `HOME` environment variable to locate the shop directory.
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- `0` — Success
|
||||
- Non-zero — Error (check output for details)
|
||||
60
website/content/contributing/_index.md
Normal file
60
website/content/contributing/_index.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: "Contributing"
|
||||
description: "How to contribute to ƿit"
|
||||
type: "standalone"
|
||||
---
|
||||
|
||||
ƿit is developed openly. Contributions of all kinds are welcome.
|
||||
|
||||
## Report Bugs
|
||||
|
||||
Found a problem? Open an issue on the [ƿit issue tracker](https://gitea.pockle.world/john/cell/issues). Include:
|
||||
|
||||
- What you expected to happen
|
||||
- What actually happened
|
||||
- A minimal reproduction (a short `.ce` or `.cm` file)
|
||||
- Your platform and ƿit version (`pit version`)
|
||||
|
||||
## Submit Packages
|
||||
|
||||
Share your ƿit packages by hosting them on a Gitea instance. Any package with a valid `pit.toml` can be installed by others:
|
||||
|
||||
```bash
|
||||
pit install gitea.example.com/you/your-package
|
||||
```
|
||||
|
||||
See [Packages](/manual/#packages) for how to structure and publish packages.
|
||||
|
||||
## Contribute to the Runtime
|
||||
|
||||
The ƿit runtime is written in C. To build from source:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.pockle.world/john/cell
|
||||
cd cell
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- C code uses 2-space indentation
|
||||
- Functions and variables are `static` unless exported
|
||||
- No headers between files in the same package
|
||||
- Use `JS_NULL` / `JS_IsNull` — there is no `undefined`
|
||||
- Objects over classes; limit prototype usage
|
||||
|
||||
### Submitting Patches
|
||||
|
||||
1. Fork the repository on Gitea
|
||||
2. Create a branch for your change
|
||||
3. Keep commits focused — one logical change per commit
|
||||
4. Test your changes with `pit test all`
|
||||
5. Open a pull request with a clear description
|
||||
|
||||
## Improve Documentation
|
||||
|
||||
Documentation lives in the `docs/` directory as Markdown files. Fixes for typos, unclear explanations, or missing examples are always appreciated.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Be respectful. Communicate clearly. Assume good faith. Technical disagreements are fine; personal attacks are not.
|
||||
5
website/content/manual/_index.md
Normal file
5
website/content/manual/_index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Language Manual"
|
||||
description: "Complete ƿit language reference"
|
||||
type: "manual"
|
||||
---
|
||||
5
website/content/spec/_index.md
Normal file
5
website/content/spec/_index.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Language Specification"
|
||||
description: "ƿit internals for language implementers"
|
||||
type: "spec"
|
||||
---
|
||||
153
website/content/start/_index.md
Normal file
153
website/content/start/_index.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: "Getting Started"
|
||||
description: "Install ƿit and write your first program"
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A C compiler (gcc or clang)
|
||||
- [Meson](https://mesonbuild.com/) build system
|
||||
- Git
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://gitea.pockle.world/john/cell
|
||||
cd cell
|
||||
|
||||
# Bootstrap the build
|
||||
make bootstrap
|
||||
```
|
||||
|
||||
This compiles the ƿit runtime and installs the `pit` binary. The ƿit shop is created at `~/.pit/`.
|
||||
|
||||
Verify your installation:
|
||||
|
||||
```bash
|
||||
pit version
|
||||
```
|
||||
|
||||
## Hello World
|
||||
|
||||
Create a file called `hello.ce`:
|
||||
|
||||
```javascript
|
||||
// hello.ce
|
||||
log.console("Hello, ƿit!")
|
||||
$stop()
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
pit hello
|
||||
```
|
||||
|
||||
You should see `Hello, ƿit!` printed to the console.
|
||||
|
||||
Every `.ce` file is an **actor** — an independent unit of execution. The `$stop()` call tells the actor to shut down when it's done.
|
||||
|
||||
## A Counting Actor
|
||||
|
||||
Actors can schedule work over time. Create `counter.ce`:
|
||||
|
||||
```javascript
|
||||
// counter.ce
|
||||
var count = 0
|
||||
|
||||
$clock(function(dt) {
|
||||
count = count + 1
|
||||
log.console(`tick ${count}`)
|
||||
if (count >= 5) {
|
||||
$stop()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```bash
|
||||
pit counter
|
||||
```
|
||||
|
||||
The `$clock` intrinsic calls your function every tick. The actor runs until you stop it.
|
||||
|
||||
## Two Actors Talking
|
||||
|
||||
The power of ƿit is in actors communicating through messages. Create two files:
|
||||
|
||||
```javascript
|
||||
// greeter.ce
|
||||
$receiver(function(msg, reply) {
|
||||
reply({greeting: `Hello, ${msg.name}!`})
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
// main.ce
|
||||
$start(function(greeter) {
|
||||
$send(greeter, {name: "world"}, function(response) {
|
||||
log.console(response.greeting)
|
||||
$stop()
|
||||
})
|
||||
}, "greeter")
|
||||
```
|
||||
|
||||
```bash
|
||||
pit main
|
||||
```
|
||||
|
||||
`$start` launches a new actor. `$send` sends a message and provides a callback for the reply. Messages are automatically serialized — actors never share memory.
|
||||
|
||||
## Using Modules
|
||||
|
||||
Modules (`.cm` files) return a value that is cached and frozen. Create a module:
|
||||
|
||||
```javascript
|
||||
// math_helpers.cm
|
||||
function square(x) {
|
||||
return x * x
|
||||
}
|
||||
|
||||
function distance(x1, y1, x2, y2) {
|
||||
var math = use('math/radians')
|
||||
var dx = x2 - x1
|
||||
var dy = y2 - y1
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
return {
|
||||
square: square,
|
||||
distance: distance
|
||||
}
|
||||
```
|
||||
|
||||
Use it from an actor:
|
||||
|
||||
```javascript
|
||||
// calc.ce
|
||||
var helpers = use('math_helpers')
|
||||
|
||||
log.console(helpers.square(5)) // 25
|
||||
log.console(helpers.distance(0, 0, 3, 4)) // 5
|
||||
|
||||
$stop()
|
||||
```
|
||||
|
||||
## Creating a Package
|
||||
|
||||
To share code or manage dependencies, create a `pit.toml`:
|
||||
|
||||
```toml
|
||||
package = "myproject"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
|
||||
Your package can now use `pit build`, `pit test`, and install dependencies.
|
||||
|
||||
## What's Next
|
||||
|
||||
- [**Language Manual**](/manual/) — full syntax reference, actors, packages, standard library
|
||||
- [**CLI Reference**](/cli/) — all `pit` commands
|
||||
- [**Language Spec**](/spec/) — internals for implementers
|
||||
41
website/data/docs_nav.yaml
Normal file
41
website/data/docs_nav.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
sections:
|
||||
- title: "Language"
|
||||
pages:
|
||||
- title: "Language Reference"
|
||||
url: "/docs/language/"
|
||||
- title: "Actors and Modules"
|
||||
url: "/docs/actors/"
|
||||
- title: "Packages"
|
||||
url: "/docs/packages/"
|
||||
- title: "Tools"
|
||||
pages:
|
||||
- title: "CLI"
|
||||
url: "/docs/cli/"
|
||||
- title: "C Modules"
|
||||
url: "/docs/c-modules/"
|
||||
- title: "Reference"
|
||||
pages:
|
||||
- title: "Built-in Functions"
|
||||
url: "/docs/functions/"
|
||||
- title: "Standard Library"
|
||||
pages:
|
||||
- title: "Overview"
|
||||
url: "/docs/library/"
|
||||
- title: "text"
|
||||
url: "/docs/library/text/"
|
||||
- title: "number"
|
||||
url: "/docs/library/number/"
|
||||
- title: "array"
|
||||
url: "/docs/library/array/"
|
||||
- title: "object"
|
||||
url: "/docs/library/object/"
|
||||
- title: "blob"
|
||||
url: "/docs/library/blob/"
|
||||
- title: "time"
|
||||
url: "/docs/library/time/"
|
||||
- title: "math"
|
||||
url: "/docs/library/math/"
|
||||
- title: "json"
|
||||
url: "/docs/library/json/"
|
||||
- title: "random"
|
||||
url: "/docs/library/random/"
|
||||
58
website/data/manual_sections.yaml
Normal file
58
website/data/manual_sections.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
sections:
|
||||
- title: "Language Syntax"
|
||||
page: "/docs/language/"
|
||||
id: "language"
|
||||
- title: "Actors and Modules"
|
||||
page: "/docs/actors/"
|
||||
id: "actors"
|
||||
- title: "Requestors"
|
||||
page: "/docs/requestors/"
|
||||
id: "requestors"
|
||||
- title: "Packages"
|
||||
page: "/docs/packages/"
|
||||
id: "packages"
|
||||
- title: "Built-in Functions"
|
||||
page: "/docs/functions/"
|
||||
id: "functions"
|
||||
- title: "Standard Library"
|
||||
page: "/docs/library/"
|
||||
id: "library"
|
||||
- title: "text"
|
||||
page: "/docs/library/text/"
|
||||
id: "library-text"
|
||||
- title: "number"
|
||||
page: "/docs/library/number/"
|
||||
id: "library-number"
|
||||
- title: "array"
|
||||
page: "/docs/library/array/"
|
||||
id: "library-array"
|
||||
- title: "object"
|
||||
page: "/docs/library/object/"
|
||||
id: "library-object"
|
||||
- title: "blob"
|
||||
page: "/docs/library/blob/"
|
||||
id: "library-blob"
|
||||
- title: "time"
|
||||
page: "/docs/library/time/"
|
||||
id: "library-time"
|
||||
- title: "math"
|
||||
page: "/docs/library/math/"
|
||||
id: "library-math"
|
||||
- title: "json"
|
||||
page: "/docs/library/json/"
|
||||
id: "library-json"
|
||||
- title: "random"
|
||||
page: "/docs/library/random/"
|
||||
id: "library-random"
|
||||
- title: "Writing C Modules"
|
||||
page: "/docs/c-modules/"
|
||||
id: "c-modules"
|
||||
- title: "Kim Encoding"
|
||||
page: "/docs/kim/"
|
||||
id: "kim"
|
||||
- title: "Nota Format"
|
||||
page: "/docs/nota/"
|
||||
id: "nota"
|
||||
- title: "Wota Format"
|
||||
page: "/docs/wota/"
|
||||
id: "wota"
|
||||
25
website/data/spec_sections.yaml
Normal file
25
website/data/spec_sections.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
sections:
|
||||
- title: "DEC64 Numbers"
|
||||
page: "/docs/spec/dec64/"
|
||||
id: "dec64"
|
||||
- title: "Value Representation"
|
||||
page: "/docs/spec/values/"
|
||||
id: "values"
|
||||
- title: "Object Types"
|
||||
page: "/docs/spec/objects/"
|
||||
id: "objects"
|
||||
- title: "Stone Memory"
|
||||
page: "/docs/spec/stone/"
|
||||
id: "stone"
|
||||
- title: "Garbage Collection"
|
||||
page: "/docs/spec/gc/"
|
||||
id: "gc"
|
||||
- title: "Bytecode VM"
|
||||
page: "/docs/spec/bytecode/"
|
||||
id: "bytecode"
|
||||
- title: "Register VM"
|
||||
page: "/docs/spec/mach/"
|
||||
id: "mach"
|
||||
- title: "Mcode IR"
|
||||
page: "/docs/spec/mcode/"
|
||||
id: "mcode"
|
||||
45
website/hugo.toml
Normal file
45
website/hugo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
baseURL = 'https://pit-lang.org/'
|
||||
languageCode = 'en-us'
|
||||
title = 'ƿit'
|
||||
theme = 'knr'
|
||||
|
||||
[markup]
|
||||
[markup.highlight]
|
||||
noClasses = false
|
||||
style = 'monokailight'
|
||||
[markup.goldmark]
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
[menus]
|
||||
[[menus.main]]
|
||||
name = 'Getting Started'
|
||||
pageRef = '/start/'
|
||||
weight = 10
|
||||
[[menus.main]]
|
||||
name = 'Manual'
|
||||
pageRef = '/manual/'
|
||||
weight = 20
|
||||
[[menus.main]]
|
||||
name = 'Spec'
|
||||
pageRef = '/spec/'
|
||||
weight = 30
|
||||
[[menus.main]]
|
||||
name = 'CLI'
|
||||
pageRef = '/cli/'
|
||||
weight = 40
|
||||
[[menus.main]]
|
||||
name = 'Contributing'
|
||||
pageRef = '/contributing/'
|
||||
weight = 50
|
||||
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "content"
|
||||
target = "content"
|
||||
[[module.mounts]]
|
||||
source = "../docs"
|
||||
target = "content/docs"
|
||||
|
||||
[params]
|
||||
description = 'An actor-based scripting language for building concurrent applications.'
|
||||
@@ -1,6 +0,0 @@
|
||||
site_name: Cell
|
||||
docs_dir: ../docs
|
||||
|
||||
theme:
|
||||
name: mkdocs
|
||||
color_mode: auto
|
||||
@@ -1 +0,0 @@
|
||||
mkdocs==1.6.1
|
||||
13
website/static/_headers
Normal file
13
website/static/_headers
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
Referrer-Policy: strict-origin-when-cross-origin
|
||||
|
||||
/fonts/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
/images/*
|
||||
Cache-Control: public, max-age=86400
|
||||
|
||||
/css/*
|
||||
Cache-Control: public, max-age=86400
|
||||
8
website/static/_redirects
Normal file
8
website/static/_redirects
Normal file
@@ -0,0 +1,8 @@
|
||||
/docs/ /manual/ 301
|
||||
/docs/language/ /manual/#language 301
|
||||
/docs/actors/ /manual/#actors 301
|
||||
/docs/packages/ /manual/#packages 301
|
||||
/docs/functions/ /manual/#functions 301
|
||||
/docs/library/ /manual/#library 301
|
||||
/docs/cli/ /cli/ 301
|
||||
/docs/c-modules/ /manual/#c-modules 301
|
||||
BIN
website/static/images/crab.png
Normal file
BIN
website/static/images/crab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
website/static/images/favicon.gif
Normal file
BIN
website/static/images/favicon.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
website/static/images/orb.gif
Normal file
BIN
website/static/images/orb.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
website/static/images/wizard.png
Normal file
BIN
website/static/images/wizard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 379 KiB |
13
website/themes/knr/layouts/_default/baseof.html
Normal file
13
website/themes/knr/layouts/_default/baseof.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.LanguageCode }}">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
<body>
|
||||
{{ partial "header.html" . }}
|
||||
<main>
|
||||
{{ block "main" . }}{{ end }}
|
||||
</main>
|
||||
{{ partial "footer.html" . }}
|
||||
</body>
|
||||
</html>
|
||||
6
website/themes/knr/layouts/_default/list.html
Normal file
6
website/themes/knr/layouts/_default/list.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<article class="content-single">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{ end }}
|
||||
6
website/themes/knr/layouts/_default/single.html
Normal file
6
website/themes/knr/layouts/_default/single.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<article class="content-single">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{ end }}
|
||||
9
website/themes/knr/layouts/docs/list.html
Normal file
9
website/themes/knr/layouts/docs/list.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{ define "main" }}
|
||||
<div class="docs-layout">
|
||||
{{ partial "nav-docs.html" . }}
|
||||
<article class="docs-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
9
website/themes/knr/layouts/docs/single.html
Normal file
9
website/themes/knr/layouts/docs/single.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{{ define "main" }}
|
||||
<div class="docs-layout">
|
||||
{{ partial "nav-docs.html" . }}
|
||||
<article class="docs-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
||||
13
website/themes/knr/layouts/index.html
Normal file
13
website/themes/knr/layouts/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{ define "main" }}
|
||||
<div class="home">
|
||||
<div class="hero">
|
||||
<div class="wynn">ƿ</div>
|
||||
<h1 class="hero-title">ƿit</h1>
|
||||
<p class="hero-tagline">An actor-based language for building concurrent applications.</p>
|
||||
</div>
|
||||
|
||||
<div class="home-content">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
19
website/themes/knr/layouts/manual/list.html
Normal file
19
website/themes/knr/layouts/manual/list.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{ define "main" }}
|
||||
<div class="longform-layout">
|
||||
<nav class="toc-nav" id="toc-nav">
|
||||
<h3>Contents</h3>
|
||||
<ul id="toc-list"></ul>
|
||||
</nav>
|
||||
<article class="longform-content" id="longform-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ range .Site.Data.manual_sections.sections }}
|
||||
<section id="{{ .id }}" data-toc-title="{{ .title }}">
|
||||
{{ with $.Site.GetPage .page }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
</article>
|
||||
</div>
|
||||
<script src="/js/toc.js"></script>
|
||||
{{ end }}
|
||||
3
website/themes/knr/layouts/partials/footer.html
Normal file
3
website/themes/knr/layouts/partials/footer.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<footer class="site-footer">
|
||||
<p>ƿit — an actor-based scripting language</p>
|
||||
</footer>
|
||||
9
website/themes/knr/layouts/partials/head.html
Normal file
9
website/themes/knr/layouts/partials/head.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} — {{ .Site.Title }}{{ end }}</title>
|
||||
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
|
||||
<link rel="icon" href="/images/favicon.gif" type="image/gif">
|
||||
<link rel="preload" href="/fonts/charter-regular.woff" as="font" type="font/woff" crossorigin>
|
||||
<link rel="preload" href="/fonts/jetbrains-mono-regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<link rel="stylesheet" href="/css/syntax.css">
|
||||
10
website/themes/knr/layouts/partials/header.html
Normal file
10
website/themes/knr/layouts/partials/header.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<header class="site-header">
|
||||
<nav class="site-nav">
|
||||
<a href="/" class="site-logo">ƿit</a>
|
||||
<div class="nav-links">
|
||||
{{ range .Site.Menus.main }}
|
||||
<a href="{{ .URL }}"{{ if $.IsMenuCurrent "main" . }} class="active"{{ end }}>{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
16
website/themes/knr/layouts/partials/nav-docs.html
Normal file
16
website/themes/knr/layouts/partials/nav-docs.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<nav class="docs-nav">
|
||||
<h3><a href="/docs/">Documentation</a></h3>
|
||||
{{ $current := . }}
|
||||
{{ range .Site.Data.docs_nav.sections }}
|
||||
<div class="nav-section">
|
||||
<h4>{{ .title }}</h4>
|
||||
<ul>
|
||||
{{ range .pages }}
|
||||
<li{{ if eq $current.RelPermalink .url }} class="active"{{ end }}>
|
||||
<a href="{{ .url }}">{{ .title }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
</nav>
|
||||
19
website/themes/knr/layouts/spec/list.html
Normal file
19
website/themes/knr/layouts/spec/list.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{ define "main" }}
|
||||
<div class="longform-layout">
|
||||
<nav class="toc-nav" id="toc-nav">
|
||||
<h3>Contents</h3>
|
||||
<ul id="toc-list"></ul>
|
||||
</nav>
|
||||
<article class="longform-content" id="longform-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ range .Site.Data.spec_sections.sections }}
|
||||
<section id="{{ .id }}" data-toc-title="{{ .title }}">
|
||||
{{ with $.Site.GetPage .page }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
</article>
|
||||
</div>
|
||||
<script src="/js/toc.js"></script>
|
||||
{{ end }}
|
||||
13
website/themes/knr/layouts/standalone/list.html
Normal file
13
website/themes/knr/layouts/standalone/list.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{ define "main" }}
|
||||
<div class="longform-layout">
|
||||
<nav class="toc-nav" id="toc-nav">
|
||||
<h3>Contents</h3>
|
||||
<ul id="toc-list"></ul>
|
||||
</nav>
|
||||
<article class="longform-content" id="longform-content">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
</div>
|
||||
<script src="/js/toc.js"></script>
|
||||
{{ end }}
|
||||
587
website/themes/knr/static/css/main.css
Normal file
587
website/themes/knr/static/css/main.css
Normal file
@@ -0,0 +1,587 @@
|
||||
/* K&R aesthetic — cream paper, serif fonts, sparse layout */
|
||||
|
||||
/* ---- Fonts ---- */
|
||||
/* TODO: Add Junicode woff2 for the ƿ glyph (psb1558/Junicode-font on GitHub) */
|
||||
@font-face {
|
||||
font-family: 'Charter';
|
||||
src: url('/fonts/charter-regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Charter';
|
||||
src: url('/fonts/charter-bold.woff') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('/fonts/jetbrains-mono-regular.woff2') format('woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* ---- Reset ---- */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, p, ul, ol, figure, blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ---- Base ---- */
|
||||
:root {
|
||||
--bg: #FAF8F1;
|
||||
--bg-code: #F2EFE4;
|
||||
--text: #2C2C2C;
|
||||
--text-secondary: #5C5C5C;
|
||||
--accent: #4A3728;
|
||||
--accent-hover: #2C2C2C;
|
||||
--border: #D4CFC4;
|
||||
--font-body: 'Charter', Georgia, 'Times New Roman', serif;
|
||||
--font-code: 'JetBrains Mono', 'Source Code Pro', 'Menlo', monospace;
|
||||
--font-wynn: 'Junicode', 'Charter', Georgia, serif;
|
||||
--content-width: 720px;
|
||||
--sidebar-width: 180px;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ---- Typography ---- */
|
||||
h1, h2, h3, h4 {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.3rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.15rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 1.5em;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
li > ul, li > ol {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid var(--border);
|
||||
padding-left: 1rem;
|
||||
color: var(--text-secondary);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ---- Code ---- */
|
||||
code {
|
||||
font-family: var(--font-code);
|
||||
font-size: 0.85em;
|
||||
background: var(--bg-code);
|
||||
padding: 0.15em 0.35em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
padding: 1rem 1.25rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0 1.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* ---- Tables ---- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0 1.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid var(--border);
|
||||
}
|
||||
|
||||
/* ---- Site Header ---- */
|
||||
.site-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
max-width: calc(var(--content-width) + var(--sidebar-width) + 2rem);
|
||||
margin: 0 auto;
|
||||
padding: 0.75rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.site-logo {
|
||||
font-family: var(--font-wynn);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-logo:hover {
|
||||
text-decoration: none;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nav-links a:hover,
|
||||
.nav-links a.active {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ---- Site Footer ---- */
|
||||
.site-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
/* ---- Content ---- */
|
||||
.content-single {
|
||||
max-width: var(--content-width);
|
||||
margin: 2rem auto;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
/* ---- Home Page ---- */
|
||||
.home {
|
||||
max-width: var(--content-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem 0 2rem;
|
||||
}
|
||||
|
||||
.wynn {
|
||||
font-family: var(--font-wynn);
|
||||
font-size: 18rem;
|
||||
line-height: 1;
|
||||
color: var(--text);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-family: var(--font-wynn);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.hero-tagline {
|
||||
font-size: 1.15rem;
|
||||
color: var(--text-secondary);
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.home-content {
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.home-content h2 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.home-content .home-links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.home-content .home-links a {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.home-art {
|
||||
text-align: center;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.home-art img {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
/* ---- Docs Layout ---- */
|
||||
.docs-layout {
|
||||
max-width: calc(var(--content-width) + var(--sidebar-width) + 3rem);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.docs-nav {
|
||||
width: var(--sidebar-width);
|
||||
flex-shrink: 0;
|
||||
padding-top: 2rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.docs-nav h3 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.docs-nav h3 a {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.docs-nav h4 {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.docs-nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.docs-nav li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.docs-nav li a {
|
||||
display: block;
|
||||
padding: 0.2rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.docs-nav li a:hover {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.docs-nav li.active a {
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.docs-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 2rem 0 3rem;
|
||||
}
|
||||
|
||||
.docs-content h1 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* ---- Responsive ---- */
|
||||
@media (max-width: 768px) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.wynn {
|
||||
font-size: 10rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.docs-nav {
|
||||
width: 100%;
|
||||
position: static;
|
||||
max-height: none;
|
||||
padding-top: 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: inline-block;
|
||||
margin-right: 1.5rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wynn {
|
||||
font-size: 7rem;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: block;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Longform Layout (Manual / Spec) ---- */
|
||||
.longform-layout {
|
||||
max-width: calc(var(--content-width) + var(--sidebar-width) + 3rem);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.longform-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 2rem 0 3rem;
|
||||
}
|
||||
|
||||
.longform-content h1 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.longform-content section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
width: var(--sidebar-width);
|
||||
flex-shrink: 0;
|
||||
padding-top: 2rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.toc-nav h3 {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.toc-nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-nav li.toc-section {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-nav li.toc-section > a {
|
||||
display: inline;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.toc-nav li.toc-section > a:hover {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toc-nav li.toc-section > a.active {
|
||||
color: var(--text);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.toc-arrow {
|
||||
display: inline-block;
|
||||
width: 0.7em;
|
||||
font-size: 0.65rem;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.toc-section.open > .toc-arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.toc-sub {
|
||||
display: none;
|
||||
padding-left: 1rem;
|
||||
margin: 0.1rem 0 0.2rem;
|
||||
}
|
||||
|
||||
.toc-section.open > .toc-sub {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toc-sub li a {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-secondary);
|
||||
padding: 0.05rem 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.toc-sub li a:hover {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ---- Donate Section ---- */
|
||||
.donate-section {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
padding: 1.5rem 2rem;
|
||||
text-align: center;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.donate-section h2 {
|
||||
border-bottom: none;
|
||||
margin-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.donate-section p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.longform-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
width: 100%;
|
||||
position: static;
|
||||
max-height: none;
|
||||
padding-top: 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
124
website/themes/knr/static/css/syntax.css
Normal file
124
website/themes/knr/static/css/syntax.css
Normal file
@@ -0,0 +1,124 @@
|
||||
/* Muted, near-monochrome syntax highlighting — K&R aesthetic */
|
||||
|
||||
/* Background & default text */
|
||||
.highlight pre {
|
||||
background: #F2EFE4;
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Keywords: bold, same color */
|
||||
.highlight .k,
|
||||
.highlight .kc,
|
||||
.highlight .kd,
|
||||
.highlight .kn,
|
||||
.highlight .kp,
|
||||
.highlight .kr,
|
||||
.highlight .kt {
|
||||
font-weight: bold;
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Strings: slightly lighter */
|
||||
.highlight .s,
|
||||
.highlight .s1,
|
||||
.highlight .s2,
|
||||
.highlight .sa,
|
||||
.highlight .sb,
|
||||
.highlight .sc,
|
||||
.highlight .dl,
|
||||
.highlight .se,
|
||||
.highlight .sh,
|
||||
.highlight .si,
|
||||
.highlight .sx {
|
||||
color: #5C5C5C;
|
||||
}
|
||||
|
||||
/* Comments: italic, lighter */
|
||||
.highlight .c,
|
||||
.highlight .c1,
|
||||
.highlight .ch,
|
||||
.highlight .cm,
|
||||
.highlight .cp,
|
||||
.highlight .cpf,
|
||||
.highlight .cs {
|
||||
color: #8B8B8B;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Numbers */
|
||||
.highlight .m,
|
||||
.highlight .mb,
|
||||
.highlight .mf,
|
||||
.highlight .mh,
|
||||
.highlight .mi,
|
||||
.highlight .mo {
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Functions */
|
||||
.highlight .nf,
|
||||
.highlight .fm {
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Operators */
|
||||
.highlight .o,
|
||||
.highlight .ow {
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Variables and names */
|
||||
.highlight .n,
|
||||
.highlight .na,
|
||||
.highlight .nb,
|
||||
.highlight .nc,
|
||||
.highlight .nd,
|
||||
.highlight .ne,
|
||||
.highlight .ni,
|
||||
.highlight .nl,
|
||||
.highlight .nn,
|
||||
.highlight .no,
|
||||
.highlight .nt,
|
||||
.highlight .nv,
|
||||
.highlight .bp {
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Punctuation */
|
||||
.highlight .p {
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
/* Template strings */
|
||||
.highlight .sa {
|
||||
color: #5C5C5C;
|
||||
}
|
||||
|
||||
/* Built-in constants */
|
||||
.highlight .kc {
|
||||
color: #2C2C2C;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Regular expressions */
|
||||
.highlight .sr {
|
||||
color: #5C5C5C;
|
||||
}
|
||||
|
||||
/* Errors */
|
||||
.highlight .err {
|
||||
color: #2C2C2C;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Line numbers */
|
||||
.highlight .ln {
|
||||
color: #8B8B8B;
|
||||
}
|
||||
|
||||
/* Generic */
|
||||
.highlight .gd { color: #5C5C5C; }
|
||||
.highlight .gi { color: #2C2C2C; }
|
||||
.highlight .ge { font-style: italic; }
|
||||
.highlight .gs { font-weight: bold; }
|
||||
BIN
website/themes/knr/static/fonts/charter-bold.woff
Normal file
BIN
website/themes/knr/static/fonts/charter-bold.woff
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user