update
This commit is contained in:
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.
|
||||||
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.
|
||||||
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
|
||||||
|
```
|
||||||
@@ -26,5 +26,15 @@ pit hello
|
|||||||
|
|
||||||
<div class="home-links">
|
<div class="home-links">
|
||||||
<a href="/start/">Get Started</a>
|
<a href="/start/">Get Started</a>
|
||||||
<a href="/docs/">Documentation</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>
|
</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"
|
||||||
|
---
|
||||||
@@ -148,7 +148,6 @@ Your package can now use `pit build`, `pit test`, and install dependencies.
|
|||||||
|
|
||||||
## What's Next
|
## What's Next
|
||||||
|
|
||||||
- [**ƿit Language**](/docs/language/) — full syntax reference
|
- [**Language Manual**](/manual/) — full syntax reference, actors, packages, standard library
|
||||||
- [**Actors and Modules**](/docs/actors/) — the execution model in depth
|
- [**CLI Reference**](/cli/) — all `pit` commands
|
||||||
- [**Packages**](/docs/packages/) — code organization and sharing
|
- [**Language Spec**](/spec/) — internals for implementers
|
||||||
- [**Standard Library**](/docs/library/) — built-in modules
|
|
||||||
|
|||||||
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"
|
||||||
@@ -17,9 +17,21 @@ theme = 'knr'
|
|||||||
pageRef = '/start/'
|
pageRef = '/start/'
|
||||||
weight = 10
|
weight = 10
|
||||||
[[menus.main]]
|
[[menus.main]]
|
||||||
name = 'Documentation'
|
name = 'Manual'
|
||||||
pageRef = '/docs/'
|
pageRef = '/manual/'
|
||||||
weight = 20
|
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]
|
||||||
[[module.mounts]]
|
[[module.mounts]]
|
||||||
|
|||||||
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
|
||||||
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 }}
|
||||||
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 }}
|
||||||
@@ -49,7 +49,7 @@ body, h1, h2, h3, h4, p, ul, ol, figure, blockquote {
|
|||||||
--font-code: 'JetBrains Mono', 'Source Code Pro', 'Menlo', monospace;
|
--font-code: 'JetBrains Mono', 'Source Code Pro', 'Menlo', monospace;
|
||||||
--font-wynn: 'Junicode', 'Charter', Georgia, serif;
|
--font-wynn: 'Junicode', 'Charter', Georgia, serif;
|
||||||
--content-width: 720px;
|
--content-width: 720px;
|
||||||
--sidebar-width: 220px;
|
--sidebar-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -444,3 +444,144 @@ th {
|
|||||||
margin-right: 0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
99
website/themes/knr/static/js/toc.js
Normal file
99
website/themes/knr/static/js/toc.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
(function() {
|
||||||
|
var container = document.getElementById('longform-content');
|
||||||
|
var tocList = document.getElementById('toc-list');
|
||||||
|
if (!container || !tocList) return;
|
||||||
|
|
||||||
|
var sections = container.querySelectorAll('section[id]');
|
||||||
|
var allLinks = [];
|
||||||
|
|
||||||
|
if (sections.length > 0) {
|
||||||
|
// composite page (manual, spec): section-level ToC with disclosure
|
||||||
|
sections.forEach(function(sec) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
li.className = 'toc-section';
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = '#' + sec.id;
|
||||||
|
a.textContent = sec.dataset.tocTitle || sec.id;
|
||||||
|
|
||||||
|
var h2s = sec.querySelectorAll('h2');
|
||||||
|
if (h2s.length > 0) {
|
||||||
|
var toggle = document.createElement('span');
|
||||||
|
toggle.className = 'toc-arrow';
|
||||||
|
toggle.textContent = '\u25B8';
|
||||||
|
li.appendChild(toggle);
|
||||||
|
li.appendChild(a);
|
||||||
|
|
||||||
|
var sub = document.createElement('ul');
|
||||||
|
sub.className = 'toc-sub';
|
||||||
|
h2s.forEach(function(h) {
|
||||||
|
if (!h.id) return;
|
||||||
|
var sli = document.createElement('li');
|
||||||
|
var sa = document.createElement('a');
|
||||||
|
sa.href = '#' + h.id;
|
||||||
|
sa.textContent = h.textContent;
|
||||||
|
sli.appendChild(sa);
|
||||||
|
sub.appendChild(sli);
|
||||||
|
});
|
||||||
|
li.appendChild(sub);
|
||||||
|
|
||||||
|
toggle.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
li.classList.toggle('open');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
li.appendChild(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
allLinks.push({el: sec, link: a, parent: li});
|
||||||
|
tocList.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
var currentSection = null;
|
||||||
|
var observer = new IntersectionObserver(function(entries) {
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
if (currentSection) currentSection.classList.remove('active');
|
||||||
|
var item = allLinks.find(function(i) { return i.el === entry.target; });
|
||||||
|
if (item) {
|
||||||
|
item.link.classList.add('active');
|
||||||
|
currentSection = item.link;
|
||||||
|
item.parent.classList.add('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {rootMargin: '0px 0px -70% 0px', threshold: 0});
|
||||||
|
|
||||||
|
sections.forEach(function(sec) { observer.observe(sec); });
|
||||||
|
} else {
|
||||||
|
// standalone page (cli, contributing): flat h2-only ToC
|
||||||
|
var headings = container.querySelectorAll('h2');
|
||||||
|
if (headings.length === 0) return;
|
||||||
|
|
||||||
|
headings.forEach(function(h) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
li.className = 'toc-section';
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = '#' + h.id;
|
||||||
|
a.textContent = h.textContent;
|
||||||
|
li.appendChild(a);
|
||||||
|
tocList.appendChild(li);
|
||||||
|
allLinks.push({el: h, link: a});
|
||||||
|
});
|
||||||
|
|
||||||
|
var current = null;
|
||||||
|
var obs = new IntersectionObserver(function(entries) {
|
||||||
|
entries.forEach(function(entry) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
if (current) current.classList.remove('active');
|
||||||
|
var item = allLinks.find(function(i) { return i.el === entry.target; });
|
||||||
|
if (item) {
|
||||||
|
item.link.classList.add('active');
|
||||||
|
current = item.link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {rootMargin: '0px 0px -70% 0px', threshold: 0});
|
||||||
|
|
||||||
|
headings.forEach(function(h) { obs.observe(h); });
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user