110 Commits
ast ... mqbe

Author SHA1 Message Date
John Alanbrook
b8b110b616 bootstrap with serialized mach 2026-02-09 22:54:42 -06:00
John Alanbrook
930dcfba36 Merge branch 'mach' into mqbe 2026-02-09 22:22:15 -06:00
John Alanbrook
eeccb3b34a bootstrap 2026-02-09 22:21:55 -06:00
John Alanbrook
407797881c bytecode serialization 2026-02-09 22:19:41 -06:00
John Alanbrook
7069475729 Merge branch 'pitweb' into mcode2 2026-02-09 20:33:56 -06:00
John Alanbrook
3e42c57479 rm tokenizer/parser/mcode generators from C 2026-02-09 20:05:50 -06:00
John Alanbrook
4b76728230 ast folding 2026-02-09 20:04:40 -06:00
John Alanbrook
4ff9332d38 lsp 2026-02-09 18:53:13 -06:00
John Alanbrook
27e852af5b Merge branch 'mach' into mqbe 2026-02-09 18:46:10 -06:00
John Alanbrook
66a44595c8 fix errors with mcode 2026-02-09 18:45:55 -06:00
John Alanbrook
fc0a1547dc Merge branch 'mach' into mqbe 2026-02-09 18:36:47 -06:00
John Alanbrook
c0b4e70eb2 fix two gc bugs 2026-02-09 18:32:41 -06:00
John Alanbrook
f4714b2b36 qbe macros 2026-02-09 18:17:31 -06:00
John Alanbrook
7f691fd52b fix mach vm suite errors 2026-02-09 18:12:44 -06:00
John Alanbrook
d5209e1d59 fix issues with parse.cm and tokenize.cm 2026-02-09 17:43:44 -06:00
John Alanbrook
68e2395b92 mcode generators 2026-02-09 17:01:39 -06:00
John Alanbrook
1b747720b7 fix regex parser error 2026-02-09 14:34:33 -06:00
John Alanbrook
849123d8fc streamlined cell running 2026-02-09 13:12:05 -06:00
John Alanbrook
6ad919624b Merge branch 'mcode2' into mach 2026-02-09 12:58:05 -06:00
John Alanbrook
a11f3e7d47 Merge branch 'pitweb' into mach 2026-02-09 12:57:01 -06:00
John Alanbrook
3d1fd37979 rm quickjs vm 2026-02-09 12:54:55 -06:00
John Alanbrook
8fc9bfe013 parse and tokenize modules 2026-02-09 12:19:05 -06:00
John Alanbrook
368511f666 parse.ce and tokenize.ce 2026-02-09 11:56:09 -06:00
John Alanbrook
3934cdb683 fix disrupts 2026-02-09 11:28:10 -06:00
John Alanbrook
45556c344d Merge branch 'pitweb' into mach 2026-02-09 11:17:45 -06:00
John Alanbrook
bc87fe5f70 string indexing 2026-02-09 11:17:42 -06:00
John Alanbrook
790293d915 Merge branch 'mach' into pitweb 2026-02-09 11:15:44 -06:00
John Alanbrook
872cd6ab51 more correct syntax and AI instructions 2026-02-09 11:00:23 -06:00
John Alanbrook
e04ab4c30c bootstrap 2026-02-09 10:56:15 -06:00
John Alanbrook
0503acb7e6 rm block scope 2026-02-09 10:11:22 -06:00
John Alanbrook
d0c68d7a7d Merge branch 'mcode2' into pitweb 2026-02-09 10:00:28 -06:00
John Alanbrook
7469383e66 refactor into multiple files 2026-02-08 16:32:14 -06:00
John Alanbrook
1fee8f9f8b condense jsruntime and jscontext 2026-02-08 10:10:42 -06:00
John Alanbrook
a4f3b025c5 update 2026-02-08 08:25:48 -06:00
John Alanbrook
d18ea1b330 update engine.cm 2026-02-08 08:24:49 -06:00
John Alanbrook
4de0659474 allow tokens as properties 2026-02-08 00:34:15 -06:00
John Alanbrook
27a9b72b07 functino tests; default args for mach and mcode 2026-02-08 00:31:18 -06:00
John Alanbrook
a3622bd5bd better parser error reporting 2026-02-08 00:23:47 -06:00
John Alanbrook
2f6700415e add functinos 2026-02-07 23:38:39 -06:00
John Alanbrook
243d92f7f3 rm ?? and .? 2026-02-07 22:09:40 -06:00
John Alanbrook
8f9d026b9b use casesensitive json 2026-02-07 17:01:11 -06:00
John Alanbrook
2c9ac8f7b6 no json roundtrip for mcode 2026-02-07 16:29:04 -06:00
John Alanbrook
80f24e131f all suite asan errors fixed for mcode 2026-02-07 16:15:58 -06:00
John Alanbrook
a8f8af7662 Merge branch 'mach' into mcode2 2026-02-07 15:49:38 -06:00
John Alanbrook
f5b3494762 memfree for mcode 2026-02-07 15:49:36 -06:00
John Alanbrook
13a6f6c79d faster mach bytecode generation 2026-02-07 15:49:09 -06:00
John Alanbrook
1a925371d3 faster parsing 2026-02-07 15:38:36 -06:00
John Alanbrook
08d2bacb1f improve ast parsing time 2026-02-07 15:22:18 -06:00
John Alanbrook
7322153e57 Merge branch 'mach' into mcode2 2026-02-07 14:53:41 -06:00
John Alanbrook
cc72c4cb0f fix mem errors for mcode 2026-02-07 14:53:35 -06:00
John Alanbrook
ae1f09a28f fix all memory errors 2026-02-07 14:53:14 -06:00
John Alanbrook
3c842912a1 gc fixing in mach vm 2026-02-07 14:25:04 -06:00
John Alanbrook
7cacf32078 Merge branch 'mach' into mcode2 2026-02-07 14:24:52 -06:00
John Alanbrook
b740612761 gc fixing in mach vm 2026-02-07 14:24:49 -06:00
John Alanbrook
6001c2b4bb Merge branch 'mach' into mcode2 2026-02-07 14:19:19 -06:00
John Alanbrook
98625fa15b mcode fix tests 2026-02-07 14:19:17 -06:00
John Alanbrook
87fafa44c8 fix last error 2026-02-07 13:43:13 -06:00
John Alanbrook
45ce76aef7 fixes 2026-02-07 12:50:46 -06:00
John Alanbrook
32fb44857c 1 test failing now 2026-02-07 12:50:26 -06:00
John Alanbrook
31d67f6710 fix vm suite tests 2026-02-07 12:34:18 -06:00
John Alanbrook
bae4e957e9 hugo website for pit 2026-02-07 12:01:58 -06:00
John Alanbrook
3621b1ef33 Merge branch 'mach' into mcode2 2026-02-07 11:53:44 -06:00
John Alanbrook
836227c8d3 fix mach proxy and templates 2026-02-07 11:53:39 -06:00
John Alanbrook
0ae59705d4 fix errors 2026-02-07 11:53:26 -06:00
John Alanbrook
8e2607b6ca Merge branch 'mcode2' into mach 2026-02-07 10:54:19 -06:00
John Alanbrook
dc73e86d8c handle mcode in callinternal 2026-02-07 10:51:45 -06:00
John Alanbrook
555cceb9d6 fixed text runner 2026-02-07 10:51:27 -06:00
John Alanbrook
fbb7933eb6 Merge branch 'mcode2' into mach 2026-02-07 10:40:20 -06:00
John Alanbrook
0287d6ada4 regex uses C strings now 2026-02-07 10:28:35 -06:00
John Alanbrook
73cd6a255d more test fixing 2026-02-07 07:59:52 -06:00
John Alanbrook
83ea67c01b Merge branch 'mach' into mcode2 2026-02-07 00:10:01 -06:00
John Alanbrook
16059cca4e fix tests 2026-02-07 00:09:58 -06:00
John Alanbrook
9ffe60ebef vm suite 2026-02-07 00:09:41 -06:00
John Alanbrook
2beafec5d9 fix tests 2026-02-07 00:09:21 -06:00
John Alanbrook
aba8eb66bd crash fixes 2026-02-06 23:38:56 -06:00
John Alanbrook
1abcaa92c7 Merge branch 'mach' into mcode2 2026-02-06 23:20:55 -06:00
John Alanbrook
168f7c71d5 fix text header chasing 2026-02-06 23:20:48 -06:00
John Alanbrook
56ed895b6e Merge branch 'mach' into mcode2 2026-02-06 23:15:38 -06:00
John Alanbrook
1e4646999d fix mach crashes 2026-02-06 23:15:33 -06:00
John Alanbrook
68d6c907fe fix mcode compilation 2026-02-06 23:13:13 -06:00
John Alanbrook
8150c64c7d pitcode 2026-02-06 22:58:21 -06:00
John Alanbrook
024d796ca4 add asan error vm stacktrace 2026-02-06 21:49:53 -06:00
John Alanbrook
ea185dbffd rm typeof 2026-02-06 21:26:45 -06:00
John Alanbrook
6571262af0 mach disrupt support 2026-02-06 21:09:18 -06:00
John Alanbrook
77ae133747 Merge branch 'mcode2' into mach 2026-02-06 20:45:57 -06:00
John Alanbrook
142a2d518b Merge branch 'stacktrace' into mach 2026-02-06 20:44:43 -06:00
John Alanbrook
5b65c64fe5 stack traces 2026-02-06 20:44:38 -06:00
John Alanbrook
e985fa5fe1 disrupt/disruption; remove try/catch 2026-02-06 18:40:56 -06:00
John Alanbrook
160ade2410 smarter gc malloc for large allocations 2026-02-06 18:38:23 -06:00
John Alanbrook
e2bc5948c1 fix functions and closures in mach 2026-02-06 18:30:26 -06:00
John Alanbrook
8cf98d8a9e Merge branch 'mcode2' into mach 2026-02-06 15:14:40 -06:00
John Alanbrook
3c38e828e5 context free tokenizing, parsing, compiling 2026-02-06 15:14:18 -06:00
John Alanbrook
af2d296f40 use new parser info 2026-02-06 12:45:25 -06:00
John Alanbrook
0a45394689 fix crash related to allocating in context heap 2026-02-06 12:43:19 -06:00
John Alanbrook
32885a422f bring in mcode 2026-02-06 04:24:14 -06:00
John Alanbrook
8959e53303 Merge branch 'newsyn' into mcode2 2026-02-06 03:55:56 -06:00
John Alanbrook
8a9a02b131 Merge branch 'newsyn' into mach 2026-02-06 03:54:38 -06:00
John Alanbrook
f9d68b2990 fix if/else, chained assignment 2026-02-06 03:54:25 -06:00
John Alanbrook
017a57b1eb use new parser information 2026-02-06 03:44:44 -06:00
John Alanbrook
ff8c68d01c mcode and mcode interpreter 2026-02-06 03:31:31 -06:00
John Alanbrook
9212003401 cannot set unbound 2026-02-06 03:24:01 -06:00
John Alanbrook
f9f8a4db42 Merge branch 'newsyn' into mach 2026-02-06 03:10:14 -06:00
John Alanbrook
8db95c654b more info in AST parser 2026-02-06 03:00:46 -06:00
John Alanbrook
63feabed5d mach vm 2026-02-06 02:50:48 -06:00
John Alanbrook
c814c0e1d8 rm new; rm void 2026-02-06 02:12:19 -06:00
John Alanbrook
bead0c48d4 Merge branch 'mcode' into newsyn 2026-02-06 02:02:46 -06:00
John Alanbrook
98dcab4ba7 comprehensive syntax test; fix multiple default args 2026-02-06 02:02:17 -06:00
John Alanbrook
ae44ce7b4b mcode and mach 2026-02-06 01:56:26 -06:00
John Alanbrook
1c38699b5a fix scope resolution 2026-02-06 01:41:03 -06:00
John Alanbrook
9a70a12d82 object literal 2026-02-05 21:41:34 -06:00
135 changed files with 38255 additions and 37476 deletions

4
.gitignore vendored
View File

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

134
CLAUDE.md
View File

@@ -1,25 +1,123 @@
# Code style
All code is done with 2 spaces for indentation.
# ƿit (pit) Language Project
For cell script and its integration files, objects are preferred over classes, and preferrably limited use of prototypes, make objects sendable between actors (.ce files).
## Building
## cell script format
Cell script files end in .ce or .cm. Cell script is similar to Javascript but with some differences.
Recompile after changes: `make`
Bootstrap from scratch (first time): `make bootstrap`
Run `cell --help` to see all CLI flags.
Variables are delcared with 'var'. Var behaves like let.
Constants are declared with 'def'.
!= and == are strict, there is no !== or ===.
There is no undefined, only null.
There are no classes, only objects and prototypes.
Prefer backticks for string interpolation. Otherwise, convering non strings with the text() function is required.
Everything should be lowercase.
## Code Style
There are no arraybuffers, only blobs, which work with bits. They must be stoned like stone(blob) before being read from.
All code uses 2 spaces for indentation. K&R style for C and Javascript.
## c format
For cell script integration files, everything should be declared static that can be. Most don't have headers at all. Files in a package are not shared between packages.
## ƿit Script Quick Reference
There is no undefined, so JS_IsNull and JS_NULL should be used only.
ƿit script files: `.ce` (actors) and `.cm` (modules). The syntax is similar to JavaScript with important differences listed below.
## how module loading is done in cell script
Within a package, a c file, if using the correct macros (CELL_USE_FUNCS etc), will be loaded as a module with its name; so png.c inside ac package is loaded as <package>/png, giving you access to its functions.
### Key Differences from JavaScript
- `var` (mutable) and `def` (constant) — no `let` or `const`
- `==` and `!=` are strict (no `===` or `!==`)
- No `undefined` — only `null`
- No classes — only objects and prototypes (`meme()`, `proto()`, `isa()`)
- No `for...in`, `for...of`, spread (`...`), rest params, or default params
- No named function declarations — use `var fn = function() {}` or arrow functions
- Variables must be declared at function body level only (not in if/while/for/blocks)
- All variables must be initialized at declaration (`var x` alone is an error; use `var x = null`)
- No `try`/`catch`/`throw` — use `disrupt`/`disruption`
- No arraybuffers — only `blob` (works with bits; must `stone(blob)` before reading)
- Identifiers can contain `?` and `!` (e.g., `nil?`, `set!`, `is?valid`)
- Prefer backticks for string interpolation; otherwise use `text()` to convert non-strings
- Everything should be lowercase
### Intrinsic Functions (always available, no `use()` needed)
The creator functions are **polymorphic** — behavior depends on argument types:
- `array(number)` — create array of size N filled with null
- `array(number, value_or_fn)` — create array with initial values
- `array(array)` — copy array
- `array(array, fn)` — map
- `array(array, array)` — concatenate
- `array(array, from, to)` — slice
- `array(record)` — get keys as array of text
- **`array(text)` — split text into individual characters** (e.g., `array("hello")``["h","e","l","l","o"]`)
- `array(text, separator)` — split by separator
- `array(text, length)` — split into chunks of length
- `text(array, separator)` — join array into text
- `text(number)` or `text(number, radix)` — number to text
- `text(text, from, to)` — substring
- `number(text)` or `number(text, radix)` — parse text to number
- `number(logical)` — boolean to number
- `record(record)` — copy
- `record(record, another)` — merge
- `record(array_of_keys)` — create record from keys
Other key intrinsics: `length()`, `stone()`, `is_stone()`, `print()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()`
Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc.
### Standard Library (loaded with `use()`)
- `blob` — binary data (bits, not bytes)
- `time` — time constants and conversions
- `math` — trig, logarithms, roots (`math/radians`, `math/turns`)
- `json` — JSON encoding/decoding
- `random` — random number generation
### Actor Model
- `.ce` files are actors (independent execution units, don't return values)
- `.cm` files are modules (return a value, cached and frozen)
- Actors never share memory; communicate via `$send()` message passing
- Actor intrinsics start with `$`: `$me`, `$stop()`, `$send()`, `$start()`, `$delay()`, `$receiver()`, `$clock()`, `$portal()`, `$contact()`, `$couple()`, `$unneeded()`, `$connection()`, `$time_limit()`
### Requestors (async composition)
`sequence()`, `parallel()`, `race()`, `fallback()` — compose asynchronous operations. See docs/requestors.md.
### Error Handling
```javascript
var fn = function() {
disrupt // bare keyword, no value
} disruption {
// handle error; can re-raise with disrupt
}
```
### Push/Pop Syntax
```javascript
var a = [1, 2]
a[] = 3 // push: [1, 2, 3]
var v = a[] // pop: v is 3, a is [1, 2]
```
## C Integration
- Declare everything `static` that can be
- Most files don't have headers; files in a package are not shared between packages
- No undefined in C API: use `JS_IsNull` and `JS_NULL` only
- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`)
## Project Layout
- `source/` — C source for the cell runtime and CLI
- `docs/` — master documentation (Markdown), reflected on the website
- `website/` — Hugo site; theme at `website/themes/knr/`
- `internal/` — internal ƿit scripts (engine.cm etc.)
- `packages/` — core packages
- `Makefile` — build system (`make` to rebuild, `make bootstrap` for first build)
## Documentation
The `docs/` folder is the single source of truth. The website at `website/` mounts it via Hugo. Key files:
- `docs/language.md` — language syntax reference
- `docs/functions.md` — all built-in intrinsic functions
- `docs/actors.md` — actor model and actor intrinsics
- `docs/requestors.md` — async requestor pattern
- `docs/library/*.md` — intrinsic type reference (text, number, array, object) and standard library modules

View File

@@ -58,7 +58,7 @@ static:
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap:
meson setup build_bootstrap -Dbuildtype=debug -Db_sanitize=address
meson setup build_bootstrap -Dbuildtype=debugoptimized
meson compile -C build_bootstrap
cp build_bootstrap/cell .
cp build_bootstrap/libcell_runtime.dylib .

View File

@@ -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_GetRuntime(js), js_reader_class_id, &js_reader_class);
JS_NewClass(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_GetRuntime(js), js_writer_class_id, &js_writer_class);
JS_NewClass(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);

41
bootstrap.ce Normal file
View File

@@ -0,0 +1,41 @@
// bootstrap.ce — regenerate .mach bytecode files consumed by the mach engine
// usage: cell bootstrap.ce
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var files = [
{src: "tokenize.cm", name: "tokenize", out: "tokenize.mach"},
{src: "parse.cm", name: "parse", out: "parse.mach"},
{src: "fold.cm", name: "fold", out: "fold.mach"},
{src: "mcode.cm", name: "mcode", out: "mcode.mach"},
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"}
]
var i = 0
var entry = null
var src = null
var tok_result = null
var ast = null
var folded = null
var ast_json = null
var bytecode = null
var f = null
while (i < length(files)) {
entry = files[i]
src = text(fd.slurp(entry.src))
tok_result = tokenize(src, entry.src)
ast = parse(tok_result.tokens, src, entry.src, tokenize)
folded = fold(ast)
ast_json = json.encode(folded)
bytecode = mach_compile_ast(entry.name, ast_json)
f = fd.open(entry.out, "w")
fd.write(f, bytecode)
fd.close(f)
print(`wrote ${entry.out}`)
i = i + 1
}

View File

@@ -13,7 +13,6 @@ JSC_CCALL(os_calc_mem,
JS_SetPropertyStr(js,ret,"memory_used_size",number2js(js,mu.memory_used_size));
JS_SetPropertyStr(js,ret,"malloc_count",number2js(js,mu.malloc_count));
JS_SetPropertyStr(js,ret,"memory_used_count",number2js(js,mu.memory_used_count));
/* atom_count and atom_size removed - atoms are now just strings */
JS_SetPropertyStr(js,ret,"str_count",number2js(js,mu.str_count));
JS_SetPropertyStr(js,ret,"str_size",number2js(js,mu.str_size));
JS_SetPropertyStr(js,ret,"obj_count",number2js(js,mu.obj_count));
@@ -35,49 +34,6 @@ JSC_CCALL(os_calc_mem,
JS_SetPropertyStr(js,ret,"binary_object_size",number2js(js,mu.binary_object_size));
)
// Evaluate a string of JavaScript code in the current QuickJS context.
JSC_SSCALL(os_eval,
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
JSValue bytecode = JS_Compile(js, str2, strlen(str2), str);
if (JS_IsException(bytecode)) return bytecode;
ret = JS_Integrate(js, bytecode, JS_NULL);
)
// Compile a string of JavaScript code into a function object.
JSC_SSCALL(js_compile,
if (!str2) return JS_ThrowReferenceError(js, "Second argument should be the script.");
if (!str) return JS_ThrowReferenceError(js, "First argument should be the name of the script.");
ret = JS_Compile(js, str2, strlen(str2), str);
)
// Link compiled bytecode with environment and execute.
JSC_CCALL(js_integrate,
JSValue env = (argc > 1 && !JS_IsNull(argv[1])) ? argv[1] : JS_NULL;
ret = JS_Integrate(js, argv[0], env);
)
// Compile a function object into a bytecode blob.
JSC_CCALL(js_compile_blob,
size_t size;
uint8_t *data = JS_WriteObject(js, &size, argv[0], JS_WRITE_OBJ_BYTECODE);
if (!data) {
return JS_ThrowInternalError(js, "Failed to serialize bytecode");
}
ret = js_new_blob_stoned_copy(js, data, size);
js_free(js, data);
)
// Compile a bytecode blob into a function object.
JSC_CCALL(js_compile_unblob,
size_t size;
void *data = js_get_blob_data(js, &size, argv[0]);
if (data == -1) return JS_EXCEPTION;
if (!data) return JS_ThrowReferenceError(js, "No data present in blob.");
return JS_ReadObject(js, data, size, JS_READ_OBJ_BYTECODE);
)
// Disassemble a function object into a string.
JSC_CCALL(js_disassemble,
return js_debugger_fn_bytecode(js, argv[0]);
@@ -92,11 +48,6 @@ static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, calc_mem, 0),
MIST_FUNC_DEF(os, mem_limit, 1),
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(os, eval, 2),
MIST_FUNC_DEF(js, compile, 2),
MIST_FUNC_DEF(js, integrate, 2),
MIST_FUNC_DEF(js, compile_blob, 1),
MIST_FUNC_DEF(js, compile_unblob, 1),
MIST_FUNC_DEF(js, disassemble, 1),
MIST_FUNC_DEF(js, fn_info, 1),
};
@@ -105,4 +56,4 @@ JSValue js_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs));
return mod;
}
}

View File

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

90
docs/_index.md Normal file
View File

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

View File

@@ -1,10 +1,15 @@
# Actors and Modules
---
title: "Actors and Modules"
description: "The ƿit execution model"
weight: 20
type: "docs"
---
Cell organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
ƿit organizes code into two types of scripts: **modules** (`.cm`) and **actors** (`.ce`).
## The Actor Model
Cell is built on the actor model of computation. Each actor:
ƿit is built on the actor model of computation. Each actor:
- Has its own **isolated memory** — actors never share state
- Runs to completion each **turn** — no preemption
@@ -21,13 +26,13 @@ A module is a script that **returns a value**. The returned value is cached and
// math_utils.cm
var math = use('math/radians')
function distance(x1, y1, x2, y2) {
var distance = function(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
function midpoint(x1, y1, x2, y2) {
var midpoint = function(x1, y1, x2, y2) {
return {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
@@ -60,12 +65,12 @@ An actor is a script that **does not return a value**. It runs as an independent
```javascript
// worker.ce
log.console("Worker started")
print("Worker started")
$on_message = function(msg) {
log.console("Received:", msg)
$receiver(function(msg, reply) {
print("Received:", msg)
// Process message...
}
})
```
**Key properties:**
@@ -83,7 +88,7 @@ Actors have access to special functions prefixed with `$`:
Reference to the current actor.
```javascript
log.console($me) // actor reference
print($me) // actor reference
```
### $stop()
@@ -100,7 +105,7 @@ Send a message to another actor.
```javascript
$send(other_actor, {type: "ping", data: 42}, function(reply) {
log.console("Got reply:", reply)
print("Got reply:", reply)
})
```
@@ -112,7 +117,7 @@ Start a new actor from a script.
```javascript
$start(function(new_actor) {
log.console("Started:", new_actor)
print("Started:", new_actor)
}, "worker")
```
@@ -122,7 +127,7 @@ Schedule a callback after a delay.
```javascript
$delay(function() {
log.console("5 seconds later")
print("5 seconds later")
}, 5)
```
@@ -169,19 +174,47 @@ $contact(function(connection) {
### $time_limit(requestor, seconds)
Wrap a requestor with a timeout.
Wrap a requestor with a timeout. See [Requestors](/docs/requestors/) for details.
```javascript
$time_limit(my_requestor, 10) // 10 second timeout
```
### $couple(actor)
Couple the current actor to another actor. When the coupled actor dies, the current actor also dies. Coupling is automatic between an actor and its overling (parent).
```javascript
$couple(other_actor)
```
### $unneeded(callback, seconds)
Schedule the actor for removal after a specified time.
```javascript
$unneeded(function() {
// cleanup before removal
}, 30)
```
### $connection(callback, actor, config)
Get information about the connection to another actor, such as latency, bandwidth, and activity.
```javascript
$connection(function(info) {
print(info.latency)
}, other_actor, {})
```
## Module Resolution
When you call `use('name')`, Cell searches:
When you call `use('name')`, ƿit searches:
1. **Current package** — files relative to package root
2. **Dependencies** — packages declared in `cell.toml`
3. **Core** — built-in Cell modules
2. **Dependencies** — packages declared in `pit.toml`
3. **Core** — built-in ƿit modules
```javascript
// From within package 'myapp':
@@ -199,14 +232,14 @@ Files starting with underscore (`_helper.cm`) are private to the package.
// main.ce - Entry point
var config = use('config')
log.console("Starting application...")
print("Starting application...")
$start(function(worker) {
$send(worker, {task: "process", data: [1, 2, 3]})
}, "worker")
$delay(function() {
log.console("Shutting down")
print("Shutting down")
$stop()
}, 10)
```

View File

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

View File

@@ -1,288 +0,0 @@
# Cell Language
Cell is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
## Basics
### Variables and Constants
```javascript
var x = 10 // mutable variable (block-scoped like let)
def PI = 3.14159 // constant (cannot be reassigned)
```
### Data Types
Cell has six fundamental types:
- **number** — DEC64 decimal floating point (no rounding errors)
- **text** — Unicode strings
- **logical** — `true` or `false`
- **null** — the absence of a value (no `undefined`)
- **array** — ordered, numerically-indexed sequences
- **object** — key-value records with prototype inheritance
- **blob** — binary data (bits, not bytes)
- **function** — first-class callable values
### Literals
```javascript
// Numbers
42
3.14
1_000_000 // underscores for readability
// Text
"hello"
'world'
`template ${x}` // string interpolation
// Logical
true
false
// Null
null
// Arrays
[1, 2, 3]
["a", "b", "c"]
// Objects
{name: "cell", version: 1}
{x: 10, y: 20}
```
### Operators
```javascript
// Arithmetic
+ - * / %
** // exponentiation
// Comparison (always strict)
== // equals (like === in JS)
!= // not equals (like !== in JS)
< > <= >=
// Logical
&& || !
// Assignment
= += -= *= /=
```
### Control Flow
```javascript
// Conditionals
if (x > 0) {
log.console("positive")
} else if (x < 0) {
log.console("negative")
} else {
log.console("zero")
}
// Ternary
var sign = x > 0 ? 1 : -1
// Loops
for (var i = 0; i < 10; i++) {
log.console(i)
}
for (var item of items) {
log.console(item)
}
for (var key in obj) {
log.console(key, obj[key])
}
while (condition) {
// body
}
// Control
break
continue
return value
throw "error message"
```
### Functions
```javascript
// Named function
function add(a, b) {
return a + b
}
// Anonymous function
var multiply = function(a, b) {
return a * b
}
// Arrow function
var square = x => x * x
var sum = (a, b) => a + b
// Rest parameters
function log_all(...args) {
for (var arg of args) log.console(arg)
}
// Default parameters
function greet(name, greeting = "Hello") {
return `${greeting}, ${name}!`
}
```
All closures capture `this` (like arrow functions in JavaScript).
## Arrays
Arrays are **distinct from objects**. They are ordered, numerically-indexed sequences. You cannot add arbitrary string keys to an array.
```javascript
var arr = [1, 2, 3]
arr[0] // 1
arr[2] = 10 // [1, 2, 10]
length(arr) // 3
// Array spread
var more = [...arr, 4, 5] // [1, 2, 10, 4, 5]
```
## Objects
Objects are key-value records with prototype-based inheritance.
```javascript
var point = {x: 10, y: 20}
point.x // 10
point["y"] // 20
// Object spread
var point3d = {...point, z: 30}
// Prototype inheritance
var colored_point = {__proto__: point, color: "red"}
colored_point.x // 10 (inherited)
```
### Prototypes
```javascript
// Create object with prototype
var child = meme(parent)
// Get prototype
var p = proto(child)
// Check prototype chain
isa(child, parent) // true
```
## Immutability with Stone
The `stone()` function makes values permanently immutable.
```javascript
var config = stone({
debug: true,
maxRetries: 3
})
config.debug = false // Error! Stone objects cannot be modified
```
Stone is **deep** — all nested objects and arrays are also frozen. This cannot be reversed.
```javascript
stone.p(value) // returns true if value is stone
```
## Built-in Functions
### length(value)
Returns the length of arrays (elements), text (codepoints), blobs (bits), or functions (arity).
```javascript
length([1, 2, 3]) // 3
length("hello") // 5
length(function(a,b){}) // 2
```
### use(path)
Import a module. Returns the cached, stone value.
```javascript
var math = use('math/radians')
var json = use('json')
```
### isa(value, type)
Check type or prototype chain.
```javascript
is_number(42) // true
is_text("hi") // true
is_array([1,2]) // true
is_object({}) // true
isa(child, parent) // true if parent is in prototype chain
```
### reverse(array)
Returns a new array with elements in reverse order.
```javascript
reverse([1, 2, 3]) // [3, 2, 1]
```
### logical(value)
Convert to boolean.
```javascript
logical(0) // false
logical(1) // true
logical("true") // true
logical("false") // false
logical(null) // false
```
## Logging
```javascript
log.console("message") // standard output
log.error("problem") // error output
```
## Pattern Matching
Cell supports regex patterns in string functions, but not standalone regex objects.
```javascript
text.search("hello world", /world/)
replace("hello", /l/g, "L")
```
## Error Handling
```javascript
try {
riskyOperation()
} catch (e) {
log.error(e)
}
throw "something went wrong"
```
If an actor has an uncaught error, it crashes.

View File

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

File diff suppressed because it is too large Load Diff

View File

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

94
docs/kim.md Normal file
View File

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

649
docs/language.md Normal file
View File

@@ -0,0 +1,649 @@
---
title: "ƿit Language"
description: "Syntax, types, operators, and built-in functions"
weight: 10
type: "docs"
---
ƿit is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.
## Basics
### Variables and Constants
Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or bare `{}` blocks.
```javascript
var x = 10
var name = "pit"
var empty = null
def PI = 3.14159 // constant, cannot be reassigned
var a = 1, b = 2, c = 3 // multiple declarations
```
### Data Types
ƿit has eight fundamental types:
- **number** — DEC64 decimal floating point (no rounding errors)
- **text** — Unicode strings
- **logical** — `true` or `false`
- **null** — the absence of a value (no `undefined`)
- **array** — ordered, numerically-indexed sequences
- **object** — key-value records with prototype inheritance
- **blob** — binary data (bits, not bytes)
- **function** — first-class callable values
### Literals
```javascript
// Numbers
42
3.14
-5
0
1e3 // scientific notation (1000)
// Text
"hello"
`template ${x}` // string interpolation
`${1 + 2}` // expression interpolation
// Logical
true
false
// Null
null
// Arrays
[1, 2, 3]
[]
// Objects
{a: 1, b: "two"}
{}
// Regex
/\d+/
/hello/i // with flags
```
## Operators
### Arithmetic
```javascript
2 + 3 // 5
5 - 3 // 2
3 * 4 // 12
12 / 4 // 3
10 % 3 // 1
2 ** 3 // 8 (exponentiation)
```
### Comparison
All comparisons are strict — there is no type coercion.
```javascript
5 == 5 // true
5 != 6 // true
3 < 5 // true
5 > 3 // true
3 <= 3 // true
5 >= 5 // true
```
### Logical
```javascript
true && true // true
true && false // false
false || true // true
false || false // false
!true // false
!false // true
```
Logical operators short-circuit:
```javascript
var called = false
var fn = function() { called = true; return true }
var r = false && fn() // fn() not called
r = true || fn() // fn() not called
```
### Bitwise
```javascript
5 & 3 // 1 (AND)
5 | 3 // 7 (OR)
5 ^ 3 // 6 (XOR)
~0 // -1 (NOT)
1 << 3 // 8 (left shift)
8 >> 3 // 1 (right shift)
-1 >>> 1 // 2147483647 (unsigned right shift)
```
### Unary
```javascript
+5 // 5
-5 // -5
-(-5) // 5
```
### Increment and Decrement
```javascript
var x = 5
x++ // returns 5, x becomes 6 (postfix)
++x // returns 7, x becomes 7 (prefix)
x-- // returns 7, x becomes 6 (postfix)
--x // returns 5, x becomes 5 (prefix)
```
### Compound Assignment
```javascript
var x = 10
x += 3 // 13
x -= 3 // 10
x *= 2 // 20
x /= 4 // 5
x %= 3 // 2
```
### Ternary
```javascript
var a = true ? 1 : 2 // 1
var b = false ? 1 : 2 // 2
var c = true ? (false ? 1 : 2) : 3 // 2 (nested)
```
### Comma
The comma operator evaluates all expressions and returns the last.
```javascript
var x = (1, 2, 3) // 3
```
### In
Test whether a key exists in an object.
```javascript
var o = {a: 1}
"a" in o // true
"b" in o // false
```
### Delete
Remove a key from an object.
```javascript
var o = {a: 1, b: 2}
delete o.a
"a" in o // false
o.b // 2
```
## Property Access
### Dot and Bracket
```javascript
var o = {x: 10}
o.x // 10 (dot read)
o.x = 20 // dot write
o["x"] // 20 (bracket read)
var key = "x"
o[key] // 20 (computed bracket)
o["y"] = 30 // bracket write
```
### Object as Key
Objects can be used as keys in other objects.
```javascript
var k = {}
var o = {}
o[k] = 42
o[k] // 42
o[{}] // null (different object)
k in o // true
delete o[k]
k in o // false
```
### Chained Access
```javascript
var d = {a: {b: [1, {c: 99}]}}
d.a.b[1].c // 99
```
## Arrays
Arrays are **distinct from objects**. They are ordered, numerically-indexed sequences.
```javascript
var arr = [1, 2, 3]
arr[0] // 1
arr[2] = 10 // [1, 2, 10]
length(arr) // 3
```
### Push and Pop
```javascript
var a = [1, 2]
a[] = 3 // push: [1, 2, 3]
length(a) // 3
var v = a[] // pop: v is 3, a is [1, 2]
length(a) // 2
```
## Objects
Objects are key-value records with prototype-based inheritance.
```javascript
var point = {x: 10, y: 20}
point.x // 10
point["y"] // 20
```
### Prototypes
```javascript
// Create object with prototype
var parent = {x: 10}
var child = meme(parent)
child.x // 10 (inherited)
proto(child) // parent
// Override does not mutate parent
child.x = 20
parent.x // 10
```
### Mixins
```javascript
var p = {a: 1}
var m1 = {b: 2}
var m2 = {c: 3}
var child = meme(p, [m1, m2])
child.a // 1 (from prototype)
child.b // 2 (from mixin)
child.c // 3 (from mixin)
```
## Control Flow
### If / Else
```javascript
var x = 0
if (true) x = 1
if (false) x = 2 else x = 3
if (false) x = 4
else if (true) x = 5
else x = 6
```
### While
```javascript
var i = 0
while (i < 5) i++
// break
i = 0
while (true) {
if (i >= 3) break
i++
}
// continue
var sum = 0
i = 0
while (i < 5) {
i++
if (i % 2 == 0) continue
sum += i
}
```
### For
Variables cannot be declared in the for initializer. Declare them at the function body level.
```javascript
var sum = 0
var i = 0
for (i = 0; i < 5; i++) sum += i
// break
sum = 0
i = 0
for (i = 0; i < 10; i++) {
if (i == 5) break
sum += i
}
// continue
sum = 0
i = 0
for (i = 0; i < 5; i++) {
if (i % 2 == 0) continue
sum += i
}
// nested
sum = 0
var j = 0
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
sum++
}
}
```
## Functions
### Function Expressions
```javascript
var add = function(a, b) { return a + b }
add(2, 3) // 5
```
### Arrow Functions
```javascript
var double = x => x * 2
double(5) // 10
var sum = (a, b) => a + b
sum(2, 3) // 5
var block = x => {
var y = x * 2
return y + 1
}
block(5) // 11
```
### Return
A function with no `return` returns `null`. An early `return` exits immediately.
```javascript
var fn = function() { var x = 1 }
fn() // null
var fn2 = function() { return 1; return 2 }
fn2() // 1
```
### Arguments
Extra arguments are ignored. Missing arguments are `null`.
```javascript
var fn = function(a, b) { return a + b }
fn(1, 2, 3) // 3 (extra arg ignored)
var fn2 = function(a, b) { return a }
fn2(1) // 1 (b is null)
```
### Immediately Invoked Function Expression
```javascript
var r = (function(x) { return x * 2 })(21) // 42
```
### Closures
Functions capture variables from their enclosing scope.
```javascript
var make = function(x) {
return function(y) { return x + y }
}
var add5 = make(5)
add5(3) // 8
```
Captured variables can be mutated:
```javascript
var counter = function() {
var n = 0
return function() { n = n + 1; return n }
}
var c = counter()
c() // 1
c() // 2
```
### Recursion
```javascript
var fact = function(n) {
if (n <= 1) return 1
return n * fact(n - 1)
}
fact(5) // 120
```
### This Binding
When a function is called as a method, `this` refers to the object.
```javascript
var obj = {
val: 10,
get: function() { return this.val }
}
obj.get() // 10
```
### Currying
```javascript
var f = function(a) {
return function(b) {
return function(c) { return a + b + c }
}
}
f(1)(2)(3) // 6
```
## Identifiers
Identifiers can contain `?` and `!` characters, both as suffixes and mid-name.
```javascript
var nil? = (x) => x == null
nil?(null) // true
nil?(42) // false
var set! = (x) => x + 1
set!(5) // 6
var is?valid = (x) => x > 0
is?valid(3) // true
var do!stuff = () => 42
do!stuff() // 42
```
The `?` in an identifier is not confused with the ternary operator:
```javascript
var nil? = (x) => x == null
var a = nil?(null) ? "yes" : "no" // "yes"
```
## Type Checking
### Type Functions
```javascript
is_number(42) // true
is_text("hi") // true
is_logical(true) // true
is_object({}) // true
is_array([]) // true
is_function(function(){}) // true
is_null(null) // true
is_object([]) // false (array is not object)
is_array({}) // false (object is not array)
```
### Truthiness
Falsy values: `false`, `0`, `""`, `null`. Everything else is truthy.
```javascript
if (0) ... // not entered
if ("") ... // not entered
if (null) ... // not entered
if (1) ... // entered
if ("hi") ... // entered
if ({}) ... // entered
if ([]) ... // entered
```
## Immutability with Stone
The `stone()` function makes values permanently immutable.
```javascript
var o = {x: 1}
is_stone(o) // false
stone(o)
is_stone(o) // true
o.x = 2 // disrupts!
```
Stone is **deep** — all nested objects and arrays are also frozen. This cannot be reversed.
## Function Proxy
A function with two parameters (`name`, `args`) acts as a proxy when properties are accessed on it. Any method call on the function dispatches through the proxy.
```javascript
var proxy = function(name, args) {
return `${name}:${length(args)}`
}
proxy.hello() // "hello:0"
proxy.add(1, 2) // "add:2"
proxy["method"]() // "method:0"
var m = "dynamic"
proxy[m]() // "dynamic:0"
```
For non-proxy functions, property access disrupts:
```javascript
var fn = function() { return 1 }
fn.foo // disrupts
fn.foo = 1 // disrupts
```
## Regex
Regex literals are written with forward slashes, with optional flags.
```javascript
var r = /\d+/
var result = extract("abc123", r)
result[0] // "123"
var ri = /hello/i
var result2 = extract("Hello", ri)
result2[0] // "Hello"
```
## Error Handling
ƿit uses `disrupt` and `disruption` for error handling. A `disrupt` signals that something went wrong. The `disruption` block attached to a function catches it.
```javascript
var safe_divide = function(a, b) {
if (b == 0) disrupt
return a / b
} disruption {
print("something went wrong")
}
```
`disrupt` is a bare keyword — it does not carry a value. The `disruption` block knows that something went wrong, but not what.
### Re-raising
A `disruption` block can re-raise by calling `disrupt` again:
```javascript
var outer = function() {
var inner = function() { disrupt } disruption { disrupt }
inner()
} disruption {
// caught here after re-raise
}
outer()
```
### Testing for Disruption
```javascript
var should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
```
If an actor has an unhandled disruption, it crashes.
## Self-Referencing Structures
Objects can reference themselves:
```javascript
var o = {name: "root"}
o.self = o
o.self.self.name // "root"
```
## Variable Shadowing
Inner functions can shadow outer variables:
```javascript
var x = 10
var fn = function() {
var x = 20
return x
}
fn() // 20
x // 10
```

View File

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

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

@@ -0,0 +1,18 @@
---
title: "Standard Library"
description: "ƿit standard library modules"
weight: 90
type: "docs"
---
The standard library provides modules loaded with `use()`.
| Module | Description |
|--------|-------------|
| [blob](/docs/library/blob/) | Binary data (bits, not bytes) |
| [time](/docs/library/time/) | Time constants and conversions |
| [math](/docs/library/math/) | Trigonometry, logarithms, roots |
| [json](/docs/library/json/) | JSON encoding and decoding |
| [random](/docs/library/random/) | Random number generation |
The `text`, `number`, `array`, and `object` functions are intrinsics — they are always available without `use`. See [Built-in Functions](/docs/functions/) for the full list, and the individual reference pages for [text](/docs/library/text/), [number](/docs/library/number/), [array](/docs/library/array/), and [object](/docs/library/object/).

View File

@@ -1,12 +1,19 @@
# array
---
title: "array"
description: "Array creation and manipulation"
weight: 30
type: "docs"
---
The `array` function and its methods handle array creation and manipulation.
The `array` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument.
## Creation
## From a Number
Create an array of a given size.
### array(number)
Create an array of specified size, filled with `null`.
All elements initialized to `null`.
```javascript
array(3) // [null, null, null]
@@ -14,24 +21,36 @@ array(3) // [null, null, null]
### array(number, initial)
Create an array with initial values.
All elements initialized to a value. If initial is a function, it is called for each element (passed the index if arity >= 1).
```javascript
array(3, 0) // [0, 0, 0]
array(3, i => i * 2) // [0, 2, 4]
```
## From an Array
Copy, map, concat, or slice.
### array(array)
Copy an array.
Copy an array (mutable).
```javascript
var copy = array(original)
```
### array(array, function)
Map — call function with each element, collect results.
```javascript
array([1, 2, 3], x => x * 2) // [2, 4, 6]
```
### array(array, from, to)
Slice an array.
Slice — extract a sub-array. Negative indices count from end.
```javascript
array([1, 2, 3, 4, 5], 1, 4) // [2, 3, 4]
@@ -40,32 +59,36 @@ array([1, 2, 3], -2) // [2, 3]
### array(array, another)
Concatenate arrays.
Concatenate two arrays.
```javascript
array([1, 2], [3, 4]) // [1, 2, 3, 4]
```
### array(object)
## From a Record
Get keys of an object.
### array(record)
Get the keys of a record as an array of text.
```javascript
array({a: 1, b: 2}) // ["a", "b"]
```
## From Text
### array(text)
Split text into grapheme clusters.
Split text into individual characters (grapheme clusters). This is the standard way to iterate over characters in a string.
```javascript
array("hello") // ["h", "e", "l", "l", "o"]
array("👨‍👩‍👧") // ["👨‍👩‍👧"]
array("hello") // ["h", "e", "l", "l", "o"]
array("ƿit") // ["ƿ", "i", "t"]
```
### array(text, separator)
Split text by separator.
Split text by a separator string.
```javascript
array("a,b,c", ",") // ["a", "b", "c"]
@@ -73,7 +96,7 @@ array("a,b,c", ",") // ["a", "b", "c"]
### array(text, length)
Split text into chunks.
Dice text into chunks of a given length.
```javascript
array("abcdef", 2) // ["ab", "cd", "ef"]
@@ -87,13 +110,13 @@ Iterate over elements.
```javascript
array.for([1, 2, 3], function(el, i) {
log.console(i, el)
print(i, el)
})
// With early exit
array.for([1, 2, 3, 4], function(el) {
if (el > 2) return true
log.console(el)
print(el)
}, false, true) // prints 1, 2
```

View File

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

View File

@@ -1,4 +1,9 @@
# json
---
title: "json"
description: "JSON encoding and decoding"
weight: 80
type: "docs"
---
JSON encoding and decoding.
@@ -86,5 +91,5 @@ var config_text = json.encode(config, 2)
// Load configuration
var loaded = json.decode(config_text)
log.console(loaded.debug) // true
print(loaded.debug) // true
```

View File

@@ -1,10 +1,15 @@
# math
---
title: "math"
description: "Trigonometry, logarithms, and roots"
weight: 70
type: "docs"
---
Cell provides three math modules with identical functions but different angle representations:
ƿit provides three math modules with identical functions but different angle representations:
```javascript
var math = use('math/radians') // angles in radians
var math = use('math/degrees') // angles in degrees
var math = use('math/degrees') // angles in degrees
var math = use('math/cycles') // angles in cycles (0-1)
```
@@ -35,7 +40,7 @@ math.tangent(math.pi / 4) // 1 (radians)
Inverse sine.
```javascript
math.arc_sine(1) // π/2 (radians)
math.arc_sine(1) // pi/2 (radians)
```
### arc_cosine(n)
@@ -43,7 +48,7 @@ math.arc_sine(1) // π/2 (radians)
Inverse cosine.
```javascript
math.arc_cosine(0) // π/2 (radians)
math.arc_cosine(0) // pi/2 (radians)
```
### arc_tangent(n, denominator)
@@ -51,9 +56,9 @@ math.arc_cosine(0) // π/2 (radians)
Inverse tangent. With two arguments, computes atan2.
```javascript
math.arc_tangent(1) // π/4 (radians)
math.arc_tangent(1, 1) // π/4 (radians)
math.arc_tangent(-1, -1) // -3π/4 (radians)
math.arc_tangent(1) // pi/4 (radians)
math.arc_tangent(1, 1) // pi/4 (radians)
math.arc_tangent(-1, -1) // -3pi/4 (radians)
```
## Exponentials and Logarithms
@@ -64,7 +69,7 @@ Euler's number raised to a power. Default power is 1.
```javascript
math.e() // 2.718281828...
math.e(2) // e²
math.e(2) // e^2
```
### ln(n)
@@ -130,21 +135,21 @@ math.e() // 2.71828...
var math = use('math/radians')
// Distance between two points
function distance(x1, y1, x2, y2) {
var distance = function(x1, y1, x2, y2) {
var dx = x2 - x1
var dy = y2 - y1
return math.sqrt(dx * dx + dy * dy)
}
// Angle between two points
function angle(x1, y1, x2, y2) {
var angle = function(x1, y1, x2, y2) {
return math.arc_tangent(y2 - y1, x2 - x1)
}
// Rotate a point
function rotate(x, y, angle) {
var c = math.cosine(angle)
var s = math.sine(angle)
var rotate = function(x, y, a) {
var c = math.cosine(a)
var s = math.sine(a)
return {
x: x * c - y * s,
y: x * s + y * c

View File

@@ -1,6 +1,11 @@
# number
---
title: "number"
description: "Numeric conversion and operations"
weight: 20
type: "docs"
---
The `number` function and its methods handle numeric conversion and operations.
The `number` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument.
## Conversion
@@ -29,15 +34,15 @@ Parse formatted numbers.
| Format | Description |
|--------|-------------|
| `""` | Standard decimal |
| `"u"` | Underbar separator (1_000) |
| `"d"` | Comma separator (1,000) |
| `"s"` | Space separator (1 000) |
| `"v"` | European (1.000,50) |
| `"b"` | Binary |
| `"o"` | Octal |
| `"h"` | Hexadecimal |
| `"j"` | JavaScript style (0x, 0o, 0b prefixes) |
| `""` | Standard decimal |
| `"u"` | Underbar separator (1_000) |
| `"d"` | Comma separator (1,000) |
| `"s"` | Space separator (1 000) |
| `"v"` | European (1.000,50) |
| `"b"` | Binary |
| `"o"` | Octal |
| `"h"` | Hexadecimal |
| `"j"` | JavaScript style (0x, 0o, 0b prefixes) |
```javascript
number("1,000", "d") // 1000
@@ -118,20 +123,20 @@ Get the fractional part.
fraction(4.75) // 0.75
```
### min(...values)
### min(a, b)
Return the smallest value.
Return the smaller of two numbers.
```javascript
min(3, 1, 4, 1, 5) // 1
min(3, 5) // 3
```
### max(...values)
### max(a, b)
Return the largest value.
Return the larger of two numbers.
```javascript
max(3, 1, 4, 1, 5) // 5
max(3, 5) // 5
```
### remainder(dividend, divisor)

View File

@@ -1,8 +1,13 @@
# object
---
title: "object"
description: "Object creation and manipulation"
weight: 40
type: "docs"
---
The `object` function and related utilities handle object creation and manipulation.
The `object` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the types of its arguments.
## Creation
## From a Record
### object(obj)
@@ -29,6 +34,8 @@ Select specific keys.
object({a: 1, b: 2, c: 3}, ["a", "c"]) // {a: 1, c: 3}
```
## From an Array of Keys
### object(keys)
Create object from keys (values are `true`).
@@ -60,9 +67,9 @@ object(["a", "b", "c"], (k, i) => i) // {a: 0, b: 1, c: 2}
Create a new object with the given prototype.
```javascript
var animal = {speak: function() { log.console("...") }}
var animal = {speak: function() { print("...") }}
var dog = meme(animal)
dog.speak = function() { log.console("woof") }
dog.speak = function() { print("woof") }
```
### proto(obj)
@@ -104,9 +111,4 @@ var obj = {a: 1, b: 2, c: 3}
// Get all keys
var keys = array(obj) // ["a", "b", "c"]
// Iterate
for (var key in obj) {
log.console(key, obj[key])
}
```

View File

@@ -1,4 +1,9 @@
# random
---
title: "random"
description: "Random number generation"
weight: 90
type: "docs"
---
Random number generation.
@@ -43,7 +48,7 @@ var random = use('random')
var coin_flip = random.random() < 0.5
// Random element from array
function pick(arr) {
var pick = function(arr) {
return arr[random.random_whole(length(arr))]
}
@@ -51,11 +56,14 @@ var colors = ["red", "green", "blue"]
var color = pick(colors)
// Shuffle array
function shuffle(arr) {
var shuffle = function(arr) {
var result = array(arr) // copy
for (var i = length(result) - 1; i > 0; i--) {
var j = random.random_whole(i + 1)
var temp = result[i]
var i = length(result) - 1
var j = 0
var temp = null
for (i = length(result) - 1; i > 0; i--) {
j = random.random_whole(i + 1)
temp = result[i]
result[i] = result[j]
result[j] = temp
}
@@ -63,8 +71,8 @@ function shuffle(arr) {
}
// Random in range
function random_range(min, max) {
return min + random.random() * (max - min)
var random_range = function(lo, hi) {
return lo + random.random() * (hi - lo)
}
var x = random_range(-10, 10) // -10 to 10

View File

@@ -1,19 +1,28 @@
# text
---
title: "text"
description: "String conversion and manipulation"
weight: 10
type: "docs"
---
The `text` function and its methods handle string conversion and manipulation.
The `text` function is an intrinsic (always available, no `use()` needed). It is **polymorphic** — its behavior depends on the type of the first argument.
## Conversion
To split text into characters, use `array(text)` — see [array](/docs/library/array/).
## From an Array
### text(array, separator)
Convert an array to text, joining elements with a separator (default: space).
Join array elements into text with a separator (default: empty string).
```javascript
text([1, 2, 3]) // "1 2 3"
text([1, 2, 3], ", ") // "1, 2, 3"
text(["a", "b"], "-") // "a-b"
text(["h", "e", "l", "l", "o"]) // "hello"
text([1, 2, 3], ", ") // "1, 2, 3"
text(["a", "b"], "-") // "a-b"
```
## From a Number
### text(number, radix)
Convert a number to text. Radix is 2-36 (default: 10).
@@ -24,13 +33,16 @@ text(255, 16) // "ff"
text(255, 2) // "11111111"
```
## From Text
### text(text, from, to)
Extract a substring from index `from` to `to`.
Extract a substring from index `from` to `to`. Negative indices count from end.
```javascript
text("hello world", 0, 5) // "hello"
text("hello world", 6) // "world"
text("hello", -3) // "llo"
```
## Methods
@@ -101,7 +113,7 @@ text.format("{0} + {1} = {2}", [1, 2, 3])
Unicode normalize the text (NFC form).
```javascript
text.normalize("café") // normalized form
text.normalize("cafe\u0301") // normalized form
```
### text.codepoint(text)
@@ -109,8 +121,7 @@ text.normalize("café") // normalized form
Get the Unicode codepoint of the first character.
```javascript
text.codepoint("A") // 65
text.codepoint("😀") // 128512
text.codepoint("A") // 65
```
### text.extract(text, pattern, from, to)

View File

@@ -1,4 +1,9 @@
# time
---
title: "time"
description: "Time constants and conversion functions"
weight: 60
type: "docs"
---
The time module provides time constants and conversion functions.
@@ -96,7 +101,7 @@ var last_week = now - time.week
var later = now + (2 * time.hour)
// Format future time
log.console(time.text(tomorrow))
print(time.text(tomorrow))
```
## Example
@@ -108,9 +113,9 @@ var time = use('time')
var start = time.number()
// ... do work ...
var elapsed = time.number() - start
log.console(`Took ${elapsed} seconds`)
print(`Took ${elapsed} seconds`)
// Schedule for tomorrow
var tomorrow = time.number() + time.day
log.console(`Tomorrow: ${time.text(tomorrow, "yyyy-MM-dd")}`)
print(`Tomorrow: ${time.text(tomorrow, "yyyy-MM-dd")}`)
```

View File

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

156
docs/nota.md Normal file
View File

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

View File

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

176
docs/requestors.md Normal file
View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

119
docs/wota.md Normal file
View File

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

251
editors/ai/pit-context.md Normal file
View File

@@ -0,0 +1,251 @@
# ƿit Language — AI Context
ƿit (pronounced "pit") is a safe, actor-based programming language. Its syntax resembles JavaScript but with significant differences. Scripts use `.ce` (actors) and `.cm` (modules) file extensions.
## Key Differences from JavaScript
- **`var` / `def`** — `var` is mutable, `def` is constant. No `let` or `const`.
- **`==` is strict** — No `===` or `!==`. `==` and `!=` are always strict comparison.
- **No `undefined`** — Only `null`. Division by zero produces `null`, not `Infinity`.
- **No classes** — Use `meme()`, `proto()`, `isa()` for prototype chains.
- **No `for...in`, `for...of`, spread, rest, or default params.**
- **Variables declared at function body level only** — Not inside `if`/`while`/`for` blocks.
- **All variables must be initialized** — `var x` alone is an error; use `var x = null`.
- **`disrupt` / `disruption`** — No `try`/`catch`/`throw`. Error handling uses:
```javascript
var fn = function() {
disrupt // raise an error (bare keyword, no value)
} disruption {
// handle the error
}
```
- **No arraybuffers** — Use `blob` (works with bits; `stone(blob)` before reading).
- **Identifiers can contain `?` and `!`** — e.g., `nil?`, `set!`, `is?valid`.
- **4-parameter limit** — Functions take at most 4 named parameters.
- **Everything lowercase** — Convention is all-lowercase identifiers with underscores.
## Variable Declaration
```javascript
var count = 0 // mutable
def MAX = 100 // constant (cannot be reassigned)
var x = null // must initialize (var x alone is an error)
```
## Functions
```javascript
var greet = function(name) {
print(`hello ${name}`)
}
// Arrow functions
var double = x => x * 2
var add = (a, b) => a + b
```
## Push / Pop Syntax
```javascript
var a = [1, 2]
a[] = 3 // push: a is now [1, 2, 3]
var v = a[] // pop: v is 3, a is [1, 2]
```
## Control Flow
```javascript
if (x > 0) {
print("positive")
} else {
print("non-positive")
}
while (i < 10) {
i = i + 1
}
for (var i = 0; i < 10; i = i + 1) {
print(i)
}
// do-while
do {
i = i + 1
} while (i < 10)
```
## Error Handling
```javascript
var safe_divide = function(a, b) {
if (b == 0) {
disrupt
}
return a / b
} disruption {
return null
}
```
## Creator Functions (Polymorphic)
These examine argument types to decide behavior:
### array()
- `array(5)` — `[null, null, null, null, null]`
- `array(3, 0)` — `[0, 0, 0]`
- `array(5, i => i * 2)` — `[0, 2, 4, 6, 8]`
- `array([1,2])` — copy
- `array([1,2,3], x => x * 10)` — map: `[10, 20, 30]`
- `array([1,2], [3,4])` — concat: `[1, 2, 3, 4]`
- `array([1,2,3,4,5], 1, 4)` — slice: `[2, 3, 4]`
- `array({a: 1, b: 2})` — keys: `["a", "b"]`
- `array("hello")` — characters: `["h", "e", "l", "l", "o"]`
- `array("a,b,c", ",")` — split: `["a", "b", "c"]`
### text()
- `text([1, 2, 3], ", ")` — join: `"1, 2, 3"`
- `text(255, 16)` — radix: `"ff"`
- `text("hello", 0, 3)` — substring: `"hel"`
### number()
- `number("42")` — parse: `42`
- `number("ff", 16)` — radix: `255`
- `number(true)` — `1`
### record()
- `record({a: 1})` — copy
- `record({a: 1}, {b: 2})` — merge: `{a: 1, b: 2}`
- `record(["x", "y"])` — from keys: `{x: true, y: true}`
## All Intrinsic Functions
**Constants:** `false`, `true`, `null`, `pi`
**Type checks:** `is_array`, `is_blob`, `is_character`, `is_data`, `is_digit`, `is_false`, `is_fit`, `is_function`, `is_integer`, `is_letter`, `is_logical`, `is_lower`, `is_null`, `is_number`, `is_object`, `is_pattern`, `is_stone`, `is_text`, `is_true`, `is_upper`, `is_whitespace`
**Creators:** `array`, `logical`, `number`, `record`, `text`
**Math:** `abs`, `ceiling`, `floor`, `fraction`, `max`, `min`, `modulo`, `neg`, `remainder`, `round`, `sign`, `trunc`, `whole`
**Text:** `character`, `codepoint`, `ends_with`, `extract`, `format`, `lower`, `normalize`, `replace`, `search`, `starts_with`, `trim`, `upper`
**Array:** `every`, `filter`, `find`, `for`, `length`, `reduce`, `reverse`, `some`, `sort`
**Objects:** `meme`, `proto`, `isa`, `stone`
**Functions:** `apply`, `splat`
**I/O:** `print`
**Async:** `fallback`, `parallel`, `race`, `sequence`
**Misc:** `logical`, `not`, `use`
## Variable Scoping
Variables are scoped to the function body in which they are declared. There is no block scoping. All declarations must be at the top level of a function body (not nested inside `if`/`while`/`for`).
```javascript
var outer = function() {
var x = 10
var inner = function() {
// x is visible here via closure
print(x)
}
inner()
}
```
## Modules (.cm files)
Modules return a value (typically a record of exports). They are loaded with `use()`, cached, and frozen.
```javascript
// math_utils.cm
var square = x => x * x
var cube = x => x * x * x
return {square: square, cube: cube}
// main.ce
var utils = use('math_utils')
print(utils.square(5)) // 25
```
## Standard Library (loaded with use())
- `blob` — binary data (works with bits, not bytes)
- `time` — time constants and conversions
- `math` — trig, logarithms, roots (sub-modules: `math/radians`, `math/turns`)
- `json` — JSON encoding/decoding (`json.encode`, `json.decode`)
- `random` — random number generation
- `fd` — file descriptor operations (`fd.read`, `fd.write`, `fd.slurp`, `fd.stat`)
## Actor Model (.ce files)
Actors are independent execution units that never share memory. They communicate via message passing.
```javascript
// greeter.ce
$receiver(function(msg) {
$send(msg.from, {greeting: `hello ${msg.name}`})
})
```
### Actor Intrinsics ($ prefix)
- `$me` — this actor's address
- `$send(address, message)` — send a message
- `$start(script, env)` — start a new actor
- `$stop()` — stop this actor
- `$delay(ms)` — delay processing
- `$receiver(fn)` — set message handler
- `$clock(interval, message)` — periodic self-message
- `$portal(name)` — create named portal
- `$contact(name)` — connect to portal
- `$couple(address)` — lifecycle coupling
- `$unneeded(fn)` — cleanup callback
- `$connection(address)` — establish connection
- `$time_limit(ms)` — execution time limit
## Common Patterns
### Iteration
```javascript
// Preferred: use for() intrinsic
for([1, 2, 3], function(item, index) {
print(`${text(index)}: ${text(item)}`)
})
// C-style for loop
for (var i = 0; i < length(items); i = i + 1) {
print(items[i])
}
```
### String Building
```javascript
// Use backtick interpolation
var msg = `hello ${name}, you are ${text(age)} years old`
// Join array
var csv = text(values, ",")
```
### Record Manipulation
```javascript
var obj = {name: "alice", age: 30}
var keys = array(obj) // ["name", "age"]
var copy = record(obj) // mutable copy
var merged = record(obj, {role: "admin"})
```
### Error-Safe Operations
```javascript
var safe_parse = function(input) {
return number(input)
} disruption {
return null
}
```

View File

@@ -0,0 +1,30 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["`", "`"]
],
"indentationRules": {
"increaseIndentPattern": "^.*\\{[^}\"'`]*$",
"decreaseIndentPattern": "^\\s*\\}"
},
"wordPattern": "[a-zA-Z_$][a-zA-Z0-9_$?!]*"
}

View File

@@ -0,0 +1,113 @@
// Document analysis module.
// Call make(tokenize_mod, parse_mod) to get an analysis object.
var json = use('json')
// Create an analysis module bound to the tokenize and parse functions.
var make = function(tokenize_mod, parse_mod) {
// Tokenize and parse a document, storing the results.
var update = function(docs, uri, params) {
var src = params.src
var version = params.version
var tok_result = null
var ast = null
var errors = []
var doc = null
var do_tokenize = function() {
tok_result = tokenize_mod(src, uri)
} disruption {
errors = [{message: "Tokenize failed", line: 1, column: 1}]
}
var do_parse = function() {
ast = parse_mod(tok_result.tokens, src, uri, tokenize_mod)
} disruption {
// parse_mod may set errors on ast even on partial failure
}
do_tokenize()
if (tok_result != null) {
do_parse()
if (ast != null && ast.errors != null) {
errors = ast.errors
}
}
doc = {
uri: uri,
text: src,
version: version,
tokens: (tok_result != null) ? tok_result.tokens : [],
ast: ast,
errors: errors
}
docs[uri] = doc
return doc
}
// Remove a document from the store.
var remove = function(docs, uri) {
delete docs[uri]
}
// Convert parse errors to LSP diagnostics.
var diagnostics = function(doc) {
var result = []
var _i = 0
var e = null
var line = null
var col = null
while (_i < length(doc.errors)) {
e = doc.errors[_i]
line = (e.line != null) ? e.line - 1 : 0
col = (e.column != null) ? e.column - 1 : 0
result[] = {
range: {
start: {line: line, character: col},
end: {line: line, character: col + 1}
},
severity: 1,
source: "pit",
message: e.message
}
_i = _i + 1
}
return result
}
// Find the token at a given line/column (0-based).
var token_at = function(doc, line, col) {
var tokens = doc.tokens
var _i = 0
var tok = null
while (_i < length(tokens)) {
tok = tokens[_i]
if (tok.from_row == line && tok.from_column <= col && tok.to_column >= col) {
return tok
}
if (tok.from_row < line && tok.to_row > line) {
return tok
}
if (tok.from_row < line && tok.to_row == line && tok.to_column >= col) {
return tok
}
if (tok.from_row == line && tok.to_row > line && tok.from_column <= col) {
return tok
}
_i = _i + 1
}
return null
}
return {
update: update,
remove: remove,
diagnostics: diagnostics,
token_at: token_at
}
}
return make

View File

@@ -0,0 +1,133 @@
// Completion provider for the ƿit LSP.
// CompletionItemKind constants (LSP spec)
def KIND_FUNCTION = 3
def KIND_VARIABLE = 6
def KIND_KEYWORD = 14
def KIND_CONSTANT = 21
// All intrinsic function names
def intrinsic_functions = [
"abs", "apply", "array", "ceiling", "character", "codepoint",
"ends_with", "every", "extract", "fallback", "filter", "find",
"floor", "format", "fraction",
"is_array", "is_blob", "is_character", "is_data", "is_digit",
"is_false", "is_fit", "is_function", "is_integer", "is_letter",
"is_logical", "is_lower", "is_null", "is_number", "is_object",
"is_pattern", "is_stone", "is_text", "is_true", "is_upper",
"is_whitespace",
"length", "logical", "lower", "max", "min", "modulo",
"neg", "normalize", "not", "number",
"parallel", "print", "race", "record", "reduce", "remainder",
"replace", "reverse", "round",
"search", "sequence", "sign", "some", "sort", "starts_with",
"stone", "text", "trim", "trunc", "upper", "whole",
"meme", "proto", "isa", "splat", "use"
]
// Keywords that can be completed
def keywords = [
"var", "def", "if", "else", "for", "while", "do",
"function", "return", "go", "break", "continue",
"disrupt", "disruption", "delete", "in", "this",
"null", "true", "false"
]
// Actor intrinsics (only in .ce files)
def actor_intrinsics = [
"$me", "$send", "$start", "$stop", "$delay",
"$receiver", "$clock", "$portal", "$contact",
"$couple", "$unneeded", "$connection", "$time_limit"
]
// Walk AST scopes to find variables visible at a position.
var collect_scope_vars = function(doc, line, col) {
var vars = []
var ast = doc.ast
var _i = 0
var _j = 0
var scope = null
var v = null
if (ast == null || ast.scopes == null) {
return vars
}
// Collect variables from all scopes (simplified: return all declared vars)
while (_i < length(ast.scopes)) {
scope = ast.scopes[_i]
if (scope.vars != null) {
_j = 0
while (_j < length(scope.vars)) {
v = scope.vars[_j]
if (v.name != null) {
vars[] = {
label: v.name,
kind: (v.is_const == true) ? KIND_CONSTANT : KIND_VARIABLE,
detail: (v.is_const == true) ? "def" : "var"
}
}
_j = _j + 1
}
}
_i = _i + 1
}
return vars
}
// Provide completions for a document at a position.
var complete = function(doc, line, col) {
var items = []
var _i = 0
var is_actor = ends_with(doc.uri, ".ce")
// Intrinsic functions
_i = 0
while (_i < length(intrinsic_functions)) {
items[] = {
label: intrinsic_functions[_i],
kind: KIND_FUNCTION,
detail: "intrinsic"
}
_i = _i + 1
}
// Keywords
_i = 0
while (_i < length(keywords)) {
items[] = {
label: keywords[_i],
kind: KIND_KEYWORD,
detail: "keyword"
}
_i = _i + 1
}
// Actor intrinsics (only for .ce files)
if (is_actor) {
_i = 0
while (_i < length(actor_intrinsics)) {
items[] = {
label: actor_intrinsics[_i],
kind: KIND_FUNCTION,
detail: "actor intrinsic"
}
_i = _i + 1
}
}
// Variables from scope analysis
var scope_vars = collect_scope_vars(doc, line, col)
_i = 0
while (_i < length(scope_vars)) {
items[] = scope_vars[_i]
_i = _i + 1
}
return items
}
return {
complete: complete
}

461
editors/vscode/lsp/hover.cm Normal file
View File

@@ -0,0 +1,461 @@
// Hover provider for the ƿit LSP.
// Shows documentation for intrinsic functions and variable info.
// Intrinsic function documentation database.
// Each entry: {signature, description}
def intrinsic_docs = {
abs: {
signature: "abs(number)",
description: "Absolute value. Returns null for non-numbers."
},
apply: {
signature: "apply(function, array)",
description: "Execute the function, passing array elements as input values."
},
array: {
signature: "array(value, ...)",
description: "Create arrays. Polymorphic: array(number) creates sized array, array(array) copies, array(array, fn) maps, array(text) splits into characters, array(text, sep) splits by separator."
},
ceiling: {
signature: "ceiling(number, place)",
description: "Round up. If place is 0 or null, round to smallest integer >= number."
},
character: {
signature: "character(value)",
description: "If text, returns the first character. If a non-negative integer, returns the character from that codepoint."
},
codepoint: {
signature: "codepoint(text)",
description: "Returns the codepoint number of the first character."
},
ends_with: {
signature: "ends_with(text, suffix)",
description: "Returns true if the text ends with the given suffix."
},
every: {
signature: "every(array, function)",
description: "Returns true if every element satisfies the predicate."
},
extract: {
signature: "extract(text, pattern, from, to)",
description: "Match text to pattern. Returns a record of saved fields, or null if no match."
},
fallback: {
signature: "fallback(requestor_array)",
description: "Returns a requestor that tries each requestor in order until one succeeds."
},
filter: {
signature: "filter(array, function)",
description: "Returns a new array containing elements for which function returns true."
},
find: {
signature: "find(array, function, reverse, from)",
description: "Returns the element number where function returns true, or null if not found. If second arg is not a function, compares directly."
},
floor: {
signature: "floor(number, place)",
description: "Round down. If place is 0 or null, round to greatest integer <= number."
},
format: {
signature: "format(text, collection, transformer)",
description: "Substitute {key} placeholders in text with values from a collection (array or record)."
},
fraction: {
signature: "fraction(number)",
description: "Returns the fractional part of a number."
},
is_array: {
signature: "is_array(value)",
description: "Returns true if the value is an array."
},
is_blob: {
signature: "is_blob(value)",
description: "Returns true if the value is a blob."
},
is_character: {
signature: "is_character(value)",
description: "Returns true if the value is a single character."
},
is_data: {
signature: "is_data(value)",
description: "Returns true if the value is data (not a function)."
},
is_digit: {
signature: "is_digit(value)",
description: "Returns true if the value is a digit character."
},
is_false: {
signature: "is_false(value)",
description: "Returns true if the value is false."
},
is_fit: {
signature: "is_fit(value)",
description: "Returns true if the value is a fit integer."
},
is_function: {
signature: "is_function(value)",
description: "Returns true if the value is a function."
},
is_integer: {
signature: "is_integer(value)",
description: "Returns true if the value is an integer."
},
is_letter: {
signature: "is_letter(value)",
description: "Returns true if the value is a letter character."
},
is_logical: {
signature: "is_logical(value)",
description: "Returns true if the value is a logical (boolean)."
},
is_lower: {
signature: "is_lower(value)",
description: "Returns true if the value is a lowercase character."
},
is_null: {
signature: "is_null(value)",
description: "Returns true if the value is null."
},
is_number: {
signature: "is_number(value)",
description: "Returns true if the value is a number."
},
is_object: {
signature: "is_object(value)",
description: "Returns true if the value is an object (record)."
},
is_pattern: {
signature: "is_pattern(value)",
description: "Returns true if the value is a pattern (regex)."
},
is_stone: {
signature: "is_stone(value)",
description: "Returns true if the value is frozen (stoned)."
},
is_text: {
signature: "is_text(value)",
description: "Returns true if the value is text."
},
is_true: {
signature: "is_true(value)",
description: "Returns true if the value is true."
},
is_upper: {
signature: "is_upper(value)",
description: "Returns true if the value is an uppercase character."
},
is_whitespace: {
signature: "is_whitespace(value)",
description: "Returns true if the value is a whitespace character."
},
length: {
signature: "length(value)",
description: "Array: number of elements. Text: number of codepoints. Function: arity. Blob: number of bits. Record: record.length()."
},
logical: {
signature: "logical(value)",
description: "Convert to logical. 0/false/null/\"false\" produce false; 1/true/\"true\" produce true."
},
lower: {
signature: "lower(text)",
description: "Returns text with all uppercase characters converted to lowercase."
},
max: {
signature: "max(number, number)",
description: "Returns the larger of two numbers."
},
min: {
signature: "min(number, number)",
description: "Returns the smaller of two numbers."
},
modulo: {
signature: "modulo(dividend, divisor)",
description: "Result has the sign of the divisor."
},
neg: {
signature: "neg(number)",
description: "Negate. Reverse the sign of a number."
},
normalize: {
signature: "normalize(text)",
description: "Unicode normalize."
},
not: {
signature: "not(logical)",
description: "Returns the opposite logical. Returns null for non-logicals."
},
number: {
signature: "number(value, radix_or_format)",
description: "Convert to number. Polymorphic: number(logical), number(text), number(text, radix), number(text, format)."
},
parallel: {
signature: "parallel(requestor_array, throttle, need)",
description: "Start all requestors concurrently. Optional throttle limits concurrency; optional need specifies minimum successes."
},
print: {
signature: "print(value)",
description: "Print a value to standard output."
},
race: {
signature: "race(requestor_array, throttle, need)",
description: "Like parallel but returns as soon as needed results are obtained. Default need is 1."
},
record: {
signature: "record(value, ...)",
description: "Create records. Polymorphic: record(record) copies, record(record, record) merges, record(array) creates from keys."
},
reduce: {
signature: "reduce(array, function, initial, reverse)",
description: "Reduce an array to a single value by applying a function to pairs of elements."
},
remainder: {
signature: "remainder(dividend, divisor)",
description: "For fit integers: dividend - ((dividend // divisor) * divisor)."
},
replace: {
signature: "replace(text, target, replacement, limit)",
description: "Return text with target replaced. Target can be text or pattern. Replacement can be text or function."
},
reverse: {
signature: "reverse(array)",
description: "Returns a new array with elements in the opposite order."
},
round: {
signature: "round(number, place)",
description: "Round to nearest."
},
search: {
signature: "search(text, target, from)",
description: "Search text for target. Returns character position or null."
},
sequence: {
signature: "sequence(requestor_array)",
description: "Process requestors in order. Each result becomes input to the next."
},
sign: {
signature: "sign(number)",
description: "Returns -1, 0, or 1."
},
some: {
signature: "some(array, function)",
description: "Returns true if any element satisfies the predicate."
},
sort: {
signature: "sort(array, select)",
description: "Returns a new sorted array. Sort keys must be all numbers or all texts. Ascending and stable."
},
starts_with: {
signature: "starts_with(text, prefix)",
description: "Returns true if the text starts with the given prefix."
},
stone: {
signature: "stone(value)",
description: "Petrify the value, making it permanently immutable. Deep freeze."
},
text: {
signature: "text(value, ...)",
description: "Convert to text. Polymorphic: text(array, sep) joins, text(number, radix/format) formats, text(text, from, to) substrings."
},
trim: {
signature: "trim(text, reject)",
description: "Remove characters from both ends. Default removes whitespace."
},
trunc: {
signature: "trunc(number, place)",
description: "Truncate toward zero."
},
upper: {
signature: "upper(text)",
description: "Returns text with all lowercase characters converted to uppercase."
},
whole: {
signature: "whole(number)",
description: "Returns the whole part of a number."
},
meme: {
signature: "meme()",
description: "Create a new meme (prototype chain marker)."
},
proto: {
signature: "proto(object, meme)",
description: "Set the prototype meme of an object."
},
isa: {
signature: "isa(object, meme)",
description: "Returns true if the object has the given meme in its prototype chain."
},
splat: {
signature: "splat(function, array)",
description: "Call function with array elements as separate arguments."
},
use: {
signature: "use(path)",
description: "Load a module. Returns the module's exported value. Modules are cached and frozen."
},
pi: {
signature: "pi",
description: "An approximation of circumference / diameter: 3.1415926535897932."
}
}
// Actor intrinsic documentation
def actor_docs = {
"$me": {
signature: "$me",
description: "The address of this actor."
},
"$send": {
signature: "$send(address, message)",
description: "Send a message to another actor."
},
"$start": {
signature: "$start(script, env)",
description: "Start a new actor from a script path."
},
"$stop": {
signature: "$stop()",
description: "Stop this actor."
},
"$delay": {
signature: "$delay(milliseconds)",
description: "Delay processing for a number of milliseconds."
},
"$receiver": {
signature: "$receiver(function)",
description: "Set the message receiver function for this actor."
},
"$clock": {
signature: "$clock(interval, message)",
description: "Send a message to self at regular intervals."
},
"$portal": {
signature: "$portal(name)",
description: "Create a named portal for inter-actor communication."
},
"$contact": {
signature: "$contact(portal_name)",
description: "Connect to a named portal."
},
"$couple": {
signature: "$couple(address)",
description: "Couple with another actor for lifecycle management."
},
"$unneeded": {
signature: "$unneeded(function)",
description: "Set a function to be called when this actor is no longer needed."
},
"$connection": {
signature: "$connection(address)",
description: "Establish a connection with another actor."
},
"$time_limit": {
signature: "$time_limit(milliseconds)",
description: "Set a time limit for this actor's execution."
}
}
// Provide hover info for a token.
var hover = function(doc, line, col, token_at) {
var tok = token_at(doc, line, col)
var info = null
var name = null
var _i = 0
var _j = 0
var scope = null
var v = null
if (tok == null) {
return null
}
// Check intrinsic functions
if (tok.kind == "name" && tok.value != null) {
name = tok.value
info = intrinsic_docs[name]
if (info != null) {
return {
contents: {
kind: "markdown",
value: `**${info.signature}**\n\n${info.description}`
}
}
}
}
// Check actor intrinsics ($name)
if (tok.value != null && starts_with(tok.value, "$")) {
info = actor_docs[tok.value]
if (info != null) {
return {
contents: {
kind: "markdown",
value: `**${info.signature}**\n\n${info.description}`
}
}
}
}
// Check keywords
if (tok.kind == "var" || tok.kind == "def") {
return {
contents: {
kind: "markdown",
value: (tok.kind == "var")
? "**var** — Declare a mutable variable."
: "**def** — Declare a constant."
}
}
}
if (tok.kind == "disrupt") {
return {
contents: {
kind: "markdown",
value: "**disrupt** — Raise an error. Use with **disruption** block to handle errors."
}
}
}
if (tok.kind == "disruption") {
return {
contents: {
kind: "markdown",
value: "**disruption** — Error handling block. Catches errors raised by **disrupt**."
}
}
}
// User variable: show declaration info from scope
if (tok.kind == "name" && tok.value != null && doc.ast != null && doc.ast.scopes != null) {
_i = 0
while (_i < length(doc.ast.scopes)) {
scope = doc.ast.scopes[_i]
if (scope.vars != null) {
_j = 0
while (_j < length(scope.vars)) {
v = scope.vars[_j]
if (v.name == tok.value) {
return {
contents: {
kind: "markdown",
value: (v.is_const == true)
? `**def** ${v.name}`
: `**var** ${v.name}`
}
}
}
_j = _j + 1
}
}
_i = _i + 1
}
}
return null
}
return {
hover: hover,
intrinsic_docs: intrinsic_docs,
actor_docs: actor_docs
}

209
editors/vscode/lsp/lsp.ce Normal file
View File

@@ -0,0 +1,209 @@
// ƿit Language Server Protocol (LSP) main loop.
// Communicates via JSON-RPC over stdin/stdout.
var fd = use('fd')
var json_mod = use('json')
var protocol = use('protocol')
var analysis_make = use('analysis')
var completions = use('completions')
var hover_mod = use('hover')
var symbols = use('symbols')
// Get tokenize_mod and parse_mod from the environment.
// These are the same functions the compiler uses internally.
var tokenize_mod = use('tokenize')
var parse_mod = use('parse')
// Create analysis module bound to tokenize/parse
var analysis = analysis_make(tokenize_mod, parse_mod)
// Document store: URI -> {text, version, ast, tokens, errors}
var docs = {}
// Log to stderr for debugging (does not interfere with protocol).
var log = function(msg) {
fd.write(2, `[pit-lsp] ${msg}\n`)
}
// Publish diagnostics for a document.
var publish_diagnostics = function(uri, doc) {
var diags = analysis.diagnostics(doc)
protocol.notify("textDocument/publishDiagnostics", {
uri: uri,
diagnostics: diags
})
}
// Parse a document and publish diagnostics.
var parse_and_notify = function(uri, src, version) {
var doc = analysis.update(docs, uri, {src: src, version: version})
publish_diagnostics(uri, doc)
}
// Handle initialize request.
var handle_initialize = function(id, params) {
protocol.respond(id, {
capabilities: {
textDocumentSync: {
openClose: true,
change: 1,
save: {includeText: true}
},
completionProvider: {
triggerCharacters: [".", "$"]
},
hoverProvider: true,
definitionProvider: true,
documentSymbolProvider: true
},
serverInfo: {
name: "pit-lsp",
version: "0.1.0"
}
})
}
// Handle textDocument/didOpen notification.
var handle_did_open = function(params) {
var td = params.textDocument
parse_and_notify(td.uri, td.text, td.version)
}
// Handle textDocument/didChange notification (full text sync).
var handle_did_change = function(params) {
var td = params.textDocument
var changes = params.contentChanges
if (length(changes) > 0) {
parse_and_notify(td.uri, changes[0].text, td.version)
}
}
// Handle textDocument/didClose notification.
var handle_did_close = function(params) {
var uri = params.textDocument.uri
analysis.remove(docs, uri)
// Clear diagnostics
protocol.notify("textDocument/publishDiagnostics", {
uri: uri,
diagnostics: []
})
}
// Handle textDocument/didSave notification.
var handle_did_save = function(params) {
var td = params.textDocument
if (params.text != null) {
parse_and_notify(td.uri, params.text, td.version)
}
}
// Handle textDocument/completion request.
var handle_completion = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var items = []
if (doc != null) {
items = completions.complete(doc, pos.line, pos.character)
}
protocol.respond(id, items)
}
// Handle textDocument/hover request.
var handle_hover = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var result = null
if (doc != null) {
result = hover_mod.hover(doc, pos.line, pos.character, analysis.token_at)
}
protocol.respond(id, result)
}
// Handle textDocument/definition request.
var handle_definition = function(id, params) {
var uri = params.textDocument.uri
var pos = params.position
var doc = docs[uri]
var result = null
if (doc != null) {
result = symbols.definition(doc, pos.line, pos.character, analysis.token_at)
}
protocol.respond(id, result)
}
// Handle textDocument/documentSymbol request.
var handle_document_symbol = function(id, params) {
var uri = params.textDocument.uri
var doc = docs[uri]
var result = []
if (doc != null) {
result = symbols.document_symbols(doc)
}
protocol.respond(id, result)
}
// Dispatch a single message. Wrapped in a function for disruption handling.
var dispatch_message = function(msg) {
var method = msg.method
if (method == "initialize") {
handle_initialize(msg.id, msg.params)
} else if (method == "initialized") {
// no-op
} else if (method == "textDocument/didOpen") {
handle_did_open(msg.params)
} else if (method == "textDocument/didChange") {
handle_did_change(msg.params)
} else if (method == "textDocument/didClose") {
handle_did_close(msg.params)
} else if (method == "textDocument/didSave") {
handle_did_save(msg.params)
} else if (method == "textDocument/completion") {
handle_completion(msg.id, msg.params)
} else if (method == "textDocument/hover") {
handle_hover(msg.id, msg.params)
} else if (method == "textDocument/definition") {
handle_definition(msg.id, msg.params)
} else if (method == "textDocument/documentSymbol") {
handle_document_symbol(msg.id, msg.params)
} else if (method == "shutdown") {
protocol.respond(msg.id, null)
return "shutdown"
} else if (method == "exit") {
return "exit"
} else {
if (msg.id != null) {
protocol.respond_error(msg.id, -32601, `Method not found: ${method}`)
}
}
return null
} disruption {
log(`error handling ${msg.method}`)
if (msg.id != null) {
protocol.respond_error(msg.id, -32603, `Internal error handling ${msg.method}`)
}
return null
}
// Main loop.
log("starting")
var running = true
var msg = null
var result = null
while (running) {
msg = protocol.read_message()
if (msg == null) {
running = false
break
}
result = dispatch_message(msg)
if (result == "exit") {
running = false
}
}
log("stopped")

View File

@@ -0,0 +1,102 @@
// JSON-RPC protocol helpers for LSP communication over stdin/stdout.
// Reads Content-Length framed messages from stdin, writes to stdout.
var fd = use('fd')
var json = use('json')
// Read a single JSON-RPC message from stdin.
// Protocol: "Content-Length: N\r\n\r\n" followed by N bytes of JSON.
var read_message = function() {
var header = ""
var ch = null
var content_length = null
var body = null
var total = 0
var chunk = null
// Read header byte by byte until we hit \r\n\r\n
while (true) {
ch = fd.read(0, 1)
if (ch == null) {
return null
}
header = header + text(ch)
if (ends_with(header, "\r\n\r\n")) {
break
}
}
// Parse Content-Length from header
var lines = array(header, "\r\n")
var _i = 0
while (_i < length(lines)) {
if (starts_with(lines[_i], "Content-Length:")) {
content_length = number(trim(text(lines[_i], 16)))
}
_i = _i + 1
}
if (content_length == null) {
return null
}
// Read exactly content_length bytes
body = ""
total = 0
while (total < content_length) {
chunk = fd.read(0, content_length - total)
if (chunk == null) {
return null
}
chunk = text(chunk)
body = body + chunk
total = total + length(chunk)
}
return json.decode(body)
}
// Send a JSON-RPC message to stdout.
var send_message = function(msg) {
var body = json.encode(msg)
var header = `Content-Length: ${text(length(body))}\r\n\r\n`
fd.write(1, header + body)
}
// Send a JSON-RPC response for a request.
var respond = function(id, result) {
send_message({
jsonrpc: "2.0",
id: id,
result: result
})
}
// Send a JSON-RPC error response.
var respond_error = function(id, code, message) {
send_message({
jsonrpc: "2.0",
id: id,
error: {
code: code,
message: message
}
})
}
// Send a JSON-RPC notification (no id).
var notify = function(method, params) {
send_message({
jsonrpc: "2.0",
method: method,
params: params
})
}
return {
read_message: read_message,
send_message: send_message,
respond: respond,
respond_error: respond_error,
notify: notify
}

View File

@@ -0,0 +1,238 @@
// Document symbols and go-to-definition provider for the ƿit LSP.
// SymbolKind constants (LSP spec)
def KIND_FUNCTION = 12
def KIND_VARIABLE = 13
def KIND_CONSTANT = 14
// Walk AST to extract document symbols (top-level vars/defs and functions).
var document_symbols = function(doc) {
var symbols = []
var ast = doc.ast
var _i = 0
var _j = 0
var stmt = null
var decl = null
var name = null
var kind = null
var range = null
if (ast == null || ast.statements == null) {
return symbols
}
while (_i < length(ast.statements)) {
stmt = ast.statements[_i]
if (stmt.kind == "var" || stmt.kind == "def") {
name = null
kind = KIND_VARIABLE
if (stmt.left != null && stmt.left.name != null) {
name = stmt.left.name
}
if (stmt.kind == "def") {
kind = KIND_CONSTANT
}
if (stmt.right != null && (stmt.right.kind == "function" || stmt.right.kind == "arrow function")) {
kind = KIND_FUNCTION
}
if (name != null) {
range = {
start: {line: stmt.from_row, character: stmt.from_column},
end: {line: stmt.to_row, character: stmt.to_column}
}
symbols[] = {
name: name,
kind: kind,
range: range,
selectionRange: {
start: {line: stmt.left.from_row, character: stmt.left.from_column},
end: {line: stmt.left.to_row, character: stmt.left.to_column}
}
}
}
}
if (stmt.kind == "var_list" && stmt.list != null) {
_j = 0
while (_j < length(stmt.list)) {
decl = stmt.list[_j]
if (decl.left != null && decl.left.name != null) {
kind = (decl.kind == "def") ? KIND_CONSTANT : KIND_VARIABLE
if (decl.right != null && (decl.right.kind == "function" || decl.right.kind == "arrow function")) {
kind = KIND_FUNCTION
}
range = {
start: {line: decl.from_row, character: decl.from_column},
end: {line: decl.to_row, character: decl.to_column}
}
symbols[] = {
name: decl.left.name,
kind: kind,
range: range,
selectionRange: {
start: {line: decl.left.from_row, character: decl.left.from_column},
end: {line: decl.left.to_row, character: decl.left.to_column}
}
}
}
_j = _j + 1
}
}
_i = _i + 1
}
return symbols
}
// Find the declaration location of a name at a given position.
var definition = function(doc, line, col, token_at) {
var tok = token_at(doc, line, col)
var ast = doc.ast
var name = null
var _i = 0
var _j = 0
var scope = null
var v = null
var decl = null
if (tok == null || tok.kind != "name" || tok.value == null) {
return null
}
name = tok.value
if (ast == null) {
return null
}
// Search through scopes for the variable declaration
if (ast.scopes != null) {
_i = 0
while (_i < length(ast.scopes)) {
scope = ast.scopes[_i]
if (scope.vars != null) {
_j = 0
while (_j < length(scope.vars)) {
v = scope.vars[_j]
if (v.name == name) {
decl = find_declaration(ast.statements, name)
if (decl != null) {
return {
uri: doc.uri,
range: {
start: {line: decl.from_row, character: decl.from_column},
end: {line: decl.to_row, character: decl.to_column}
}
}
}
}
_j = _j + 1
}
}
_i = _i + 1
}
}
// Fallback: walk statements for var/def with this name
decl = find_declaration(ast.statements, name)
if (decl != null) {
return {
uri: doc.uri,
range: {
start: {line: decl.from_row, character: decl.from_column},
end: {line: decl.to_row, character: decl.to_column}
}
}
}
return null
}
// Recursively search statements for a var/def declaration of a given name.
var find_declaration = function(statements, name) {
var _i = 0
var _j = 0
var stmt = null
var result = null
if (statements == null) {
return null
}
while (_i < length(statements)) {
stmt = statements[_i]
// Direct var/def
if ((stmt.kind == "var" || stmt.kind == "def")
&& stmt.left != null && stmt.left.name == name) {
return stmt
}
// var_list
if (stmt.kind == "var_list" && stmt.list != null) {
_j = 0
while (_j < length(stmt.list)) {
if (stmt.list[_j].left != null && stmt.list[_j].left.name == name) {
return stmt.list[_j]
}
_j = _j + 1
}
}
// Recurse into blocks
if (stmt.statements != null) {
result = find_declaration(stmt.statements, name)
if (result != null) {
return result
}
}
// if/else
if (stmt.kind == "if") {
if (stmt.then != null && stmt.then.statements != null) {
result = find_declaration(stmt.then.statements, name)
if (result != null) {
return result
}
}
if (stmt.else != null && stmt.else.statements != null) {
result = find_declaration(stmt.else.statements, name)
if (result != null) {
return result
}
}
}
// Function body
if ((stmt.kind == "function" || stmt.kind == "arrow function") && stmt.statements != null) {
result = find_declaration(stmt.statements, name)
if (result != null) {
return result
}
}
// var/def with function right side
if ((stmt.kind == "var" || stmt.kind == "def") && stmt.right != null) {
if ((stmt.right.kind == "function" || stmt.right.kind == "arrow function") && stmt.right.statements != null) {
result = find_declaration(stmt.right.statements, name)
if (result != null) {
return result
}
}
}
_i = _i + 1
}
return null
}
return {
document_symbols: document_symbols,
definition: definition
}

View File

@@ -0,0 +1,62 @@
{
"name": "pit-language",
"displayName": "ƿit Language",
"description": "Language support for ƿit (.ce/.cm) — syntax highlighting, diagnostics, completions, hover, and go-to-definition",
"version": "0.1.0",
"publisher": "pit-lang",
"engines": {
"vscode": "^1.75.0"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"onLanguage:pit"
],
"main": "./out/extension.js",
"contributes": {
"languages": [
{
"id": "pit",
"aliases": [
"ƿit",
"pit"
],
"extensions": [
".ce",
".cm"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "pit",
"scopeName": "source.pit",
"path": "./syntaxes/pit.tmLanguage.json"
}
],
"configuration": {
"title": "ƿit",
"properties": {
"pit.cellPath": {
"type": "string",
"default": "cell",
"description": "Path to the cell executable"
}
}
}
},
"scripts": {
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./"
},
"dependencies": {
"vscode-languageclient": "^9.0.0",
"vscode-languageserver-protocol": "^3.17.0"
},
"devDependencies": {
"@types/vscode": "^1.75.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,44 @@
import * as path from "path";
import { workspace, ExtensionContext } from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
} from "vscode-languageclient/node";
let client: LanguageClient;
export function activate(context: ExtensionContext) {
const config = workspace.getConfiguration("pit");
const cellPath = config.get<string>("cellPath", "cell");
const lspDir = path.join(context.extensionPath, "lsp");
const serverOptions: ServerOptions = {
command: cellPath,
args: ["lsp/lsp"],
options: { cwd: lspDir },
};
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "pit" }],
synchronize: {
fileEvents: workspace.createFileSystemWatcher("**/*.{ce,cm}"),
},
};
client = new LanguageClient(
"pitLanguageServer",
"ƿit Language Server",
serverOptions,
clientOptions
);
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}

View File

@@ -0,0 +1,160 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "pit",
"scopeName": "source.pit",
"patterns": [
{ "include": "#comment-line" },
{ "include": "#comment-block" },
{ "include": "#string-template" },
{ "include": "#string-double" },
{ "include": "#regexp" },
{ "include": "#keyword-control" },
{ "include": "#keyword-error" },
{ "include": "#storage-type" },
{ "include": "#constant-language" },
{ "include": "#variable-language" },
{ "include": "#actor-intrinsic" },
{ "include": "#keyword-operator" },
{ "include": "#arrow-function" },
{ "include": "#support-function" },
{ "include": "#constant-numeric-hex" },
{ "include": "#constant-numeric-binary" },
{ "include": "#constant-numeric-octal" },
{ "include": "#constant-numeric" },
{ "include": "#punctuation" }
],
"repository": {
"comment-line": {
"name": "comment.line.double-slash.pit",
"match": "//.*$"
},
"comment-block": {
"name": "comment.block.pit",
"begin": "/\\*",
"end": "\\*/",
"beginCaptures": { "0": { "name": "punctuation.definition.comment.begin.pit" } },
"endCaptures": { "0": { "name": "punctuation.definition.comment.end.pit" } }
},
"string-double": {
"name": "string.quoted.double.pit",
"begin": "\"",
"end": "\"",
"beginCaptures": { "0": { "name": "punctuation.definition.string.begin.pit" } },
"endCaptures": { "0": { "name": "punctuation.definition.string.end.pit" } },
"patterns": [
{
"name": "constant.character.escape.pit",
"match": "\\\\(?:[\"\\\\bfnrt/]|u[0-9a-fA-F]{4})"
}
]
},
"string-template": {
"name": "string.template.pit",
"begin": "`",
"end": "`",
"beginCaptures": { "0": { "name": "punctuation.definition.string.template.begin.pit" } },
"endCaptures": { "0": { "name": "punctuation.definition.string.template.end.pit" } },
"patterns": [
{
"name": "constant.character.escape.pit",
"match": "\\\\(?:[`\\\\bfnrt/$]|u[0-9a-fA-F]{4})"
},
{
"name": "meta.template.expression.pit",
"begin": "\\$\\{",
"end": "\\}",
"beginCaptures": { "0": { "name": "punctuation.definition.template-expression.begin.pit" } },
"endCaptures": { "0": { "name": "punctuation.definition.template-expression.end.pit" } },
"patterns": [
{ "include": "source.pit" }
]
}
]
},
"regexp": {
"name": "string.regexp.pit",
"begin": "(?<=[=(:,;!&|?~^>]|^|return|disrupt)\\s*(/(?![/*]))",
"end": "/([gimsuvy]*)",
"beginCaptures": { "1": { "name": "punctuation.definition.string.begin.pit" } },
"endCaptures": { "1": { "name": "keyword.other.pit" } },
"patterns": [
{
"name": "constant.character.escape.pit",
"match": "\\\\."
}
]
},
"keyword-control": {
"name": "keyword.control.pit",
"match": "\\b(if|else|for|while|do|break|continue|return|go)\\b"
},
"keyword-error": {
"name": "keyword.control.error.pit",
"match": "\\b(disrupt|disruption)\\b"
},
"storage-type": {
"patterns": [
{
"name": "storage.type.pit",
"match": "\\b(var|def)\\b"
},
{
"name": "storage.type.function.pit",
"match": "\\bfunction\\b"
}
]
},
"constant-language": {
"name": "constant.language.pit",
"match": "\\b(null|true|false)\\b"
},
"variable-language": {
"name": "variable.language.this.pit",
"match": "\\bthis\\b"
},
"actor-intrinsic": {
"name": "variable.language.actor.pit",
"match": "\\$[a-zA-Z_][a-zA-Z0-9_]*"
},
"keyword-operator": {
"name": "keyword.operator.pit",
"match": "\\b(delete|in|typeof)\\b"
},
"arrow-function": {
"name": "storage.type.function.arrow.pit",
"match": "=>"
},
"support-function": {
"name": "support.function.pit",
"match": "\\b(abs|apply|array|ceiling|character|codepoint|ends_with|every|extract|fallback|filter|find|floor|for|format|fraction|is_array|is_blob|is_character|is_data|is_digit|is_false|is_fit|is_function|is_integer|is_letter|is_logical|is_lower|is_null|is_number|is_object|is_pattern|is_stone|is_text|is_true|is_upper|is_whitespace|length|logical|lower|max|min|modulo|neg|normalize|not|number|parallel|print|race|record|reduce|remainder|replace|reverse|round|search|sequence|sign|some|sort|starts_with|stone|text|trim|trunc|upper|whole|meme|proto|isa|splat|use)(?=\\s*\\()"
},
"constant-numeric-hex": {
"name": "constant.numeric.hex.pit",
"match": "\\b0[xX][0-9a-fA-F]+\\b"
},
"constant-numeric-binary": {
"name": "constant.numeric.binary.pit",
"match": "\\b0[bB][01]+\\b"
},
"constant-numeric-octal": {
"name": "constant.numeric.octal.pit",
"match": "\\b0[oO][0-7]+\\b"
},
"constant-numeric": {
"name": "constant.numeric.pit",
"match": "\\b[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?\\b"
},
"punctuation": {
"patterns": [
{
"name": "punctuation.separator.comma.pit",
"match": ","
},
{
"name": "punctuation.terminator.statement.pit",
"match": ";"
}
]
}
}
}

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020"],
"sourceMap": true,
"rootDir": "src",
"strict": true
},
"include": ["src"],
"exclude": ["node_modules", "out"]
}

13
fold.ce Normal file
View File

@@ -0,0 +1,13 @@
var fd = use("fd")
var json = use("json")
var filename = args[0]
var src = text(fd.slurp(filename))
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var tok_result = tokenize(src, filename)
var ast = parse(tok_result.tokens, src, filename, tokenize)
var folded = fold(ast)
print(json.encode(folded))

968
fold.cm Normal file
View File

@@ -0,0 +1,968 @@
// fold.cm — AST optimization pass
// Constant folding, constant propagation, dead code elimination
var fold = function(ast) {
var scopes = ast.scopes
var nr_scopes = length(scopes)
// ============================================================
// Helpers
// ============================================================
var is_literal = function(expr) {
if (expr == null) return false
var k = expr.kind
return k == "number" || k == "text" || k == "true" || k == "false" || k == "null"
}
var is_pure = function(expr) {
if (expr == null) return true
var k = expr.kind
var i = 0
if (k == "number" || k == "text" || k == "true" || k == "false" ||
k == "null" || k == "name" || k == "this") return true
if (k == "function") return true
if (k == "!" || k == "~" || k == "-unary" || k == "+unary") {
return is_pure(expr.expression)
}
if (k == "array") {
i = 0
while (i < length(expr.list)) {
if (!is_pure(expr.list[i])) return false
i = i + 1
}
return true
}
if (k == "record") {
i = 0
while (i < length(expr.list)) {
if (!is_pure(expr.list[i].right)) return false
i = i + 1
}
return true
}
if (k == "then") {
return is_pure(expr.expression) && is_pure(expr.then) && is_pure(expr.else)
}
if (k == "==" || k == "!=" || k == "&&" || k == "||") {
return is_pure(expr.left) && is_pure(expr.right)
}
return false
}
var copy_loc = function(from, to) {
to.at = from.at
to.from_row = from.from_row
to.from_column = from.from_column
to.to_row = from.to_row
to.to_column = from.to_column
return to
}
var make_number = function(val, src) {
return copy_loc(src, {kind: "number", value: text(val), number: val})
}
var make_text = function(val, src) {
return copy_loc(src, {kind: "text", value: val})
}
var make_bool = function(val, src) {
if (val) return copy_loc(src, {kind: "true"})
return copy_loc(src, {kind: "false"})
}
var make_null = function(src) {
return copy_loc(src, {kind: "null"})
}
var is_truthy_literal = function(expr) {
if (expr == null) return null
var k = expr.kind
var nv = null
if (k == "true") return true
if (k == "false" || k == "null") return false
if (k == "number") {
nv = expr.number
if (nv == null) nv = number(expr.value)
return nv != 0
}
if (k == "text") return length(expr.value) > 0
return null
}
// ============================================================
// Scope helpers
// ============================================================
var find_scope = function(fn_nr) {
var i = 0
while (i < nr_scopes) {
if (scopes[i].function_nr == fn_nr) return scopes[i]
i = i + 1
}
return null
}
var scope_var = function(fn_nr, name) {
var sc = find_scope(fn_nr)
if (sc == null) return null
return sc[name]
}
var remove_scope_var = function(fn_nr, name) {
var sc = find_scope(fn_nr)
if (sc == null) return null
delete sc[name]
}
// ============================================================
// Pass 1: pre-scan for constants and function arities
// ============================================================
var const_defs = {}
var fn_arities = {}
var register_const = function(fn_nr, name, lit_node) {
var key = text(fn_nr)
if (const_defs[key] == null) const_defs[key] = {}
const_defs[key][name] = lit_node
}
var get_const = function(fn_nr, name) {
var key = text(fn_nr)
if (const_defs[key] == null) return null
return const_defs[key][name]
}
var register_arity = function(fn_nr, name, count) {
var key = text(fn_nr)
if (fn_arities[key] == null) fn_arities[key] = {}
fn_arities[key][name] = count
}
var pre_scan_stmts = null
var pre_scan_fn = null
pre_scan_fn = function(node) {
if (node == null) return null
if (node.statements != null) pre_scan_stmts(node.statements, node.function_nr)
if (node.disruption != null) pre_scan_stmts(node.disruption, node.function_nr)
}
pre_scan_stmts = function(stmts, fn_nr) {
var i = 0
var j = 0
var stmt = null
var kind = null
var name = null
var sv = null
var item = null
while (i < length(stmts)) {
stmt = stmts[i]
kind = stmt.kind
if (kind == "def") {
name = stmt.left.name
if (name != null && is_literal(stmt.right)) {
sv = scope_var(fn_nr, name)
if (sv != null && !sv.closure) {
register_const(fn_nr, name, stmt.right)
}
}
} else if (kind == "function") {
name = stmt.name
if (name != null && stmt.arity != null) {
register_arity(fn_nr, name, stmt.arity)
}
pre_scan_fn(stmt)
} else if (kind == "var") {
if (stmt.right != null && stmt.right.kind == "function" && stmt.right.arity != null) {
name = stmt.left.name
if (name != null) {
sv = scope_var(fn_nr, name)
if (sv != null && sv.make == "var") {
register_arity(fn_nr, name, stmt.right.arity)
}
}
}
} else if (kind == "var_list") {
j = 0
while (j < length(stmt.list)) {
item = stmt.list[j]
if (item.kind == "var" && item.right != null && item.right.kind == "function" && item.right.arity != null) {
name = item.left.name
if (name != null) {
sv = scope_var(fn_nr, name)
if (sv != null && sv.make == "var") {
register_arity(fn_nr, name, item.right.arity)
}
}
}
j = j + 1
}
}
i = i + 1
}
}
var pre_scan_expr_fns = null
pre_scan_expr_fns = function(expr) {
if (expr == null) return null
var k = expr.kind
var i = 0
if (k == "function") {
pre_scan_fn(expr)
}
if (expr.left != null) pre_scan_expr_fns(expr.left)
if (expr.right != null) pre_scan_expr_fns(expr.right)
if (expr.expression != null) pre_scan_expr_fns(expr.expression)
if (expr.then != null) pre_scan_expr_fns(expr.then)
if (expr.else != null) pre_scan_expr_fns(expr.else)
if (k == "(" || k == "array") {
i = 0
while (i < length(expr.list)) {
pre_scan_expr_fns(expr.list[i])
i = i + 1
}
}
if (k == "record") {
i = 0
while (i < length(expr.list)) {
pre_scan_expr_fns(expr.list[i].right)
i = i + 1
}
}
}
var pre_scan_stmt_exprs = null
pre_scan_stmt_exprs = function(stmts, fn_nr) {
var i = 0
var j = 0
var stmt = null
var kind = null
while (i < length(stmts)) {
stmt = stmts[i]
kind = stmt.kind
if (kind == "var" || kind == "def") {
pre_scan_expr_fns(stmt.right)
} else if (kind == "var_list") {
j = 0
while (j < length(stmt.list)) {
pre_scan_expr_fns(stmt.list[j].right)
j = j + 1
}
} else if (kind == "call") {
pre_scan_expr_fns(stmt.expression)
} else if (kind == "if") {
pre_scan_expr_fns(stmt.expression)
pre_scan_stmt_exprs(stmt.then, fn_nr)
pre_scan_stmt_exprs(stmt.list, fn_nr)
if (stmt.else != null) pre_scan_stmt_exprs(stmt.else, fn_nr)
} else if (kind == "while" || kind == "do") {
pre_scan_expr_fns(stmt.expression)
pre_scan_stmt_exprs(stmt.statements, fn_nr)
} else if (kind == "for") {
if (stmt.init != null) {
if (stmt.init.kind == "var" || stmt.init.kind == "def") {
pre_scan_expr_fns(stmt.init.right)
} else {
pre_scan_expr_fns(stmt.init)
}
}
pre_scan_expr_fns(stmt.test)
pre_scan_expr_fns(stmt.update)
pre_scan_stmt_exprs(stmt.statements, fn_nr)
} else if (kind == "return" || kind == "go") {
pre_scan_expr_fns(stmt.expression)
} else if (kind == "block") {
pre_scan_stmt_exprs(stmt.statements, fn_nr)
} else if (kind == "label") {
if (stmt.statement != null) {
pre_scan_stmt_exprs([stmt.statement], fn_nr)
}
} else if (kind == "function") {
// already handled in pre_scan_stmts
null
}
i = i + 1
}
}
var pre_scan = function() {
pre_scan_stmts(ast.statements, 0)
pre_scan_stmts(ast.functions, 0)
pre_scan_stmt_exprs(ast.statements, 0)
pre_scan_stmt_exprs(ast.functions, 0)
}
// ============================================================
// Pass 2: fold expressions and statements
// ============================================================
var fold_expr = null
var fold_stmt = null
var fold_stmts = null
fold_expr = function(expr, fn_nr) {
if (expr == null) return null
var k = expr.kind
var left = null
var right = null
var lv = null
var rv = null
var result = null
var i = 0
var sv = null
var lit = null
var cond_k = null
var ek = null
var target = null
var ar = null
var akey = null
var tv = null
// Recurse into children first (bottom-up)
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" ||
k == "**" || k == "==" || k == "!=" || k == "<" || k == ">" ||
k == "<=" || k == ">=" || k == "&" || k == "|" || k == "^" ||
k == "<<" || k == ">>" || k == ">>>" || k == "&&" || k == "||" ||
k == "," || k == "in") {
expr.left = fold_expr(expr.left, fn_nr)
expr.right = fold_expr(expr.right, fn_nr)
} else if (k == "." || k == "[") {
expr.left = fold_expr(expr.left, fn_nr)
if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, fn_nr)
} else if (k == "!" || k == "~" || k == "-unary" || k == "+unary" || k == "delete") {
expr.expression = fold_expr(expr.expression, fn_nr)
} else if (k == "++" || k == "--") {
return expr
} else if (k == "then") {
expr.expression = fold_expr(expr.expression, fn_nr)
expr.then = fold_expr(expr.then, fn_nr)
expr.else = fold_expr(expr.else, fn_nr)
} else if (k == "(") {
expr.expression = fold_expr(expr.expression, fn_nr)
i = 0
while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1
}
} else if (k == "array") {
i = 0
while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1
}
} else if (k == "record") {
i = 0
while (i < length(expr.list)) {
expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
i = i + 1
}
} else if (k == "text literal") {
i = 0
while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1
}
} else if (k == "function") {
fold_fn(expr)
return expr
} else if (k == "assign" || k == "+=" || k == "-=" || k == "*=" ||
k == "/=" || k == "%=" || k == "<<=" || k == ">>=" ||
k == ">>>=" || k == "&=" || k == "^=" || k == "|=" ||
k == "**=" || k == "&&=" || k == "||=") {
expr.right = fold_expr(expr.right, fn_nr)
return expr
}
// Constant propagation: name → literal
if (k == "name" && expr.level == 0) {
lit = get_const(fn_nr, expr.name)
if (lit != null) {
sv = scope_var(fn_nr, expr.name)
if (sv != null && !sv.closure) {
return copy_loc(expr, {kind: lit.kind, value: lit.value, number: lit.number})
}
}
return expr
}
// Binary constant folding
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || k == "**") {
left = expr.left
right = expr.right
if (left != null && right != null && left.kind == "number" && right.kind == "number") {
lv = left.number
rv = right.number
if (lv == null) lv = number(left.value)
if (rv == null) rv = number(right.value)
if (k == "/") {
if (rv == 0) return make_null(expr)
}
if (k == "%") {
if (rv == 0) return make_null(expr)
}
result = null
if (k == "+") result = lv + rv
else if (k == "-") result = lv - rv
else if (k == "*") result = lv * rv
else if (k == "/") result = lv / rv
else if (k == "%") result = lv % rv
else if (k == "**") result = lv ** rv
if (result == null) return make_null(expr)
return make_number(result, expr)
}
// text + text
if (k == "+" && left != null && right != null && left.kind == "text" && right.kind == "text") {
return make_text(left.value + right.value, expr)
}
return expr
}
// Comparison folding
if (k == "==" || k == "!=" || k == "<" || k == ">" || k == "<=" || k == ">=") {
left = expr.left
right = expr.right
if (left != null && right != null) {
if (left.kind == "number" && right.kind == "number") {
lv = left.number
rv = right.number
if (lv == null) lv = number(left.value)
if (rv == null) rv = number(right.value)
if (k == "==") return make_bool(lv == rv, expr)
if (k == "!=") return make_bool(lv != rv, expr)
if (k == "<") return make_bool(lv < rv, expr)
if (k == ">") return make_bool(lv > rv, expr)
if (k == "<=") return make_bool(lv <= rv, expr)
if (k == ">=") return make_bool(lv >= rv, expr)
}
if (left.kind == "text" && right.kind == "text") {
if (k == "==") return make_bool(left.value == right.value, expr)
if (k == "!=") return make_bool(left.value != right.value, expr)
}
}
return expr
}
// Bitwise folding
if (k == "&" || k == "|" || k == "^" || k == "<<" || k == ">>") {
left = expr.left
right = expr.right
if (left != null && right != null && left.kind == "number" && right.kind == "number") {
lv = left.number
rv = right.number
if (lv == null) lv = number(left.value)
if (rv == null) rv = number(right.value)
if (k == "&") return make_number(lv & rv, expr)
if (k == "|") return make_number(lv | rv, expr)
if (k == "^") return make_number(lv ^ rv, expr)
if (k == "<<") return make_number(lv << rv, expr)
if (k == ">>") return make_number(lv >> rv, expr)
}
return expr
}
// Unary folding
if (k == "!") {
if (expr.expression != null) {
ek = expr.expression.kind
if (ek == "true") return make_bool(false, expr)
if (ek == "false") return make_bool(true, expr)
}
return expr
}
if (k == "~") {
if (expr.expression != null && expr.expression.kind == "number") {
lv = expr.expression.number
if (lv == null) lv = number(expr.expression.value)
return make_number(~lv, expr)
}
return expr
}
if (k == "-unary") {
if (expr.expression != null && expr.expression.kind == "number") {
lv = expr.expression.number
if (lv == null) lv = number(expr.expression.value)
return make_number(0 - lv, expr)
}
return expr
}
// Ternary with literal condition
if (k == "then") {
tv = is_truthy_literal(expr.expression)
if (tv == true) return expr.then
if (tv == false) return expr.else
return expr
}
// Call: stamp arity
if (k == "(") {
target = expr.expression
if (target != null && target.kind == "name" && target.level == 0) {
ar = null
akey = text(fn_nr)
if (fn_arities[akey] != null) ar = fn_arities[akey][target.name]
if (ar != null) expr.arity = ar
}
return expr
}
return expr
}
var fold_fn = null
fold_stmt = function(stmt, fn_nr) {
if (stmt == null) return null
var k = stmt.kind
var i = 0
var sv = null
var cond_k = null
var ik = null
var tv = null
if (k == "var" || k == "def") {
stmt.right = fold_expr(stmt.right, fn_nr)
return stmt
}
if (k == "var_list") {
i = 0
while (i < length(stmt.list)) {
stmt.list[i] = fold_stmt(stmt.list[i], fn_nr)
i = i + 1
}
return stmt
}
if (k == "call") {
stmt.expression = fold_expr(stmt.expression, fn_nr)
return stmt
}
if (k == "if") {
stmt.expression = fold_expr(stmt.expression, fn_nr)
tv = is_truthy_literal(stmt.expression)
if (tv == true) {
stmt.then = fold_stmts(stmt.then, fn_nr)
return {kind: "block", statements: stmt.then,
at: stmt.at, from_row: stmt.from_row, from_column: stmt.from_column,
to_row: stmt.to_row, to_column: stmt.to_column}
}
if (tv == false) {
if (stmt.else != null && length(stmt.else) > 0) {
stmt.else = fold_stmts(stmt.else, fn_nr)
return {kind: "block", statements: stmt.else,
at: stmt.at, from_row: stmt.from_row, from_column: stmt.from_column,
to_row: stmt.to_row, to_column: stmt.to_column}
}
if (stmt.list != null && length(stmt.list) > 0) {
return fold_stmt(stmt.list[0], fn_nr)
}
return null
}
stmt.then = fold_stmts(stmt.then, fn_nr)
stmt.list = fold_stmts(stmt.list, fn_nr)
if (stmt.else != null) stmt.else = fold_stmts(stmt.else, fn_nr)
return stmt
}
if (k == "while") {
stmt.expression = fold_expr(stmt.expression, fn_nr)
if (stmt.expression.kind == "false" || stmt.expression.kind == "null") return null
stmt.statements = fold_stmts(stmt.statements, fn_nr)
return stmt
}
if (k == "do") {
stmt.statements = fold_stmts(stmt.statements, fn_nr)
stmt.expression = fold_expr(stmt.expression, fn_nr)
return stmt
}
if (k == "for") {
if (stmt.init != null) {
ik = stmt.init.kind
if (ik == "var" || ik == "def") {
stmt.init = fold_stmt(stmt.init, fn_nr)
} else {
stmt.init = fold_expr(stmt.init, fn_nr)
}
}
if (stmt.test != null) stmt.test = fold_expr(stmt.test, fn_nr)
if (stmt.update != null) stmt.update = fold_expr(stmt.update, fn_nr)
stmt.statements = fold_stmts(stmt.statements, fn_nr)
return stmt
}
if (k == "return" || k == "go") {
stmt.expression = fold_expr(stmt.expression, fn_nr)
return stmt
}
if (k == "block") {
stmt.statements = fold_stmts(stmt.statements, fn_nr)
return stmt
}
if (k == "label") {
stmt.statement = fold_stmt(stmt.statement, fn_nr)
return stmt
}
if (k == "function") {
fold_fn(stmt)
return stmt
}
return stmt
}
fold_stmts = function(stmts, fn_nr) {
var i = 0
var stmt = null
var out = []
var sv = null
var name = null
while (i < length(stmts)) {
stmt = fold_stmt(stmts[i], fn_nr)
if (stmt == null) {
i = i + 1
continue
}
// Dead code elimination: unused pure var/def
if (stmt.kind == "var" || stmt.kind == "def") {
name = stmt.left.name
if (name != null) {
sv = scope_var(fn_nr, name)
if (sv != null && sv.nr_uses == 0 && is_pure(stmt.right)) {
stmt.dead = true
}
}
}
// Dead function elimination
if (stmt.kind == "function" && stmt.name != null) {
sv = scope_var(fn_nr, stmt.name)
if (sv != null && sv.nr_uses == 0) {
stmt.dead = true
}
}
if (stmt.dead != true) push(out, stmt)
i = i + 1
}
return out
}
fold_fn = function(node) {
if (node == null) return null
var fn_nr = node.function_nr
if (fn_nr == null) return null
// Fold param defaults
var i = 0
while (i < length(node.list)) {
if (node.list[i].expression != null) {
node.list[i].expression = fold_expr(node.list[i].expression, fn_nr)
}
i = i + 1
}
if (node.statements != null) node.statements = fold_stmts(node.statements, fn_nr)
if (node.disruption != null) node.disruption = fold_stmts(node.disruption, fn_nr)
}
// ============================================================
// Pass 3: cleanup scopes
// ============================================================
var cleanup = function() {
var i = 0
var sc = null
var keys = null
var j = 0
var key = null
var entry = null
var slots = 0
var close_slots = 0
// Remove dead vars from scope records and recalculate slot counts
while (i < nr_scopes) {
sc = scopes[i]
keys = array(sc)
slots = 0
close_slots = 0
j = 0
while (j < length(keys)) {
key = keys[j]
if (key != "function_nr") {
entry = sc[key]
if (entry != null && entry.nr_uses == 0 && entry.make != "input" && entry.make != "function") {
delete sc[key]
} else if (entry != null) {
slots = slots + 1
if (entry.closure) close_slots = close_slots + 1
}
}
j = j + 1
}
i = i + 1
}
// Update nr_slots and nr_close_slots on function nodes
var update_fn_slots = null
update_fn_slots = function(node) {
if (node == null) return null
var fn_nr = node.function_nr
if (fn_nr == null) return null
var sc = find_scope(fn_nr)
if (sc == null) return null
var keys = array(sc)
var s = 0
var cs = 0
var ki = 0
var ent = null
while (ki < length(keys)) {
if (keys[ki] != "function_nr") {
ent = sc[keys[ki]]
if (ent != null) {
s = s + 1
if (ent.closure) cs = cs + 1
}
}
ki = ki + 1
}
node.nr_slots = s
node.nr_close_slots = cs
}
var walk_stmts_for_fns = null
var walk_expr_for_fns = null
walk_expr_for_fns = function(expr) {
if (expr == null) return null
var k = expr.kind
var i = 0
if (k == "function") {
update_fn_slots(expr)
walk_stmts_for_fns(expr.statements)
walk_stmts_for_fns(expr.disruption)
return null
}
if (expr.left != null) walk_expr_for_fns(expr.left)
if (expr.right != null) walk_expr_for_fns(expr.right)
if (expr.expression != null) walk_expr_for_fns(expr.expression)
if (expr.then != null) walk_expr_for_fns(expr.then)
if (expr.else != null) walk_expr_for_fns(expr.else)
if (k == "(" || k == "array" || k == "text literal") {
i = 0
while (i < length(expr.list)) {
walk_expr_for_fns(expr.list[i])
i = i + 1
}
}
if (k == "record") {
i = 0
while (i < length(expr.list)) {
walk_expr_for_fns(expr.list[i].right)
i = i + 1
}
}
}
walk_stmts_for_fns = function(stmts) {
if (stmts == null) return null
var i = 0
var j = 0
var stmt = null
var k = null
while (i < length(stmts)) {
stmt = stmts[i]
k = stmt.kind
if (k == "function") {
update_fn_slots(stmt)
walk_stmts_for_fns(stmt.statements)
walk_stmts_for_fns(stmt.disruption)
} else if (k == "var" || k == "def") {
walk_expr_for_fns(stmt.right)
} else if (k == "var_list") {
j = 0
while (j < length(stmt.list)) {
walk_expr_for_fns(stmt.list[j].right)
j = j + 1
}
} else if (k == "call") {
walk_expr_for_fns(stmt.expression)
} else if (k == "if") {
walk_expr_for_fns(stmt.expression)
walk_stmts_for_fns(stmt.then)
walk_stmts_for_fns(stmt.list)
if (stmt.else != null) walk_stmts_for_fns(stmt.else)
} else if (k == "while" || k == "do") {
walk_expr_for_fns(stmt.expression)
walk_stmts_for_fns(stmt.statements)
} else if (k == "for") {
if (stmt.init != null) {
if (stmt.init.kind == "var" || stmt.init.kind == "def") {
walk_expr_for_fns(stmt.init.right)
} else {
walk_expr_for_fns(stmt.init)
}
}
walk_expr_for_fns(stmt.test)
walk_expr_for_fns(stmt.update)
walk_stmts_for_fns(stmt.statements)
} else if (k == "return" || k == "go") {
walk_expr_for_fns(stmt.expression)
} else if (k == "block") {
walk_stmts_for_fns(stmt.statements)
} else if (k == "label") {
if (stmt.statement != null) walk_stmts_for_fns([stmt.statement])
}
i = i + 1
}
}
walk_stmts_for_fns(ast.statements)
walk_stmts_for_fns(ast.functions)
// Update intrinsics: collect what's still referenced
var used_intrinsics = {}
var collect_intrinsics = null
var collect_expr_intrinsics = null
collect_expr_intrinsics = function(expr) {
if (expr == null) return null
var k = expr.kind
var i = 0
if (k == "name" && expr.level == -1 && expr.name != null && expr.make != "functino") {
used_intrinsics[expr.name] = true
}
if (expr.left != null) collect_expr_intrinsics(expr.left)
if (expr.right != null) collect_expr_intrinsics(expr.right)
if (expr.expression != null) collect_expr_intrinsics(expr.expression)
if (expr.then != null) collect_expr_intrinsics(expr.then)
if (expr.else != null) collect_expr_intrinsics(expr.else)
if (k == "(" || k == "array" || k == "text literal") {
i = 0
while (i < length(expr.list)) {
collect_expr_intrinsics(expr.list[i])
i = i + 1
}
}
if (k == "record") {
i = 0
while (i < length(expr.list)) {
collect_expr_intrinsics(expr.list[i].right)
i = i + 1
}
}
if (k == "function") {
collect_intrinsics(expr.statements)
collect_intrinsics(expr.disruption)
i = 0
while (i < length(expr.list)) {
if (expr.list[i].expression != null) {
collect_expr_intrinsics(expr.list[i].expression)
}
i = i + 1
}
}
}
collect_intrinsics = function(stmts) {
if (stmts == null) return null
var i = 0
var j = 0
var pi = 0
var stmt = null
var k = null
while (i < length(stmts)) {
stmt = stmts[i]
k = stmt.kind
if (k == "var" || k == "def") {
collect_expr_intrinsics(stmt.right)
} else if (k == "var_list") {
j = 0
while (j < length(stmt.list)) {
collect_expr_intrinsics(stmt.list[j].right)
j = j + 1
}
} else if (k == "call") {
collect_expr_intrinsics(stmt.expression)
} else if (k == "if") {
collect_expr_intrinsics(stmt.expression)
collect_intrinsics(stmt.then)
collect_intrinsics(stmt.list)
if (stmt.else != null) collect_intrinsics(stmt.else)
} else if (k == "while" || k == "do") {
collect_expr_intrinsics(stmt.expression)
collect_intrinsics(stmt.statements)
} else if (k == "for") {
if (stmt.init != null) {
if (stmt.init.kind == "var" || stmt.init.kind == "def") {
collect_expr_intrinsics(stmt.init.right)
} else {
collect_expr_intrinsics(stmt.init)
}
}
collect_expr_intrinsics(stmt.test)
collect_expr_intrinsics(stmt.update)
collect_intrinsics(stmt.statements)
} else if (k == "return" || k == "go") {
collect_expr_intrinsics(stmt.expression)
} else if (k == "function") {
collect_intrinsics(stmt.statements)
collect_intrinsics(stmt.disruption)
pi = 0
while (pi < length(stmt.list)) {
if (stmt.list[pi].expression != null) {
collect_expr_intrinsics(stmt.list[pi].expression)
}
pi = pi + 1
}
} else if (k == "block") {
collect_intrinsics(stmt.statements)
} else if (k == "label") {
if (stmt.statement != null) collect_intrinsics([stmt.statement])
}
i = i + 1
}
}
collect_intrinsics(ast.statements)
collect_intrinsics(ast.functions)
var new_intrinsics = []
i = 0
while (i < length(ast.intrinsics)) {
if (used_intrinsics[ast.intrinsics[i]] == true) {
push(new_intrinsics, ast.intrinsics[i])
}
i = i + 1
}
ast.intrinsics = new_intrinsics
}
// ============================================================
// Main
// ============================================================
pre_scan()
// Pass 2: fold all statements and functions
ast.statements = fold_stmts(ast.statements, 0)
var fi = 0
while (fi < length(ast.functions)) {
fold_fn(ast.functions[fi])
fi = fi + 1
}
// Remove dead top-level functions
var live_fns = []
var fn = null
fi = 0
while (fi < length(ast.functions)) {
fn = ast.functions[fi]
if (fn.dead != true) {
push(live_fns, fn)
}
fi = fi + 1
}
ast.functions = live_fns
// Pass 3: cleanup
cleanup()
return ast
}
return fold

BIN
fold.mach Normal file

Binary file not shown.

137
internal/bootstrap.cm Normal file
View File

@@ -0,0 +1,137 @@
// Hidden vars (os, args, core_path, use_mcode) come from env
// args[0] = script name, args[1..] = user args
var load_internal = os.load_internal
function use_embed(name) {
return load_internal("js_" + name + "_use")
}
var fd = use_embed('fd')
var json = use_embed('json')
var use_cache = {}
use_cache['fd'] = fd
use_cache['os'] = os
use_cache['json'] = json
// Bootstrap: load tokenize.cm, parse.cm, fold.cm from pre-compiled mach bytecode
function use_basic(path) {
if (use_cache[path])
return use_cache[path]
var result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
}
// Load a module from .mach bytecode, falling back to .ast.json
function boot_load(name, env) {
var mach_path = name + ".mach"
var data = null
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
return mach_load(data, env)
}
data = text(fd.slurp(name + ".ast.json"))
return mach_eval_ast(name, data, env)
}
var boot_env = {use: use_basic}
var tokenize_mod = boot_load("tokenize", boot_env)
var parse_mod = boot_load("parse", boot_env)
var fold_mod = boot_load("fold", boot_env)
// Optionally load mcode compiler module
var mcode_mod = null
if (use_mcode) {
mcode_mod = boot_load("mcode", boot_env)
}
// analyze: tokenize + parse, check for errors
function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename)
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0
var prev_line = -1
var prev_msg = null
var e = null
var msg = null
var line = null
var col = null
var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) {
while (_i < length(ast.errors)) {
e = ast.errors[_i]
msg = e.message
line = e.line
col = e.column
if (msg != prev_msg || line != prev_line) {
if (line != null && col != null) {
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`)
} else {
print(`${filename}: error: ${msg}`)
}
}
prev_line = line
prev_msg = msg
_i = _i + 1
}
disrupt
}
ast = fold_mod(ast)
return ast
}
// Run AST through either mcode or mach pipeline
function run_ast(name, ast, env) {
var compiled = null
if (use_mcode) {
compiled = mcode_mod(ast)
return mcode_run(name, json.encode(compiled), env)
}
return mach_eval_ast(name, json.encode(ast), env)
}
// use() with ƿit pipeline for .cm modules
function use(path) {
var file_path = path + '.cm'
var script = null
var ast = null
var result = null
if (use_cache[path])
return use_cache[path]
// Check CWD first, then core_path
if (!fd.is_file(file_path))
file_path = core_path + '/' + path + '.cm'
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast(path, ast, {use: use})
use_cache[path] = result
return result
}
// Fallback to embedded C module
result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
}
// Load and run the user's program
var program = args[0]
var script_file = program
// Add .ce extension if not already present
if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm'))
script_file = program + '.ce'
var user_args = []
var _j = 1
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
var script = text(fd.slurp(script_file))
var ast = analyze(script, script_file)
run_ast(program, ast, {use: use, args: user_args, json: json})

BIN
internal/bootstrap.mach Normal file

Binary file not shown.

View File

@@ -1,17 +1,21 @@
(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
switch(os.platform()) {
case 'Windows': dylib_ext = '.dll'; break;
case 'macOS': dylib_ext = '.dylib'; break;
case 'Linux': dylib_ext = '.so'; break;
}
var cases = {
Windows: '.dll',
macOS: '.dylib',
Linux: '.so'
}
print(os.platform())
dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
@@ -51,14 +55,16 @@ var fd = use_embed('fd')
// Get the shop path from HOME environment
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
if (!home) {
throw Error('Could not determine home directory')
os.print('Could not determine home directory\n')
os.exit(1)
}
var shop_path = home + '/.cell'
var packages_path = shop_path + '/packages'
var core_path = packages_path + '/core'
if (!fd.is_dir(core_path)) {
throw Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
os.print('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.\n')
os.exit(1)
}
var use_cache = {}
@@ -79,7 +85,7 @@ function use_core(path) {
var script_blob = fd.slurp(file_path)
var script = text(script_blob)
var mod = `(function setup_module(use){${script}})`
var fn = js.eval('core:' + path, mod)
var fn = mach_eval('core:' + path, mod)
var result = call(fn,sym, [use_core])
use_cache[cache_key] = result;
return result;
@@ -133,32 +139,27 @@ function log(name, args) {
var caller = caller_data(1)
var msg = args[0]
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
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}`)
}
}
function disrupt(err)
function actor_die(err)
{
if (is_function(err.toString)) {
os.print(err.toString())
os.print("\n")
os.print(err.stack)
if (err && is_function(err.toString)) {
os.print(err.toString())
os.print("\n")
if (err.stack) os.print(err.stack)
}
if (overling) {
@@ -185,14 +186,14 @@ function disrupt(err)
log.console(err.stack)
}
actor_mod.disrupt()
actor_mod["disrupt"]()
}
actor_mod.on_exception(disrupt)
actor_mod.on_exception(actor_die)
_cell.args = init ?? {}
_cell.args = init != null ? init : {}
_cell.id = "newguy"
function create_actor(desc = {id:guid()}) {
@@ -241,11 +242,15 @@ os.runtime_env = runtime_env
$_.time_limit = function(requestor, seconds)
{
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');
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
}
return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit')
var finished = false
@@ -260,7 +265,14 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = null
}
if (requestor_cancel) {
try { pronto.requestor_cancel(reason) } catch (_) {}
requestor_cancel(reason)
requestor_cancel = null
}
}
function safe_cancel_requestor(reason) {
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
@@ -268,15 +280,12 @@ $_.time_limit = function(requestor, seconds)
timer_cancel = $_.delay(function() {
if (finished) return
def reason = make_reason(factory, 'Timeout.', seconds)
if (requestor_cancel) {
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
safe_cancel_requestor(reason)
finished = true
callback(null, reason)
}, seconds)
try {
function do_request() {
requestor_cancel = requestor(function(val, reason) {
if (finished) return
finished = true
@@ -286,16 +295,14 @@ $_.time_limit = function(requestor, seconds)
}
callback(val, reason)
}, value)
} catch (ex) {
cancel(ex)
callback(null, ex)
} disruption {
cancel(Error('requestor failed'))
callback(null, Error('requestor failed'))
}
do_request()
return function(reason) {
if (requestor_cancel) {
try { requestor_cancel(reason) } catch (_) {}
requestor_cancel = null
}
safe_cancel_requestor(reason)
}
}
}
@@ -408,52 +415,54 @@ 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) throw Error(`Already started a portal listening on ${portal.port}`)
if (!port) throw Error("Requires a valid port.")
if (portal) {
log.error(`Already started a portal listening on ${portal.port}`)
disrupt
}
if (!port) {
log.error("Requires a valid port.")
disrupt
}
log.system(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port})
portal_fn = fn
}
function handle_host(e) {
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)
}
break
case "disconnect":
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)
arrfor(array(peers), function(id, index) {
if (peers[id] == e.peer) delete peers[id]
}
} 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
}
arrfor(array(obj), function(key, index) {
if (key in obj)
populate_actor_addresses(obj[key])
})
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
}
if (data.data) populate_actor_addresses(data.data)
turn(data)
}
}
@@ -487,10 +496,14 @@ $_.stop = function stop(actor) {
need_stop = true
return
}
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.')
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
}
sys_msg(actor, {kind:"stop"})
}
@@ -527,20 +540,22 @@ function actor_prep(actor, send) {
// Send a message immediately without queuing
function actor_send_immediate(actor, send) {
try {
actor_send(actor, send);
} catch (err) {
log.error("Failed to send immediate message:", err);
}
actor_send(actor, send)
}
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)) throw Error(`Must send to an actor object. Attempted send to ${actor}`)
if (!is_object(message)) throw Error('Must send an object record.')
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
}
// message to self
if (actor[ACTORDATA].id == _cell.id) {
@@ -583,12 +598,10 @@ function actor_send(actor, message) {
// Holds all messages queued during the current turn.
var message_queue = []
var need_stop = false
function send_messages() {
function send_messages() {
// if we've been flagged to stop, bail out before doing anything
if (need_stop) {
disrupt()
actor_die()
message_queue = []
return
}
@@ -608,19 +621,26 @@ var need_stop = false
var replies = {}
function send(actor, message, reply) {
if (!is_object(actor))
throw Error(`Must send to an actor object. Provided: ${actor}`);
if (!is_object(actor)) {
log.error(`Must send to an actor object. Provided: ${actor}`)
disrupt
}
if (!is_object(message))
throw Error('Message must be an object')
if (!is_object(message)) {
log.error('Message must be an object')
disrupt
}
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))
throw Error(`Supplied actor had a return, but it's not a valid actor! ${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
}
actor = header.replycc
target = header.replycc
send_msg.return = header.reply
}
@@ -638,7 +658,7 @@ function send(actor, message, reply) {
}
// Instead of sending immediately, queue it
actor_prep(actor, send_msg);
actor_prep(target, send_msg);
}
stone(send)
@@ -669,7 +689,7 @@ overling = _cell.args.overling
$_.overling = overling
root = _cell.args.root
root ??= $_.self
if (root == null) root = $_.self
if (overling) {
$_.couple(overling) // auto couple to overling
@@ -705,36 +725,35 @@ function handle_actor_disconnect(id) {
delete greeters[id]
}
log.system(`actor ${id} disconnected`)
if (!is_null(couplings[id])) disrupt("coupled actor died") // couplings now disrupts instead of stop
if (!is_null(couplings[id])) actor_die("coupled actor died") // couplings now disrupts instead of stop
}
function handle_sysym(msg)
{
var from
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
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`)
}
}
@@ -744,30 +763,27 @@ function handle_message(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)
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]
return
case "stopped":
handle_actor_disconnect(msg.id)
break
}
if (receive_fn) receive_fn(letter)
} else if (msg.type == "stopped") {
handle_actor_disconnect(msg.id)
}
};
}
function enet_check()
{
@@ -792,8 +808,10 @@ if (!locator) {
locator = shop.resolve_locator(_cell.args.program + ".ce", pkg)
}
if (!locator)
throw Error(`Main program ${_cell.args.program} could not be found`)
if (!locator) {
os.print(`Main program ${_cell.args.program} could not be found\n`)
os.exit(1)
}
$_.clock(_ => {
// Get capabilities for the main program
@@ -818,7 +836,6 @@ $_.clock(_ => {
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
if (val)
throw Error('Program must not return anything');
log.error('Program must not return anything')
disrupt
})
})()

View File

@@ -577,8 +577,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id);
JS_NewClass(JS_GetRuntime(js), js_dylib_class_id, &js_dylib_class);
JS_NewClass(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;

11
mcode.ce Normal file
View File

@@ -0,0 +1,11 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var mcode = use("mcode")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename)
var compiled = mcode(ast)
print(json.encode(compiled))

1834
mcode.cm Normal file

File diff suppressed because it is too large Load Diff

BIN
mcode.mach Normal file

Binary file not shown.

View File

@@ -44,11 +44,14 @@ src += [ # core
'wildmatch.c',
'qjs_actor.c',
'miniz.c',
'quickjs.c',
'runtime.c',
'mach.c',
'mcode.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
]
src += ['scheduler.c']
src += ['qbe_helpers.c']
scripts = [
'debug/js.c',

View File

@@ -571,13 +571,13 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx)
{
JS_NewClassID(&enet_host_id);
JS_NewClass(JS_GetRuntime(ctx), enet_host_id, &enet_host);
JS_NewClass(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(JS_GetRuntime(ctx), enet_peer_class_id, &enet_peer_class);
JS_NewClass(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);

8
parse.ce Normal file
View File

@@ -0,0 +1,8 @@
var fd = use("fd")
var tokenize = use("tokenize")
var parse = use("parse")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename)
print(json.encode(ast))

1918
parse.cm Normal file

File diff suppressed because it is too large Load Diff

BIN
parse.mach Normal file

Binary file not shown.

764
qbe.cm Normal file
View File

@@ -0,0 +1,764 @@
// QBE IL Macro Layer
// Maps mcode VM operations to QBE intermediate language fragments.
// Each macro returns a backtick template string of QBE IL.
// Convention: p = unique prefix for temporaries/labels, result in %{p}
// ============================================================
// Constants
// ============================================================
def js_null = 7
def js_false = 3
def js_true = 35
def js_exception = 15
def js_empty_text = 27
def int32_min = -2147483648
def int32_max = 2147483647
def mantissa_mask = 4503599627370495
// ============================================================
// Type Checks — each takes (p, v), produces %{p} as w (0 or 1)
// ============================================================
var is_int = function(p, v) {
return ` %${p}.t =l and ${v}, 1
%${p} =w ceql %${p}.t, 0
`
}
var is_number = function(p, v) {
return ` %${p}.t1 =l and ${v}, 1
%${p}.ii =w ceql %${p}.t1, 0
%${p}.t2 =l and ${v}, 7
%${p}.fi =w ceql %${p}.t2, 5
%${p} =w or %${p}.ii, %${p}.fi
`
}
var is_null = function(p, v) {
return ` %${p}.t =l and ${v}, 31
%${p} =w ceql %${p}.t, 7
`
}
var is_bool = function(p, v) {
return ` %${p}.t =l and ${v}, 31
%${p} =w ceql %${p}.t, 3
`
}
var is_exception = function(p, v) {
return ` %${p}.t =l and ${v}, 31
%${p} =w ceql %${p}.t, 15
`
}
var is_ptr = function(p, v) {
return ` %${p}.t =l and ${v}, 7
%${p} =w ceql %${p}.t, 1
`
}
var is_imm_text = function(p, v) {
return ` %${p}.t =l and ${v}, 31
%${p} =w ceql %${p}.t, 27
`
}
var is_text = function(p, v) {
return ` %${p}.imm =l and ${v}, 31
%${p}.is_imm =w ceql %${p}.imm, 27
jnz %${p}.is_imm, @${p}.yes, @${p}.chk_ptr
@${p}.chk_ptr
%${p}.ptag =l and ${v}, 7
%${p}.is_ptr =w ceql %${p}.ptag, 1
jnz %${p}.is_ptr, @${p}.load_hdr, @${p}.no
@${p}.load_hdr
%${p}.ptr =l and ${v}, -8
%${p}.hdr =l loadl %${p}.ptr
@${p}.chase
%${p}.ht =l and %${p}.hdr, 7
%${p}.is_fwd =w ceql %${p}.ht, 7
jnz %${p}.is_fwd, @${p}.follow, @${p}.chk_type
@${p}.follow
%${p}.ptr =l shr %${p}.hdr, 3
%${p}.hdr =l loadl %${p}.ptr
jmp @${p}.chase
@${p}.chk_type
%${p} =w ceql %${p}.ht, 2
jmp @${p}.done
@${p}.yes
%${p} =w copy 1
jmp @${p}.done
@${p}.no
%${p} =w copy 0
@${p}.done
`
}
// ============================================================
// Value Extraction
// ============================================================
// get_int(p, v) — extract int32 as w from tagged int. Result: %{p}
var get_int = function(p, v) {
return ` %${p}.sl =l sar ${v}, 1
%${p} =w copy %${p}.sl
`
}
// get_bool(p, v) — extract bool as w. Result: %{p}
var get_bool = function(p, v) {
return ` %${p}.sl =l shr ${v}, 5
%${p} =w and %${p}.sl, 1
`
}
// get_ptr(p, v) — extract pointer as l. Result: %{p}
var get_ptr = function(p, v) {
return ` %${p} =l and ${v}, -8
`
}
// get_float64(p, v) — decode short float to d. Result: %{p} as d
// Caller must handle zero check (sexp == 0 -> 0.0) before calling.
var get_float64 = function(p, v) {
return ` %${p}.sign =l shr ${v}, 63
%${p}.sexp =l shr ${v}, 55
%${p}.sexp =l and %${p}.sexp, 255
%${p}.mant =l shr ${v}, 3
%${p}.mant =l and %${p}.mant, ${mantissa_mask}
%${p}.dexp =l sub %${p}.sexp, 127
%${p}.dexp =l add %${p}.dexp, 1023
%${p}.s63 =l shl %${p}.sign, 63
%${p}.e52 =l shl %${p}.dexp, 52
%${p}.bits =l or %${p}.s63, %${p}.e52
%${p}.bits =l or %${p}.bits, %${p}.mant
%${p} =d cast %${p}.bits
`
}
// to_float64(p, v) — convert any numeric value (int or short float) to d.
// Result: %{p} as d
var to_float64 = function(p, v) {
return ` %${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.from_int, @${p}.from_float
@${p}.from_int
%${p}.isl =l sar ${v}, 1
%${p}.iw =w copy %${p}.isl
%${p} =d swtof %${p}.iw
jmp @${p}.done
@${p}.from_float
%${p}.fsexp =l shr ${v}, 55
%${p}.fsexp =l and %${p}.fsexp, 255
%${p}.is_zero =w ceql %${p}.fsexp, 0
jnz %${p}.is_zero, @${p}.fzero, @${p}.fdecode
@${p}.fzero
%${p} =d copy d_0.0
jmp @${p}.done
@${p}.fdecode
%${p}.fsign =l shr ${v}, 63
%${p}.fmant =l shr ${v}, 3
%${p}.fmant =l and %${p}.fmant, ${mantissa_mask}
%${p}.fdexp =l sub %${p}.fsexp, 127
%${p}.fdexp =l add %${p}.fdexp, 1023
%${p}.fs63 =l shl %${p}.fsign, 63
%${p}.fe52 =l shl %${p}.fdexp, 52
%${p}.fbits =l or %${p}.fs63, %${p}.fe52
%${p}.fbits =l or %${p}.fbits, %${p}.fmant
%${p} =d cast %${p}.fbits
@${p}.done
`
}
// ============================================================
// Value Creation
// ============================================================
// new_int(p, i) — tag int32 w as JSValue l. Result: %{p}
var new_int = function(p, i) {
return ` %${p}.ext =l extuw ${i}
%${p} =l shl %${p}.ext, 1
`
}
// new_bool(p, b) — tag bool w as JSValue l. Result: %{p}
var new_bool = function(p, b) {
return ` %${p}.ext =l extuw ${b}
%${p}.sh =l shl %${p}.ext, 5
%${p} =l or %${p}.sh, 3
`
}
// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
var new_float64 = function(p, ctx, d) {
return ` %${p} =l call $__JS_NewFloat64(l ${ctx}, d ${d})
`
}
// ============================================================
// Arithmetic — add(p, ctx, a, b)
// Int fast path inline, text concat and float as C calls.
// Jumps to @disrupt on type mismatch.
// ============================================================
var add = function(p, ctx, a, b) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.sum =l add %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.sum, ${int32_min}
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.sum
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.sum
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_text =w call $JS_IsText(l ${a})
%${p}.b_is_text =w call $JS_IsText(l ${b})
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
jnz %${p}.both_text, @${p}.text_path, @${p}.chk_num
@${p}.text_path
%${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
jmp @${p}.done
@${p}.chk_num
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var sub = function(p, ctx, a, b) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.diff =l sub %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.diff, ${int32_min}
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.diff
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.diff
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var mul = function(p, ctx, a, b) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.prod =l mul %${p}.ia, %${p}.ib
%${p}.lo =w csltl %${p}.prod, ${int32_min}
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
%${p}.ov =w or %${p}.lo, %${p}.hi
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
@${p}.int_ok
%${p}.rw =w copy %${p}.prod
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.int_overflow
%${p}.fd =d sltof %${p}.prod
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var div = function(p, ctx, a, b) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.ret_null, @${p}.chk_exact
@${p}.ret_null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.chk_exact
%${p}.rem =w rem %${p}.ia, %${p}.ib
%${p}.exact =w ceqw %${p}.rem, 0
jnz %${p}.exact, @${p}.int_div, @${p}.int_to_float
@${p}.int_div
%${p}.q =w div %${p}.ia, %${p}.ib
%${p}.qext =l extuw %${p}.q
%${p} =l shl %${p}.qext, 1
jmp @${p}.done
@${p}.int_to_float
%${p}.da =d swtof %${p}.ia
%${p}.db =d swtof %${p}.ib
%${p}.dr =d div %${p}.da, %${p}.db
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var mod = function(p, ctx, a, b) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =w copy 0
%${p}.tmp =l sar ${a}, 1
%${p}.ia =w copy %${p}.tmp
%${p}.ib =w copy 0
%${p}.tmp2 =l sar ${b}, 1
%${p}.ib =w copy %${p}.tmp2
%${p}.div0 =w ceqw %${p}.ib, 0
jnz %${p}.div0, @${p}.ret_null, @${p}.do_mod
@${p}.ret_null
%${p} =l copy ${js_null}
jmp @${p}.done
@${p}.do_mod
%${p}.r =w rem %${p}.ia, %${p}.ib
%${p}.rext =l extuw %${p}.r
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.not_both_int
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_path, @disrupt
@${p}.float_path
%${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
// ============================================================
// Comparisons — eq, ne, lt, le, gt, ge
// Each takes (p, ctx, a, b), produces %{p} as l (tagged JSValue bool)
// ============================================================
// Helper: generate comparison for a given op string and int comparison QBE op
// null_true: whether null==null returns true (eq, le, ge) or false (ne, lt, gt)
var cmp = function(p, ctx, a, b, int_cmp_op, float_cmp_op_id, is_eq, is_ne, null_true) {
var eq_only = 0
if (is_eq || is_ne) {
eq_only = 1
}
var mismatch_val = js_false
if (is_ne) {
mismatch_val = js_true
}
var null_val = js_false
if (null_true) {
null_val = js_true
}
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.ib =l sar ${b}, 1
%${p}.iw =w copy %${p}.ia
%${p}.ibw =w copy %${p}.ib
%${p}.cr =w ${int_cmp_op} %${p}.iw, %${p}.ibw
%${p}.crext =l extuw %${p}.cr
%${p}.sh =l shl %${p}.crext, 5
%${p} =l or %${p}.sh, 3
jmp @${p}.done
@${p}.not_both_int
%${p}.a_tag5 =l and ${a}, 31
%${p}.b_tag5 =l and ${b}, 31
%${p}.a_is_null =w ceql %${p}.a_tag5, 7
%${p}.b_is_null =w ceql %${p}.b_tag5, 7
%${p}.both_null =w and %${p}.a_is_null, %${p}.b_is_null
jnz %${p}.both_null, @${p}.null_path, @${p}.chk_bool
@${p}.null_path
%${p} =l copy ${null_val}
jmp @${p}.done
@${p}.chk_bool
%${p}.a_is_bool =w ceql %${p}.a_tag5, 3
%${p}.b_is_bool =w ceql %${p}.b_tag5, 3
%${p}.both_bool =w and %${p}.a_is_bool, %${p}.b_is_bool
jnz %${p}.both_bool, @${p}.bool_path, @${p}.chk_num
@${p}.bool_path
%${p}.ba =l shr ${a}, 5
%${p}.baw =w and %${p}.ba, 1
%${p}.bb =l shr ${b}, 5
%${p}.bbw =w and %${p}.bb, 1
%${p}.bcr =w ${int_cmp_op} %${p}.baw, %${p}.bbw
%${p}.bcrext =l extuw %${p}.bcr
%${p}.bsh =l shl %${p}.bcrext, 5
%${p} =l or %${p}.bsh, 3
jmp @${p}.done
@${p}.chk_num
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.num_path, @${p}.chk_text
@${p}.num_path
%${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${float_cmp_op_id}, l ${a}, l ${b})
%${p}.fcrext =l extuw %${p}.fcr
%${p}.fsh =l shl %${p}.fcrext, 5
%${p} =l or %${p}.fsh, 3
jmp @${p}.done
@${p}.chk_text
%${p}.a_is_text =w call $JS_IsText(l ${a})
%${p}.b_is_text =w call $JS_IsText(l ${b})
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
jnz %${p}.both_text, @${p}.text_path, @${p}.mismatch
@${p}.text_path
%${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
%${p}.tcr =w ${int_cmp_op} %${p}.scmp, 0
%${p}.tcrext =l extuw %${p}.tcr
%${p}.tsh =l shl %${p}.tcrext, 5
%${p} =l or %${p}.tsh, 3
jmp @${p}.done
@${p}.mismatch
%${p} =l copy ${mismatch_val}
@${p}.done
`
}
// Comparison op IDs matching MACH_EQ..MACH_GE order for qbe_float_cmp
// MACH_EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5
// null_true: eq, le, ge return true for null==null; ne, lt, gt return false
var eq = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "ceqw", 0, true, false, true)
}
var ne = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "cnew", 1, false, true, false)
}
var lt = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csltw", 2, false, false, false)
}
var le = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "cslew", 3, false, false, true)
}
var gt = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csgtw", 4, false, false, false)
}
var ge = function(p, ctx, a, b) {
return cmp(p, ctx, a, b, "csgew", 5, false, false, true)
}
// ============================================================
// Unary Ops
// ============================================================
// neg(p, ctx, v) — negate. Int fast path (INT32_MIN edge case), else C call.
var neg = function(p, ctx, v) {
return `@${p}.start
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
@${p}.min_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fdn =d neg %${p}.fd
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w sub 0, %${p}.iw
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
@${p}.done
`
}
// inc(p, ctx, v) — increment. Int fast path (INT32_MAX edge case), else C call.
var inc = function(p, ctx, v) {
return `@${p}.start
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_max =w ceqw %${p}.iw, ${int32_max}
jnz %${p}.is_max, @${p}.max_overflow, @${p}.int_ok
@${p}.max_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fd1 =d add %${p}.fd, d_1.0
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w add %${p}.iw, 1
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
@${p}.done
`
}
// dec(p, ctx, v) — decrement. Int fast path (INT32_MIN edge case), else C call.
var dec = function(p, ctx, v) {
return `@${p}.start
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
@${p}.min_overflow
%${p}.fd =d swtof %${p}.iw
%${p}.fd1 =d sub %${p}.fd, d_1.0
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
jmp @${p}.done
@${p}.int_ok
%${p}.ni =w sub %${p}.iw, 1
%${p}.niext =l extuw %${p}.ni
%${p} =l shl %${p}.niext, 1
jmp @${p}.done
@${p}.float_path
%${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
@${p}.done
`
}
// lnot(p, ctx, v) — logical not. C call to JS_ToBool, then negate inline.
var lnot = function(p, ctx, v) {
return ` %${p}.bval =w call $JS_ToBool(l ${ctx}, l ${v})
%${p}.neg =w ceqw %${p}.bval, 0
%${p}.nex =l extuw %${p}.neg
%${p}.sh =l shl %${p}.nex, 5
%${p} =l or %${p}.sh, 3
`
}
// bnot(p, ctx, v) — bitwise not. Convert to int32, ~, re-tag.
var bnot = function(p, ctx, v) {
return `@${p}.start
%${p}.tag =l and ${v}, 1
%${p}.is_int =w ceql %${p}.tag, 0
jnz %${p}.is_int, @${p}.int_path, @${p}.slow_path
@${p}.int_path
%${p}.sl =l sar ${v}, 1
%${p}.iw =w copy %${p}.sl
%${p}.nw =w xor %${p}.iw, -1
%${p}.nex =l extuw %${p}.nw
%${p} =l shl %${p}.nex, 1
jmp @${p}.done
@${p}.slow_path
%${p} =l call $qbe_bnot(l ${ctx}, l ${v})
@${p}.done
`
}
// ============================================================
// Bitwise Ops — band, bor, bxor, shl, shr, ushr
// Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================
var bitwise_op = function(p, ctx, a, b, qbe_op) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.iaw =w copy %${p}.ia
%${p}.ib =l sar ${b}, 1
%${p}.ibw =w copy %${p}.ib
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.ibw
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.slow_path
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
@${p}.float_to_int
%${p} =l call $qbe_bitwise_${qbe_op}(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var band = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "and")
}
var bor = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "or")
}
var bxor = function(p, ctx, a, b) {
return bitwise_op(p, ctx, a, b, "xor")
}
// Shift ops: mask shift amount to 5 bits (& 31)
var shift_op = function(p, ctx, a, b, qbe_op) {
return `@${p}.start
%${p}.at =l and ${a}, 1
%${p}.bt =l and ${b}, 1
%${p}.not_int =l or %${p}.at, %${p}.bt
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
@${p}.int_path
%${p}.ia =l sar ${a}, 1
%${p}.iaw =w copy %${p}.ia
%${p}.ib =l sar ${b}, 1
%${p}.ibw =w copy %${p}.ib
%${p}.sh =w and %${p}.ibw, 31
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.sh
%${p}.rext =l extuw %${p}.rw
%${p} =l shl %${p}.rext, 1
jmp @${p}.done
@${p}.slow_path
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
@${p}.float_to_int
%${p} =l call $qbe_shift_${qbe_op}(l ${ctx}, l ${a}, l ${b})
@${p}.done
`
}
var shl = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "shl")
}
var shr = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "sar")
}
var ushr = function(p, ctx, a, b) {
return shift_op(p, ctx, a, b, "shr")
}
// ============================================================
// Module export
// ============================================================
return {
// constants
js_null: js_null,
js_false: js_false,
js_true: js_true,
js_exception: js_exception,
js_empty_text: js_empty_text,
// type checks
is_int: is_int,
is_number: is_number,
is_null: is_null,
is_bool: is_bool,
is_exception: is_exception,
is_ptr: is_ptr,
is_imm_text: is_imm_text,
is_text: is_text,
// value extraction
get_int: get_int,
get_bool: get_bool,
get_ptr: get_ptr,
get_float64: get_float64,
to_float64: to_float64,
// value creation
new_int: new_int,
new_bool: new_bool,
new_float64: new_float64,
// arithmetic
add: add,
sub: sub,
mul: mul,
div: div,
mod: mod,
// comparisons
eq: eq,
ne: ne,
lt: lt,
le: le,
gt: gt,
ge: ge,
// unary
neg: neg,
inc: inc,
dec: dec,
lnot: lnot,
bnot: bnot,
// bitwise
band: band,
bor: bor,
bxor: bxor,
shl: shl,
shr: shr,
ushr: ushr
}

4
qop.c
View File

@@ -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_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
JS_NewClass(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_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class);
JS_NewClass(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);

View File

@@ -11,7 +11,8 @@
#include "cell_internal.h"
#include "cJSON.h"
#define ENGINE "internal/engine.cm"
#define BOOTSTRAP_MACH "internal/bootstrap.mach"
#define BOOTSTRAP_AST "internal/bootstrap.ast.json"
#define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core"
@@ -26,6 +27,7 @@ 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) {
@@ -70,70 +72,41 @@ int find_cell_shop(void)
// Load a file from the core directory
static char* load_core_file(const char *filename, size_t *out_size) {
if (!core_path) return NULL;
size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1;
char *full_path = malloc(path_len);
if (!full_path) return NULL;
snprintf(full_path, path_len, "%s/%s", core_path, filename);
FILE *fh = fopen(full_path, "rb");
free(full_path);
if (!fh) return NULL;
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
char *data = malloc(file_size + 1);
if (!data) {
fclose(fh);
return NULL;
}
if (fread(data, 1, file_size, fh) != (size_t)file_size) {
free(data);
fclose(fh);
return NULL;
}
fclose(fh);
data[file_size] = 0;
if (out_size) *out_size = file_size;
return data;
}
static int print_json_errors(const char *json) {
if (!json) return 0;
cJSON *root = cJSON_Parse(json);
if (!root) return 0;
cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors");
if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) {
cJSON_Delete(root);
return 0;
}
const char *filename = "<unknown>";
cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename");
if (cJSON_IsString(fname))
filename = fname->valuestring;
cJSON *e;
cJSON_ArrayForEach(e, errors) {
const char *msg = cJSON_GetStringValue(
cJSON_GetObjectItemCaseSensitive(e, "message"));
cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line");
cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column");
if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col))
fprintf(stderr, "%s:%d:%d: error: %s\n",
filename, (int)line->valuedouble, (int)col->valuedouble, msg);
else if (msg)
fprintf(stderr, "%s: error: %s\n", filename, msg);
}
cJSON_Delete(root);
return 1;
}
// Get the core path for use by scripts
const char* cell_get_core_path(void) {
return core_path;
@@ -154,28 +127,40 @@ JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt)
{
JSRuntime *rt = JS_NewRuntime();
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
JSContext *js = JS_NewContext(rt);
if (!g_runtime) {
g_runtime = JS_NewRuntime();
}
JSContext *js = JS_NewContext(g_runtime);
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
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 compile engine.cm
size_t engine_size;
char *data = load_core_file(ENGINE, &engine_size);
if (!data) {
printf("ERROR: Could not load %s from %s!\n", ENGINE, core_path);
return;
// Load pre-compiled bootstrap bytecode (.mach), fall back to AST JSON
size_t boot_size;
int boot_is_bin = 1;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
if (!boot_data) {
boot_is_bin = 0;
boot_data = load_core_file(BOOTSTRAP_AST, &boot_size);
}
JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE);
free(data);
if (JS_IsException(bytecode)) {
uncaught_exception(js, bytecode);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return;
}
@@ -186,8 +171,8 @@ void script_startup(cell_rt *prt)
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
crt->actor_sym = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym));
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null)
if (crt->init_wota) {
@@ -205,9 +190,19 @@ void script_startup(cell_rt *prt)
// Stone the environment
hidden_env = JS_Stone(js, hidden_env);
// Integrate and run
// Run through MACH VM
crt->state = ACTOR_RUNNING;
JSValue v = JS_Integrate(js, bytecode, hidden_env);
JSValue v;
if (boot_is_bin) {
v = JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
} else {
cJSON *ast = cJSON_Parse(boot_data);
free(boot_data);
if (!ast) { printf("ERROR: Failed to parse bootstrap AST\n"); return; }
v = JS_RunMachTree(js, ast, hidden_env);
cJSON_Delete(ast);
}
uncaught_exception(js, v);
crt->state = ACTOR_IDLE;
set_actor_state(crt);
@@ -255,108 +250,26 @@ static int run_test_suite(size_t heap_size)
return result;
}
/* Run an immediate script string */
static int run_eval(const char *script_or_file, int print_bytecode, int use_bootstrap_env)
static void print_usage(const char *prog)
{
if (!find_cell_shop()) return 1;
/* Check if argument is a file path */
struct stat st;
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
/* It's a file, read its contents */
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
/* Treat as inline script */
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
int result = 0;
JSGCRef bytecode_ref;
JS_PushGCRef(ctx, &bytecode_ref);
bytecode_ref.val = JS_Compile(ctx, script, strlen(script), filename);
if (JS_IsException(bytecode_ref.val)) {
uncaught_exception(ctx, bytecode_ref.val);
JS_PopGCRef(ctx, &bytecode_ref);
result = 1;
} else {
if (print_bytecode) {
printf("=== Compiled Bytecode ===\n");
JS_DumpFunctionBytecode(ctx, bytecode_ref.val);
}
JSValue env = JS_NULL;
if (use_bootstrap_env) {
JSGCRef env_ref, json_ref, nota_ref, wota_ref;
JS_PushGCRef(ctx, &env_ref);
JS_PushGCRef(ctx, &json_ref);
JS_PushGCRef(ctx, &nota_ref);
JS_PushGCRef(ctx, &wota_ref);
env_ref.val = JS_NewObject(ctx);
/* Create modules with GC rooting, then stone them */
json_ref.val = js_json_use(ctx);
nota_ref.val = js_nota_use(ctx);
wota_ref.val = js_wota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", JS_Stone(ctx, json_ref.val));
JS_SetPropertyStr(ctx, env_ref.val, "nota", JS_Stone(ctx, nota_ref.val));
JS_SetPropertyStr(ctx, env_ref.val, "wota", JS_Stone(ctx, wota_ref.val));
env = JS_Stone(ctx, env_ref.val);
JS_PopGCRef(ctx, &wota_ref);
JS_PopGCRef(ctx, &nota_ref);
JS_PopGCRef(ctx, &json_ref);
JS_PopGCRef(ctx, &env_ref);
}
JSValue v = JS_Integrate(ctx, bytecode_ref.val, env);
JS_PopGCRef(ctx, &bytecode_ref);
if (JS_IsException(v)) {
uncaught_exception(ctx, v);
result = 1;
} else {
JS_FreeValue(ctx, v);
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return result;
printf("Usage: %s [options] <script> [args...]\n\n", prog);
printf("Run a cell script (.ce actor or .cm module).\n\n");
printf("Options:\n");
printf(" --mcode <script> [args] Run through mcode compilation pipeline\n");
printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n");
printf("\nRecompile after changes: make\n");
printf("Bootstrap from scratch: make bootstrap\n");
}
int cell_init(int argc, char **argv)
{
/* Check for --help flag */
if (argc >= 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
print_usage(argv[0]);
return 0;
}
/* Check for --test flag to run C test suite */
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
size_t heap_size = 64 * 1024; /* 64KB default */
@@ -370,451 +283,82 @@ int cell_init(int argc, char **argv)
return run_test_suite(heap_size);
}
/* Check for --ast flag to output AST JSON */
if (argc >= 3 && strcmp(argv[1], "--ast") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *json = JS_AST(ctx, script, strlen(script), filename);
if (json) {
int has_errors = print_json_errors(json);
printf("%s\n", json);
free(json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Default: run script through bootstrap pipeline */
int use_mcode = 0;
int arg_start = 1;
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
use_mcode = 1;
arg_start = 2;
}
/* Check for --tokenize flag to output token array JSON */
if (argc >= 3 && strcmp(argv[1], "--tokenize") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
if (!find_cell_shop()) return 1;
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *json = JS_Tokenize(ctx, script, strlen(script), filename);
if (json) {
int has_errors = print_json_errors(json);
printf("%s\n", json);
free(json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return has_errors ? 1 : 0;
} else {
printf("Failed to tokenize\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
size_t boot_size;
int boot_is_bin = 1;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
if (!boot_data) {
boot_is_bin = 0;
boot_data = load_core_file(BOOTSTRAP_AST, &boot_size);
}
/* Check for --mach flag to output machine code JSON */
if (argc >= 3 && strcmp(argv[1], "--mach") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (mach_json) {
if (print_json_errors(mach_json)) {
free(mach_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
printf("%s\n", mach_json);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
}
/* Check for --vmcode flag to dump linked register VM bytecode */
if (argc >= 3 && strcmp(argv[1], "--vmcode") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (mach_json) {
if (print_json_errors(mach_json)) {
free(mach_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
JS_DumpRegisterMach(ctx, mach_json, JS_NULL);
free(mach_json);
} else {
printf("Failed to generate machine code\n");
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return mach_json ? 0 : 1;
}
/* Check for --mach-run flag to generate and run machine code through register VM */
if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) {
const char *script_or_file = argv[2];
char *script = NULL;
char *allocated_script = NULL;
const char *filename = "<eval>";
struct stat st;
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
FILE *f = fopen(script_or_file, "r");
if (!f) {
printf("Failed to open file: %s\n", script_or_file);
return 1;
}
allocated_script = malloc(st.st_size + 1);
if (!allocated_script) {
fclose(f);
printf("Failed to allocate memory for script\n");
return 1;
}
size_t read_size = fread(allocated_script, 1, st.st_size, f);
fclose(f);
allocated_script[read_size] = '\0';
script = allocated_script;
filename = script_or_file;
} else {
script = (char *)script_or_file;
}
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(allocated_script);
return 1;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
printf("Failed to create JS context\n");
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Parse to AST */
char *ast_json = JS_AST(ctx, script, strlen(script), filename);
if (!ast_json) {
printf("Failed to parse AST\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
if (print_json_errors(ast_json)) {
free(ast_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Generate machine code */
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
if (!mach_json) {
printf("Failed to generate machine code\n");
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
if (print_json_errors(mach_json)) {
free(mach_json);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return 1;
}
/* Execute through register VM */
JSValue result = JS_IntegrateRegister(ctx, mach_json, JS_NULL);
free(mach_json);
int exit_code = 0;
if (JS_IsException(result)) {
JSValue exc = JS_GetException(ctx);
const char *err_str = JS_ToCString(ctx, exc);
if (err_str) {
printf("Error: %s\n", err_str);
JS_FreeCString(ctx, err_str);
}
exit_code = 1;
} else if (!JS_IsNull(result)) {
/* Print result */
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
free(allocated_script);
return exit_code;
}
/* Check for -e or --eval flag to run immediate script */
/* Also check for -p flag to print bytecode */
/* -s / --serializers flag provides json, nota, wota in env */
if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) {
return run_eval(argv[2], 0, 0);
}
if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) {
return run_eval(argv[2], 1, 0);
}
if (argc >= 3 && (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--serializers") == 0)) {
return run_eval(argv[2], 0, 1);
}
int script_start = 1;
/* Find the cell shop at ~/.cell */
int found = find_cell_shop();
if (!found) {
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s\n", core_path);
return 1;
}
/* Create the initial actor from the command line */
int actor_argc = argc - script_start;
char **actor_argv = argv + script_start;
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
printf("Failed to create JS runtime\n");
free(boot_data);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(rt, 16 * 1024 * 1024);
if (!ctx) {
printf("Failed to create JS context\n");
free(boot_data); JS_FreeRuntime(rt);
return 1;
}
WotaBuffer startwota;
wota_buffer_init(&startwota, 5);
wota_write_record(&startwota, 2);
wota_write_text(&startwota, "program");
wota_write_text(&startwota, actor_argv[0]);
wota_write_text(&startwota, "arg");
wota_write_array(&startwota, actor_argc - 1);
for (int i = 1; i < actor_argc; i++)
wota_write_text(&startwota, actor_argv[i]);
/* Initialize synchronization primitives */
actor_initialize();
root_cell = create_actor(startwota.data);
#ifndef TARGET_PLAYDATE
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);
#endif
actor_loop();
JS_FreeValue(ctx, js_blob_use(ctx));
return 0;
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode));
JSValue args_arr = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
JSValue result;
if (boot_is_bin) {
result = JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
} else {
cJSON *ast = cJSON_Parse(boot_data);
free(boot_data);
if (!ast) { printf("Failed to parse bootstrap AST\n"); JS_FreeContext(ctx); JS_FreeRuntime(rt); return 1; }
result = JS_RunMachTree(ctx, ast, hidden_env);
cJSON_Delete(ast);
}
int exit_code = 0;
if (JS_IsException(result)) {
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
}
}
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return exit_code;
}
int JS_ArrayLength(JSContext *js, JSValue a)
@@ -848,36 +392,16 @@ double cell_random() {
void cell_trace_sethook(cell_hook)
{
}
int uncaught_exception(JSContext *js, JSValue v)
{
cell_rt *rt = JS_GetContextOpaque(js);
if (!JS_HasException(js)) {
JS_FreeValue(js,v);
(void)v;
if (!JS_HasException(js))
return 1;
}
JSValue exp = JS_GetException(js);
JSValue message = JS_GetPropertyStr(js, exp, "message");
const char *msg_str = JS_ToCString(js, message);
if (msg_str) {
printf("Exception: %s\n", msg_str);
JS_FreeCString(js, msg_str);
}
JS_FreeValue(js, message);
JSValue stack = JS_GetPropertyStr(js, exp, "stack");
const char *stack_str = JS_ToCString(js, stack);
if (stack_str) {
printf("Stack:\n%s\n", stack_str);
JS_FreeCString(js, stack_str);
}
JS_FreeValue(js, stack);
JS_FreeValue(js, exp);
JS_FreeValue(js, v);
/* Error message and backtrace were already printed to stderr
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
JS_GetException(js);
return 0;
}
}

View File

@@ -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_GetRuntime(js), js_##TYPE##_id, &js_##TYPE##_class);\
JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \

View File

@@ -24,21 +24,26 @@ typedef struct letter {
typedef struct cell_rt {
JSContext *context;
JSValue idx_buffer;
JSValue on_exception;
JSValue message_handle;
/* 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;
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 */
@@ -47,14 +52,11 @@ 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;
@@ -63,8 +65,6 @@ 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);

3552
source/mach.c Normal file

File diff suppressed because it is too large Load Diff

1824
source/mcode.c Normal file

File diff suppressed because it is too large Load Diff

194
source/qbe_helpers.c Normal file
View File

@@ -0,0 +1,194 @@
/*
* QBE Helper Functions
*
* Thin C wrappers called from QBE-generated code for operations
* that are too complex to inline: float arithmetic, float comparison,
* string comparison, bitwise ops on floats, and boolean conversion.
*/
#include "quickjs-internal.h"
#include <math.h>
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum {
QBE_CMP_EQ = 0,
QBE_CMP_NE = 1,
QBE_CMP_LT = 2,
QBE_CMP_LE = 3,
QBE_CMP_GT = 4,
QBE_CMP_GE = 5
};
/* ============================================================
Float binary arithmetic
============================================================ */
static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b,
double (*op)(double, double)) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
double r = op(da, db);
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
static double op_add(double a, double b) { return a + b; }
static double op_sub(double a, double b) { return a - b; }
static double op_mul(double a, double b) { return a * b; }
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_add);
}
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub);
}
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_mul);
}
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
if (db == 0.0)
return JS_NULL;
double r = da / db;
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
if (db == 0.0)
return JS_NULL;
double r = fmod(da, db);
if (!isfinite(r))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
double r = pow(da, db);
if (!isfinite(r) && isfinite(da) && isfinite(db))
return JS_NULL;
return JS_NewFloat64(ctx, r);
}
/* ============================================================
Float unary ops
============================================================ */
JSValue qbe_float_neg(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, -d);
}
JSValue qbe_float_inc(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, d + 1);
}
JSValue qbe_float_dec(JSContext *ctx, JSValue v) {
double d;
JS_ToFloat64(ctx, &d, v);
return JS_NewFloat64(ctx, d - 1);
}
/* ============================================================
Float comparison — returns 0 or 1 for QBE branching
============================================================ */
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
double da, db;
JS_ToFloat64(ctx, &da, a);
JS_ToFloat64(ctx, &db, b);
switch (op) {
case QBE_CMP_EQ: return da == db;
case QBE_CMP_NE: return da != db;
case QBE_CMP_LT: return da < db;
case QBE_CMP_LE: return da <= db;
case QBE_CMP_GT: return da > db;
case QBE_CMP_GE: return da >= db;
default: return 0;
}
}
/* ============================================================
Boolean conversion wrapper
============================================================ */
int qbe_to_bool(JSContext *ctx, JSValue v) {
return JS_ToBool(ctx, v);
}
/* ============================================================
Bitwise not on non-int (float -> int32 -> ~)
============================================================ */
JSValue qbe_bnot(JSContext *ctx, JSValue v) {
int32_t i;
JS_ToInt32(ctx, &i, v);
return JS_NewInt32(ctx, ~i);
}
/* ============================================================
Bitwise binary ops on floats (convert both to int32, apply, re-tag)
============================================================ */
JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia & ib);
}
JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia | ib);
}
JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia ^ ib);
}
/* ============================================================
Shift ops on floats (convert to int32, shift, re-tag)
============================================================ */
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia << (ib & 31));
}
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, ia >> (ib & 31));
}
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
int32_t ia, ib;
JS_ToInt32(ctx, &ia, a);
JS_ToInt32(ctx, &ib, b);
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
}

View File

@@ -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 = JS_DupValue(js, argv[1]);
rt->message_handle_ref.val = argv[1];
rt->context = js;
JS_FreeCString(js, id);
)
@@ -106,8 +106,7 @@ JSC_SCALL(actor_setname,
JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception);
rt->on_exception = JS_DupValue(js,argv[0]);
rt->on_exception_ref.val = argv[0];
)
JSC_CCALL(actor_clock,

1900
source/quickjs-internal.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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 (JSRuntime *rt, size_t stack_size);
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
/* should be called when changing thread to update the stack top value
used to check stack overflow. */
void JS_UpdateStackTop (JSRuntime *rt);
void JS_UpdateStackTop (JSContext *ctx);
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 (JSRuntime *rt, JSClassID class_id,
int JS_NewClass (JSContext *ctx, JSClassID class_id,
const JSClassDef *class_def);
int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id);
int JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id);
extern JSClassID js_class_id_alloc;
@@ -577,11 +577,8 @@ static JS_BOOL JS_IsStone(JSValue v);
int JS_GetLength (JSContext *ctx, JSValue obj, int64_t *pres);
JSValue JS_Throw (JSContext *ctx, JSValue obj);
void JS_SetUncatchableException (JSContext *ctx, JS_BOOL flag);
JSValue JS_GetException (JSContext *ctx);
JS_BOOL JS_HasException (JSContext *ctx);
JS_BOOL JS_IsError (JSContext *ctx, JSValue val);
JSValue JS_NewError (JSContext *ctx);
JSValue __js_printf_like (2, 3)
JS_ThrowSyntaxError (JSContext *ctx, const char *fmt, ...);
JSValue __js_printf_like (2, 3)
@@ -697,6 +694,9 @@ JSValue JS_GetProperty (JSContext *ctx, JSValue this_obj, JSValue prop);
// For records
JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop);
int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val);
// Set property on the global object
int JS_SetGlobalStr (JSContext *ctx, const char *prop, JSValue val);
int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val);
JSValue JS_GetPrototype (JSContext *ctx, JSValue val);
@@ -714,16 +714,6 @@ JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj);
JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
/* Compile source code to bytecode without executing.
'input' must be zero terminated i.e. input[input_len] = '\0'.
Returns unlinked bytecode on success, JS_EXCEPTION on error. */
JSValue JS_Compile (JSContext *ctx, const char *input, size_t input_len,
const char *filename);
/* Link compiled bytecode with environment and execute.
env should be stoned record or null.
Variables resolve: env first, then global intrinsics. */
JSValue JS_Integrate (JSContext *ctx, JSValue bytecode, JSValue env);
void JS_SetOpaque (JSValue obj, void *opaque);
void *JS_GetOpaque (JSValue obj, JSClassID class_id);
void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id);
@@ -740,7 +730,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 (JSRuntime *rt, JSInterruptHandler *cb,
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque);
/* select which debug info is stripped from the compiled code */
#define JS_STRIP_SOURCE (1 << 0) /* strip source code */
@@ -749,28 +739,6 @@ void JS_SetInterruptHandler (JSRuntime *rt, JSInterruptHandler *cb,
void JS_SetStripInfo (JSRuntime *rt, int flags);
int JS_GetStripInfo (JSRuntime *rt);
/* Object Writer/Reader (currently only used to handle precompiled code) */
#define JS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */
#define JS_WRITE_OBJ_BSWAP (1 << 1) /* byte swapped output */
#define JS_WRITE_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */
#define JS_WRITE_OBJ_REFERENCE \
(1 << 3) /* allow object references to \
encode arbitrary object \
graph */
uint8_t *JS_WriteObject (JSContext *ctx, size_t *psize, JSValue obj,
int flags);
uint8_t *JS_WriteObject2 (JSContext *ctx, size_t *psize, JSValue obj,
int flags, uint8_t ***psab_tab,
size_t *psab_tab_len);
#define JS_READ_OBJ_BYTECODE (1 << 0) /* allow function/module */
#define JS_READ_OBJ_ROM_DATA (1 << 1) /* avoid duplicating 'buf' data */
#define JS_READ_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */
#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */
JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len,
int flags);
/* Dump bytecode of a compiled function (for debugging) */
void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val);
/* C function definition */
typedef enum JSCFunctionEnum {
@@ -1100,149 +1068,58 @@ void *js_malloc_rt (size_t size);
void *js_mallocz_rt (size_t size);
void js_free_rt (void *ptr);
/* ============================================================================
Context-Neutral Module Format (CellModule)
============================================================================ */
struct cJSON;
/* Capture descriptor - what a nested function closes over */
typedef enum {
CAP_FROM_PARENT_LOCAL = 1, /* capture local from parent function */
CAP_FROM_PARENT_UPVALUE = 2 /* forward upvalue from parent's upvalues */
} CellCapKind;
/* Compiled bytecode (context-free, serializable) */
typedef struct MachCode MachCode;
typedef struct CellCapDesc {
uint8_t kind; /* CAP_FROM_PARENT_LOCAL or CAP_FROM_PARENT_UPVALUE */
uint16_t index; /* local index in parent, or upvalue index in parent */
} CellCapDesc;
/* Compile AST cJSON tree to context-free MachCode. */
MachCode *JS_CompileMachTree(struct cJSON *ast);
/* External relocation - for integrate-time patching */
typedef enum {
EXT_GET = 1, /* OP_get_var -> OP_get_env_slot or OP_get_global_slot */
EXT_SET = 2 /* OP_put_var -> OP_set_env_slot or OP_set_global_slot */
} CellExtKind;
/* Compile AST JSON string to context-free MachCode. */
MachCode *JS_CompileMach(const char *ast_json);
typedef struct CellExternalReloc {
uint32_t pc_offset; /* where operand lives in bytecode */
uint32_t name_sid; /* string id of the external name */
uint8_t kind; /* EXT_GET or EXT_SET */
} CellExternalReloc;
/* Free a compiled MachCode tree. */
void JS_FreeMachCode(MachCode *mc);
/* Constant types in cpool */
typedef enum {
CELL_CONST_NULL = 0,
CELL_CONST_INT = 1,
CELL_CONST_FLOAT = 2,
CELL_CONST_STRING = 3, /* string_sid into module string table */
CELL_CONST_UNIT = 4 /* unit_id for nested function */
} CellConstType;
/* Serialize MachCode to binary. Returns sys_malloc'd buffer; caller frees. */
uint8_t *JS_SerializeMachCode(MachCode *mc, size_t *out_size);
typedef struct CellConst {
uint8_t type; /* CellConstType */
union {
int32_t i32;
double f64;
uint32_t string_sid;
uint32_t unit_id;
};
} CellConst;
/* Deserialize binary back to MachCode. Returns NULL on error. */
MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size);
/* Per-unit structure (context-neutral, flattened) */
typedef struct CellUnit {
/* Constant pool */
uint32_t const_count;
CellConst *constants;
/* Load compiled MachCode into a JSContext, materializing JSValues. */
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
/* Bytecode */
uint32_t bytecode_len;
uint8_t *bytecode;
/* Dump MACH bytecode to stdout. Takes AST cJSON tree. */
void JS_DumpMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
/* Stack requirements */
uint16_t arg_count;
uint16_t var_count;
uint16_t stack_size;
/* Dump MACH bytecode to stdout. Takes AST JSON string. */
void JS_DumpMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Upvalue (capture) descriptors */
uint16_t upvalue_count;
CellCapDesc *upvalues;
/* Compile and execute MACH bytecode from AST cJSON tree. */
JSValue JS_RunMachTree (JSContext *ctx, struct cJSON *ast, JSValue env);
/* External relocations */
uint32_t external_count;
CellExternalReloc *externals;
/* Compile and execute MACH bytecode from AST JSON string. */
JSValue JS_RunMach (JSContext *ctx, const char *ast_json, JSValue env);
/* Debug info (optional) */
uint32_t pc2line_len;
uint8_t *pc2line;
uint32_t name_sid; /* unit name for stack traces */
} CellUnit;
/* Deserialize and execute pre-compiled MACH binary bytecode. */
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
/* Module-level structure (context-neutral) */
#define CELL_MODULE_MAGIC 0x4C4C4543 /* "CELL" */
#define CELL_MODULE_VERSION 1
/* Execute MCODE from cJSON tree. Takes ownership of root. */
JSValue JS_CallMcodeTree (JSContext *ctx, struct cJSON *root);
typedef struct CellModule {
uint32_t magic; /* CELL_MODULE_MAGIC */
uint8_t version; /* CELL_MODULE_VERSION */
uint8_t flags;
/* Execute MCODE from cJSON tree with hidden env. Takes ownership of root. */
JSValue JS_CallMcodeTreeEnv (JSContext *ctx, struct cJSON *root, JSValue env);
/* Shared string table (module-global) */
uint32_t string_count;
uint32_t string_data_size;
uint8_t *string_data; /* concatenated UTF-8 strings */
uint32_t *string_offsets; /* offset for each string */
/* Unit table (entry 0 is the main/entry unit) */
uint32_t unit_count;
CellUnit *units;
/* Debug: source stored once at module level */
uint32_t source_len;
char *source;
} CellModule;
/* Free a CellModule and all its contents */
void cell_module_free (CellModule *mod);
/* Write a CellModule to a byte buffer.
Returns allocated buffer (caller must free with pjs_free), or NULL on error. */
uint8_t *cell_module_write (CellModule *mod, size_t *out_len);
/* Read a CellModule from a byte buffer.
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *cell_module_read (const uint8_t *buf, size_t buf_len);
/* Convert compiled JSFunctionBytecode to CellModule.
Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */
CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_func);
/* Compile source code directly to CellModule.
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 JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_AST (JSContext *ctx, const char *source, size_t len, const char *filename);
/* Tokenize source code and return token array as JSON string.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Tokenize (JSContext *ctx, const char *source, size_t len, const char *filename);
/* Generate register-based machine code from AST JSON.
Returns malloc'd JSON string (caller must free), or NULL on error. */
char *JS_Mach (JSContext *ctx, const char *ast_json);
/* Link and execute register-based machine code.
mach_json: JSON output from JS_Mach
env: stone record for external variable resolution (or JS_NULL)
/* Parse and execute MCODE JSON string.
Returns result of execution, or JS_EXCEPTION on error. */
JSValue JS_IntegrateRegister (JSContext *ctx, const char *mach_json, JSValue env);
JSValue JS_CallMcode (JSContext *ctx, const char *mcode_json);
/* Dump linked register VM bytecode to stdout for debugging.
Links mach_json and prints the resulting bytecode with resolved labels. */
void JS_DumpRegisterMach (JSContext *ctx, const char *mach_json, JSValue env);
/* Integrate a CellModule with an environment and execute.
Returns callable function value, or JS_EXCEPTION on error. */
JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env);
/* Get stack trace as cJSON array of frame objects.
Returns NULL if no register VM frame is active.
Caller must call cJSON_Delete() on the result. */
struct cJSON *JS_GetStack (JSContext *ctx);
#undef js_unlikely
#undef inline

13394
source/runtime.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -266,11 +266,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context;
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);
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);
for (int i = 0; i < hmlen(actor->timers); i++) {
JS_FreeValue(js, actor->timers[i].value);
@@ -289,10 +289,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id);
pthread_mutex_unlock(actor->mutex);
@@ -419,8 +417,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret);
}
@@ -434,14 +432,13 @@ 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 = JS_NULL;
actor->unneeded_ref.val = JS_NULL;
goto END;
}
actor->unneeded = JS_DupValue(actor->context, fn);
actor->unneeded_ref.val = fn;
actor->ar_secs = seconds;
END:
@@ -493,11 +490,10 @@ cell_rt *create_actor(void *wota)
actor->heap = mi_heap_new();
#endif
actor->init_wota = wota;
actor->idx_buffer = JS_NULL;
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
/* 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. */
arrsetcap(actor->letters, 5);
@@ -587,7 +583,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, JS_NULL, 1, &arg);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) {

View File

@@ -118,11 +118,11 @@ void actor_free(cell_rt *actor)
JSContext *js = actor->context;
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);
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);
/* Free timer callbacks stored in actor */
for (int i = 0; i < hmlen(actor->timers); i++) {
@@ -142,10 +142,8 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JSRuntime *rt = JS_GetRuntime(js);
JS_SetInterruptHandler(rt, NULL, NULL);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
JS_FreeRuntime(rt);
free(actor->id);
free(actor);
@@ -157,14 +155,13 @@ void actor_free(cell_rt *actor)
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
{
if (actor->disrupt) return;
JS_FreeValue(actor->context, actor->unneeded);
if (!JS_IsFunction(actor->context, fn)) {
actor->unneeded = JS_NULL;
if (!JS_IsFunction(fn)) {
actor->unneeded_ref.val = JS_NULL;
goto END;
}
actor->unneeded = JS_DupValue(actor->context, fn);
actor->unneeded_ref.val = fn;
actor->ar_secs = seconds;
END:
@@ -257,8 +254,8 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
actor->disrupt = 1;
if (!JS_IsNull(actor->unneeded)) {
JSValue ret = JS_Call(actor->context, actor->unneeded, JS_NULL, 0, NULL);
if (!JS_IsNull(actor->unneeded_ref.val)) {
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
uncaught_exception(actor->context, ret);
}
@@ -328,11 +325,7 @@ cell_rt *create_actor(void *wota)
{
cell_rt *actor = calloc(sizeof(*actor), 1);
actor->init_wota = wota;
actor->idx_buffer = JS_NULL;
actor->message_handle = JS_NULL;
actor->unneeded = JS_NULL;
actor->on_exception = JS_NULL;
actor->actor_sym = JS_NULL;
/* GCRef fields are registered after JSContext creation in script_startup. */
arrsetcap(actor->letters, 5);
@@ -408,7 +401,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, JS_NULL, 1, &arg);
result = JS_Call(actor->context, actor->message_handle_ref.val, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result);
JS_FreeValue(actor->context, arg);
} else if (l.type == LETTER_CALLBACK) {

View File

@@ -1742,9 +1742,12 @@ TEST(has_exception_initially_false) {
return 1;
}
TEST(new_error) {
JSValue err = JS_NewError(ctx);
ASSERT(JS_IsError(ctx, err));
TEST(throw_and_check_exception) {
ASSERT(!JS_HasException(ctx));
JS_ThrowTypeError(ctx, "test error");
ASSERT(JS_HasException(ctx));
JS_GetException(ctx); /* clear it */
ASSERT(!JS_HasException(ctx));
return 1;
}
@@ -2001,496 +2004,11 @@ TEST(wota_encode_blob) {
return 1;
}
/* ============================================================================
CELL MODULE TESTS - Serialize/Deserialize bytecode
============================================================================ */
TEST(cell_module_compile_basic) {
/* Compile simple source to CellModule */
const char *source = "1 + 2";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Check module has units */
ASSERT_MSG(mod->unit_count > 0, "Module has no units");
ASSERT_MSG(mod->units[0].bytecode_len > 0, "Unit has no bytecode");
cell_module_free(mod);
return 1;
}
TEST(cell_module_write_read) {
/* Compile, serialize, deserialize */
const char *source = "var x = 10; x * 2";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Serialize */
size_t len;
uint8_t *buf = cell_module_write(mod, &len);
ASSERT_MSG(buf != NULL, "cell_module_write returned NULL");
ASSERT_MSG(len > 0, "cell_module_write produced empty buffer");
/* Deserialize */
CellModule *mod2 = cell_module_read(buf, len);
free(buf);
ASSERT_MSG(mod2 != NULL, "cell_module_read returned NULL");
/* Verify structure matches */
ASSERT_MSG(mod2->unit_count == mod->unit_count, "unit_count mismatch");
ASSERT_MSG(mod2->string_count == mod->string_count, "string_count mismatch");
cell_module_free(mod);
cell_module_free(mod2);
return 1;
}
TEST(cell_module_integrate_basic) {
/* Compile, then integrate and execute */
const char *source = "3 + 4";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Integrate into context */
JSValue func = cell_module_integrate(ctx, mod, JS_NULL);
if (JS_IsException(func)) {
cell_module_free(mod);
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
/* Execute */
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
cell_module_free(mod);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_INT(result, 7);
return 1;
}
TEST(cell_module_roundtrip_execute) {
/* Full round-trip: compile -> write -> read -> integrate -> execute */
const char *source = "var a = 5; var b = 3; a * b";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Serialize */
size_t len;
uint8_t *buf = cell_module_write(mod, &len);
cell_module_free(mod);
ASSERT_MSG(buf != NULL, "cell_module_write returned NULL");
/* Deserialize */
CellModule *mod2 = cell_module_read(buf, len);
free(buf);
ASSERT_MSG(mod2 != NULL, "cell_module_read returned NULL");
/* Integrate and execute */
JSValue func = cell_module_integrate(ctx, mod2, JS_NULL);
cell_module_free(mod2);
if (JS_IsException(func)) {
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_INT(result, 15);
return 1;
}
TEST(cell_module_string_constant) {
/* Test string constant handling */
const char *source = "'hello' + ' world'";
CellModule *mod = JS_CompileModule(ctx, source, strlen(source), "<test>");
ASSERT_MSG(mod != NULL, "JS_CompileModule returned NULL");
/* Verify string table has entries */
ASSERT_MSG(mod->string_count > 0, "Module has no strings");
/* Integrate and execute */
JSValue func = cell_module_integrate(ctx, mod, JS_NULL);
cell_module_free(mod);
if (JS_IsException(func)) {
ASSERT_MSG(0, "cell_module_integrate threw exception");
}
JSValue result = JS_Call(ctx, func, JS_NULL, 0, NULL);
JS_FreeValue(ctx, func);
if (JS_IsException(result)) {
ASSERT_MSG(0, "JS_Call threw exception");
}
ASSERT_STR(result, "hello world");
return 1;
}
/* ============================================================================
ERROR RECOVERY TESTS - Helper macros
============================================================================ */
#define ASSERT_HAS_ERRORS(json_str, min_count) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (!_errs || !cJSON_IsArray(_errs) || cJSON_GetArraySize(_errs) < (min_count)) { \
printf("[line %d: expected at least %d error(s), got %d] ", __LINE__, (min_count), \
_errs && cJSON_IsArray(_errs) ? cJSON_GetArraySize(_errs) : 0); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_NO_ERRORS(json_str) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
if (_errs && cJSON_IsArray(_errs) && cJSON_GetArraySize(_errs) > 0) { \
cJSON *_first = cJSON_GetArrayItem(_errs, 0); \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_first, "message")); \
printf("[line %d: expected no errors, got: %s] ", __LINE__, _msg ? _msg : "?"); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
#define ASSERT_ERROR_MSG_CONTAINS(json_str, substring) do { \
cJSON *_root = cJSON_Parse(json_str); \
ASSERT_MSG(_root != NULL, "failed to parse JSON output"); \
cJSON *_errs = cJSON_GetObjectItem(_root, "errors"); \
int _found = 0; \
if (_errs && cJSON_IsArray(_errs)) { \
cJSON *_e; \
cJSON_ArrayForEach(_e, _errs) { \
const char *_msg = cJSON_GetStringValue(cJSON_GetObjectItem(_e, "message")); \
if (_msg && strstr(_msg, (substring))) { _found = 1; break; } \
} \
} \
if (!_found) { \
printf("[line %d: no error containing '%s'] ", __LINE__, (substring)); \
cJSON_Delete(_root); \
return 0; \
} \
cJSON_Delete(_root); \
} while(0)
/* ============================================================================
TOKENIZER ERROR TESTS
============================================================================ */
TEST(tokenize_unterminated_string) {
const char *src = "var x = \"hello";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated string");
free(json);
return 1;
}
TEST(tokenize_unterminated_template) {
const char *src = "var x = `hello";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated template");
free(json);
return 1;
}
TEST(tokenize_unterminated_block_comment) {
const char *src = "var x /* comment";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "unterminated block comment");
free(json);
return 1;
}
TEST(tokenize_malformed_hex) {
const char *src = "var x = 0x";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed hex");
free(json);
return 1;
}
TEST(tokenize_malformed_binary) {
const char *src = "var x = 0b";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "malformed binary");
free(json);
return 1;
}
TEST(tokenize_malformed_exponent) {
const char *src = "var x = 1e+";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "no digits after exponent");
free(json);
return 1;
}
TEST(tokenize_valid_no_errors) {
const char *src = "var x = 42";
char *json = JS_Tokenize(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_Tokenize returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
PARSER ERROR TESTS
============================================================================ */
TEST(ast_missing_identifier_after_var) {
const char *src = "var = 1";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "expected identifier");
free(json);
return 1;
}
TEST(ast_missing_initializer_def) {
const char *src = "def x";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "missing initializer");
free(json);
return 1;
}
TEST(ast_recovery_continues_after_error) {
const char *src = "var = 1; var y = 2";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_HAS_ERRORS(json, 1);
/* Check that 'y' statement is present in the AST */
ASSERT_MSG(strstr(json, "\"y\"") != NULL, "recovery failed: 'y' not in AST");
free(json);
return 1;
}
TEST(ast_valid_no_errors) {
const char *src = "var x = 1; var y = 2";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
AST SEMANTIC ERROR TESTS
============================================================================ */
TEST(ast_sem_assign_to_const) {
const char *src = "def x = 5; x = 3";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_assign_to_arg) {
const char *src = "function(x) { x = 5; }";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_redeclare_const) {
const char *src = "def x = 1; def x = 2";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot redeclare constant");
free(json);
return 1;
}
TEST(ast_sem_break_outside_loop) {
const char *src = "break";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_continue_outside_loop) {
const char *src = "continue";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "outside of loop");
free(json);
return 1;
}
TEST(ast_sem_break_inside_loop_ok) {
const char *src = "while (true) { break; }";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_increment_const) {
const char *src = "def x = 1; x++";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_ERROR_MSG_CONTAINS(json, "cannot assign to constant");
free(json);
return 1;
}
TEST(ast_sem_shadow_var_ok) {
const char *src = "var array = []; array";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_var_assign_ok) {
const char *src = "var x = 1; x = x + 1";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
TEST(ast_sem_nested_function_scope) {
const char *src = "var x = 1; function f(x) { return x + 1; }";
char *json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(json != NULL, "JS_AST returned NULL");
ASSERT_NO_ERRORS(json);
free(json);
return 1;
}
/* ============================================================================
CODEGEN/SEMANTIC ERROR TESTS
============================================================================ */
TEST(mach_assign_to_const) {
const char *src = "def x = 1; x = 2";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot assign to constant");
free(mach_json);
return 1;
}
TEST(mach_assign_to_arg) {
const char *src = "function f(x) { x = 5 }";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot assign to function argument");
free(mach_json);
return 1;
}
TEST(mach_redeclare_const) {
const char *src = "def x = 1; def x = 2";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "cannot redeclare constant");
free(mach_json);
return 1;
}
TEST(mach_break_outside_loop) {
const char *src = "break";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "outside of loop");
free(mach_json);
return 1;
}
TEST(mach_continue_outside_loop) {
const char *src = "continue";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "outside of loop");
free(mach_json);
return 1;
}
TEST(mach_assign_unbound_var) {
const char *src = "x = 42";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_ERROR_MSG_CONTAINS(mach_json, "not declared");
free(mach_json);
return 1;
}
TEST(mach_shadow_is_ok) {
const char *src = "var array = []; array";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_NO_ERRORS(mach_json);
free(mach_json);
return 1;
}
TEST(mach_valid_no_errors) {
const char *src = "var x = 1; x = x + 1";
char *ast_json = JS_AST(ctx, src, strlen(src), "<test>");
ASSERT_MSG(ast_json != NULL, "JS_AST returned NULL");
char *mach_json = JS_Mach(ctx, ast_json);
free(ast_json);
ASSERT_MSG(mach_json != NULL, "JS_Mach returned NULL");
ASSERT_NO_ERRORS(mach_json);
free(mach_json);
return 1;
}
/* ============================================================================
MAIN TEST RUNNER
============================================================================ */
int run_c_test_suite(JSContext *ctx)
{
printf("\n=== Cell Runtime C Test Suite ===\n\n");
@@ -2664,7 +2182,7 @@ int run_c_test_suite(JSContext *ctx)
printf("\nExceptions/Errors:\n");
RUN_TEST(has_exception_initially_false);
RUN_TEST(new_error);
RUN_TEST(throw_and_check_exception);
printf("\nFloat Edge Cases:\n");
RUN_TEST(float_small_positive);
@@ -2696,49 +2214,6 @@ int run_c_test_suite(JSContext *ctx)
RUN_TEST(wota_encode_nested_array);
RUN_TEST(wota_encode_blob);
// CellModule tests
RUN_TEST(cell_module_compile_basic);
RUN_TEST(cell_module_write_read);
RUN_TEST(cell_module_integrate_basic);
RUN_TEST(cell_module_roundtrip_execute);
RUN_TEST(cell_module_string_constant);
printf("\nTokenizer Errors:\n");
RUN_TEST(tokenize_unterminated_string);
RUN_TEST(tokenize_unterminated_template);
RUN_TEST(tokenize_unterminated_block_comment);
RUN_TEST(tokenize_malformed_hex);
RUN_TEST(tokenize_malformed_binary);
RUN_TEST(tokenize_malformed_exponent);
RUN_TEST(tokenize_valid_no_errors);
printf("\nParser Errors:\n");
RUN_TEST(ast_missing_identifier_after_var);
RUN_TEST(ast_missing_initializer_def);
RUN_TEST(ast_recovery_continues_after_error);
RUN_TEST(ast_valid_no_errors);
printf("\nAST Semantic Errors:\n");
RUN_TEST(ast_sem_assign_to_const);
RUN_TEST(ast_sem_assign_to_arg);
RUN_TEST(ast_sem_redeclare_const);
RUN_TEST(ast_sem_break_outside_loop);
RUN_TEST(ast_sem_continue_outside_loop);
RUN_TEST(ast_sem_break_inside_loop_ok);
RUN_TEST(ast_sem_increment_const);
RUN_TEST(ast_sem_shadow_var_ok);
RUN_TEST(ast_sem_var_assign_ok);
RUN_TEST(ast_sem_nested_function_scope);
printf("\nCodegen Errors:\n");
RUN_TEST(mach_assign_to_const);
RUN_TEST(mach_assign_to_arg);
RUN_TEST(mach_redeclare_const);
RUN_TEST(mach_break_outside_loop);
RUN_TEST(mach_continue_outside_loop);
RUN_TEST(mach_assign_unbound_var);
RUN_TEST(mach_shadow_is_ok);
RUN_TEST(mach_valid_no_errors);
printf("\n=================================\n");
printf("Results: %d passed, %d failed\n", tests_passed, tests_failed);

641
syntax_suite.ce Normal file
View File

@@ -0,0 +1,641 @@
// 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 = ""
var _i = 0
for (_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 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
var i = 0
for (i = 0; i < 5; i++) sum += i
assert_eq(sum, 10, "for basic")
})
run("for break continue", function() {
var sum = 0
var i = 0
for (i = 0; i < 10; i++) {
if (i == 5) break
sum += i
}
assert_eq(sum, 10, "for break")
sum = 0
for (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
var i = 0, j = 0
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
sum++
assert_eq(sum, 9, "nested for")
})
// === 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))
var _j = 0
if (failed > 0) {
print("")
for (_j = 0; _j < failed; _j++) {
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
}
}

39
tests/demo.ce Normal file
View File

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

6
tokenize.ce Normal file
View File

@@ -0,0 +1,6 @@
var fd = use("fd")
var tokenize = use("tokenize")
var filename = args[0]
var src = text(fd.slurp(filename))
var result = tokenize(src, filename)
print(json.encode({filename: result.filename, tokens: result.tokens}))

516
tokenize.cm Normal file
View File

@@ -0,0 +1,516 @@
var tokenize = function(src, filename) {
var len = length(src)
var cp = []
var _i = 0
while (_i < len) {
push(cp, codepoint(src[_i]))
_i = _i + 1
}
var pos = 0
var row = 0
var col = 0
var tokens = []
// Codepoint constants
def CP_LF = 10
def CP_CR = 13
def CP_TAB = 9
def CP_SPACE = 32
def CP_BANG = 33
def CP_DQUOTE = 34
def CP_HASH = 35
def CP_DOLLAR = 36
def CP_PERCENT = 37
def CP_AMP = 38
def CP_SQUOTE = 39
def CP_LPAREN = 40
def CP_RPAREN = 41
def CP_STAR = 42
def CP_PLUS = 43
def CP_COMMA = 44
def CP_MINUS = 45
def CP_DOT = 46
def CP_SLASH = 47
def CP_0 = 48
def CP_1 = 49
def CP_7 = 55
def CP_9 = 57
def CP_COLON = 58
def CP_SEMI = 59
def CP_LT = 60
def CP_EQ = 61
def CP_GT = 62
def CP_QMARK = 63
def CP_AT = 64
def CP_A = 65
def CP_B = 66
def CP_E = 69
def CP_F = 70
def CP_O = 79
def CP_X = 88
def CP_Z = 90
def CP_LBRACKET = 91
def CP_BSLASH = 92
def CP_RBRACKET = 93
def CP_CARET = 94
def CP_UNDERSCORE = 95
def CP_BACKTICK = 96
def CP_a = 97
def CP_b = 98
def CP_e = 101
def CP_f = 102
def CP_n = 110
def CP_o = 111
def CP_r = 114
def CP_t = 116
def CP_u = 117
def CP_x = 120
def CP_z = 122
def CP_LBRACE = 123
def CP_PIPE = 124
def CP_RBRACE = 125
def CP_TILDE = 126
// Keywords lookup
var keywords = {
if: "if", in: "in", do: "do", go: "go",
var: "var", def: "def", for: "for",
else: "else", this: "this", null: "null", true: "true",
false: "false", while: "while", break: "break",
return: "return", delete: "delete",
disrupt: "disrupt", function: "function", continue: "continue",
disruption: "disruption"
}
var pk = function() {
if (pos >= len) return -1
return cp[pos]
}
var pk_at = function(n) {
var idx = pos + n
if (idx >= len) return -1
return cp[idx]
}
var adv = function() {
var c = cp[pos]
pos = pos + 1
if (c == CP_LF) {
row = row + 1
col = 0
} else {
col = col + 1
}
return c
}
var is_digit = function(c) {
return c >= CP_0 && c <= CP_9
}
var is_hex = function(c) {
return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F)
}
var hex_val = function(c) {
if (c >= CP_0 && c <= CP_9) return c - CP_0
if (c >= CP_a && c <= CP_f) return c - CP_a + 10
if (c >= CP_A && c <= CP_F) return c - CP_A + 10
return 0
}
var read_unicode_escape = function() {
var cp_val = 0
var hi = 0
while (hi < 4 && pos < len && is_hex(pk())) {
cp_val = cp_val * 16 + hex_val(adv())
hi = hi + 1
}
return character(cp_val)
}
var is_alpha = function(c) {
return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z)
}
var is_alnum = function(c) {
return is_alpha(c) || is_digit(c)
}
var is_ident_start = function(c) {
return is_alpha(c) || c == CP_UNDERSCORE || c == CP_DOLLAR
}
var is_ident_char = function(c) {
return is_alnum(c) || c == CP_UNDERSCORE || c == CP_DOLLAR || c == CP_QMARK || c == CP_BANG
}
var substr = function(start, end) {
var s = ""
var i = start
while (i < end) {
s = s + character(cp[i])
i = i + 1
}
return s
}
var read_string = function(quote_cp) {
var start = pos
var start_row = row
var start_col = col
var value = ""
var esc = 0
adv() // skip opening quote
while (pos < len && pk() != quote_cp) {
if (pk() == CP_BSLASH) {
adv()
esc = adv()
if (esc == CP_n) { value = value + "\n" }
else if (esc == CP_t) { value = value + "\t" }
else if (esc == CP_r) { value = value + "\r" }
else if (esc == CP_BSLASH) { value = value + "\\" }
else if (esc == CP_SQUOTE) { value = value + "'" }
else if (esc == CP_DQUOTE) { value = value + "\"" }
else if (esc == CP_0) { value = value + character(0) }
else if (esc == CP_BACKTICK) { value = value + "`" }
else if (esc == CP_u) { value = value + read_unicode_escape() }
else { value = value + character(esc) }
} else {
value = value + character(adv())
}
}
if (pos < len) adv() // skip closing quote
push(tokens, {
kind: "text", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: value
})
}
var read_template = function() {
var start = pos
var start_row = row
var start_col = col
var value = ""
var depth = 0
var tc = 0
var q = 0
adv() // skip opening backtick
while (pos < len && pk() != CP_BACKTICK) {
if (pk() == CP_BSLASH && pos + 1 < len) {
value = value + character(adv())
value = value + character(adv())
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) {
value = value + character(adv()) // $
value = value + character(adv()) // {
depth = 1
while (pos < len && depth > 0) {
tc = pk()
if (tc == CP_LBRACE) { depth = depth + 1; value = value + character(adv()) }
else if (tc == CP_RBRACE) {
depth = depth - 1
if (depth > 0) { value = value + character(adv()) }
else { value = value + character(adv()) }
}
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) {
q = adv()
value = value + character(q)
while (pos < len && pk() != q) {
if (pk() == CP_BSLASH && pos + 1 < len) {
value = value + character(adv())
}
value = value + character(adv())
}
if (pos < len) { value = value + character(adv()) }
} else { value = value + character(adv()) }
}
} else {
value = value + character(adv())
}
}
if (pos < len) adv() // skip closing backtick
push(tokens, {
kind: "text", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: value
})
}
var read_number = function() {
var start = pos
var start_row = row
var start_col = col
var raw = ""
if (pk() == CP_0 && (pk_at(1) == CP_x || pk_at(1) == CP_X)) {
adv(); adv()
while (pos < len && (is_hex(pk()) || pk() == CP_UNDERSCORE)) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_b || pk_at(1) == CP_B)) {
adv(); adv()
while (pos < len && (pk() == CP_0 || pk() == CP_1 || pk() == CP_UNDERSCORE)) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_o || pk_at(1) == CP_O)) {
adv(); adv()
while (pos < len && pk() >= CP_0 && pk() <= CP_7) adv()
} else {
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
if (pos < len && pk() == CP_DOT) {
adv()
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
}
if (pos < len && (pk() == CP_e || pk() == CP_E)) {
adv()
if (pos < len && (pk() == CP_PLUS || pk() == CP_MINUS)) adv()
while (pos < len && is_digit(pk())) adv()
}
}
raw = substr(start, pos)
push(tokens, {
kind: "number", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: raw, number: number(raw)
})
}
var read_name = function() {
var start = pos
var start_row = row
var start_col = col
var name = ""
var kw = null
while (pos < len && is_ident_char(pk())) adv()
name = substr(start, pos)
kw = keywords[name]
if (kw != null) {
push(tokens, {
kind: kw, at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col
})
} else {
push(tokens, {
kind: "name", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: name
})
}
}
var read_comment = function() {
var start = pos
var start_row = row
var start_col = col
var raw = ""
if (pk_at(1) == CP_SLASH) {
while (pos < len && pk() != CP_LF && pk() != CP_CR) adv()
} else {
adv(); adv() // skip /*
while (pos < len) {
if (pk() == CP_STAR && pk_at(1) == CP_SLASH) {
adv(); adv()
break
}
adv()
}
}
raw = substr(start, pos)
push(tokens, {
kind: "comment", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: raw
})
}
var emit_op = function(kind, count) {
var start = pos
var start_row = row
var start_col = col
var i = 0
while (i < count) { adv(); i = i + 1 }
push(tokens, {
kind: kind, at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col
})
}
var emit_ident = function(count) {
var start = pos
var start_row = row
var start_col = col
var val = ""
var i = 0
while (i < count) { val = val + character(adv()); i = i + 1 }
push(tokens, {
kind: "name", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: val
})
}
var tokenize_one = function() {
var c = pk()
var start = 0
var start_row = 0
var start_col = 0
var raw = ""
if (c == -1) return false
if (c == CP_LF) {
start = pos; start_row = row; start_col = col
adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true
}
if (c == CP_CR) {
start = pos; start_row = row; start_col = col
adv()
if (pos < len && pk() == CP_LF) adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true
}
if (c == CP_SPACE || c == CP_TAB) {
start = pos; start_row = row; start_col = col
while (pos < len && (pk() == CP_SPACE || pk() == CP_TAB)) adv()
raw = substr(start, pos)
push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw })
return true
}
if (c == CP_SQUOTE || c == CP_DQUOTE) { read_string(c); return true }
if (c == CP_BACKTICK) { read_template(); return true }
if (is_digit(c)) { read_number(); return true }
if (c == CP_DOT && is_digit(pk_at(1))) { read_number(); return true }
if (is_ident_start(c)) { read_name(); return true }
if (c == CP_SLASH) {
if (pk_at(1) == CP_SLASH || pk_at(1) == CP_STAR) { read_comment(); return true }
if (pk_at(1) == CP_EQ) { emit_op("/=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("/", 1); return true
}
if (c == CP_STAR) {
if (pk_at(1) == CP_STAR) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("**=", 3); return true }
emit_op("**", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("*=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("*", 1); return true
}
if (c == CP_PERCENT) {
if (pk_at(1) == CP_EQ) { emit_op("%=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("%", 1); return true
}
if (c == CP_PLUS) {
if (pk_at(1) == CP_EQ) { emit_op("+=", 2); return true }
if (pk_at(1) == CP_PLUS) { emit_op("++", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("+", 1); return true
}
if (c == CP_MINUS) {
if (pk_at(1) == CP_EQ) { emit_op("-=", 2); return true }
if (pk_at(1) == CP_MINUS) { emit_op("--", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("-", 1); return true
}
if (c == CP_LT) {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op("<=", 2); return true }
if (pk_at(1) == CP_LT) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("<<=", 3); return true }
emit_op("<<", 2); return true
}
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("<", 1); return true
}
if (c == CP_GT) {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op(">=", 2); return true }
if (pk_at(1) == CP_GT) {
if (pk_at(2) == CP_GT) {
if (pk_at(3) == CP_BANG) { emit_ident(4); return true }
if (pk_at(3) == CP_EQ) { emit_op(">>>=", 4); return true }
emit_op(">>>", 3); return true
}
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op(">>=", 3); return true }
emit_op(">>", 2); return true
}
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op(">", 1); return true
}
if (c == CP_EQ) {
if (pk_at(1) == CP_EQ) {
if (pk_at(2) == CP_EQ) { emit_op("===", 3); return true }
emit_op("==", 2); return true
}
if (pk_at(1) == CP_GT) { emit_op("=>", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("=", 1); return true
}
if (c == CP_BANG) {
if (pk_at(1) == CP_EQ) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("!==", 3); return true }
emit_op("!=", 2); return true
}
emit_op("!", 1); return true
}
if (c == CP_AMP) {
if (pk_at(1) == CP_AMP) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("&&=", 3); return true }
emit_op("&&", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("&=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("&", 1); return true
}
if (c == CP_PIPE) {
if (pk_at(1) == CP_PIPE) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("||=", 3); return true }
emit_op("||", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("|=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("|", 1); return true
}
if (c == CP_CARET) {
if (pk_at(1) == CP_EQ) { emit_op("^=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("^", 1); return true
}
if (c == CP_LBRACKET) {
if (pk_at(1) == CP_RBRACKET && pk_at(2) == CP_BANG) { emit_ident(3); return true }
emit_op("[", 1); return true
}
if (c == CP_TILDE) {
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
emit_op("~", 1); return true
}
emit_op(character(c), 1)
return true
}
// Main loop
while (pos < len) {
tokenize_one()
}
// EOF token
push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col })
return {filename: filename, tokens: tokens, cp: cp}
}
return tokenize

BIN
tokenize.mach Normal file

Binary file not shown.

3494
vm_suite.ce Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

336
vm_test/syntax.txt Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More