347 Commits

Author SHA1 Message Date
John Alanbrook
b771b2b5d8 suite passes now with mcode->mach lowering 2026-02-12 09:40:24 -06:00
John Alanbrook
68fb440502 Merge branch 'mach' into bytecode_cleanup 2026-02-12 07:50:09 -06:00
John Alanbrook
e7a2f16004 mcode to mach 2026-02-12 05:23:33 -06:00
John Alanbrook
3a8a17ab60 mcode->mach 2026-02-12 04:28:14 -06:00
John Alanbrook
8a84be65e1 new path 2026-02-11 14:41:37 -06:00
John Alanbrook
c1910ee1db Merge branch 'mcode2' into mach 2026-02-11 13:16:07 -06:00
John Alanbrook
7036cdf2d1 Merge branch 'mach' into bytecode_cleanup 2026-02-11 13:15:20 -06:00
John Alanbrook
fbeec17ce5 simplifications 2026-02-11 13:15:04 -06:00
John Alanbrook
2c55ae8cb2 quiesence exit 2026-02-11 11:50:29 -06:00
John Alanbrook
259bc139fc rm stack usage 2026-02-11 10:17:55 -06:00
John Alanbrook
a252412eca removal of old code 2026-02-11 09:47:30 -06:00
John Alanbrook
b327e16463 rm unused functions 2026-02-11 09:09:40 -06:00
John Alanbrook
da6f096a56 qbe rt 2026-02-10 20:28:51 -06:00
John Alanbrook
1320ef9f47 Merge branch 'mcode2' into mach 2026-02-10 19:04:35 -06:00
John Alanbrook
ed4a5474d5 Merge branch 'mach' into mcode2 2026-02-10 19:04:22 -06:00
John Alanbrook
f52dd80d52 fix compile error 2026-02-10 19:02:42 -06:00
John Alanbrook
504e268b9d run native modules 2026-02-10 18:52:11 -06:00
John Alanbrook
0d47002167 add compile script 2026-02-10 18:35:18 -06:00
John Alanbrook
b65db63447 remove vm_test, update test harness 2026-02-10 17:52:57 -06:00
John Alanbrook
c1ccff5437 fix >256 object literal error 2026-02-10 17:42:58 -06:00
John Alanbrook
2f681fa366 output for parser stages and c runtime doc 2026-02-10 17:38:15 -06:00
John Alanbrook
682b1cf9cf Merge branch 'pitweb' into mcode2 2026-02-10 17:29:03 -06:00
John Alanbrook
ddf3fc1c77 add object literal test 2026-02-10 17:28:59 -06:00
John Alanbrook
f1a5072ff2 fix increment operators on objects 2026-02-10 17:17:36 -06:00
John Alanbrook
f44fb502be string literal error 2026-02-10 17:02:22 -06:00
John Alanbrook
d75ce916d7 compile optimization 2026-02-10 16:37:11 -06:00
John Alanbrook
fe5dc6ecc9 fix fd.c bugs 2026-02-10 14:21:49 -06:00
John Alanbrook
54673e4a04 better disrupt logging; actor exit on crash 2026-02-10 12:38:06 -06:00
John Alanbrook
0d8b5cfb04 bootstrap loads engine 2026-02-10 12:13:18 -06:00
John Alanbrook
3d71f4a363 Merge branch 'mach' into pitweb 2026-02-10 11:15:44 -06:00
John Alanbrook
4deb0e2577 new syntax for internals 2026-02-10 11:03:01 -06:00
John Alanbrook
67b96e1627 add test for multiple declaration 2026-02-10 10:39:23 -06:00
John Alanbrook
4e5f1d8faa fix labeled loops, do-while, shorthand property syntax, and added more tests 2026-02-10 10:32:54 -06:00
John Alanbrook
bd577712d9 fix function shorthand default params 2026-02-10 10:13:46 -06:00
John Alanbrook
6df3b741cf add runtime warnings for stale files 2026-02-10 10:05:27 -06:00
John Alanbrook
178837b88d bootstrap 2026-02-10 09:53:41 -06:00
John Alanbrook
120ce9d30c Merge branch 'mcode2' into pitweb 2026-02-10 09:23:30 -06:00
John Alanbrook
58f185b379 fix merge 2026-02-10 09:21:33 -06:00
John Alanbrook
f7b5252044 core flag 2026-02-10 09:21:21 -06:00
John Alanbrook
ded5f7d74b cell shop env var 2026-02-10 09:13:10 -06:00
John Alanbrook
fe6033d6cb deploy website script 2026-02-10 08:12:51 -06:00
John Alanbrook
60e61eef76 scheduler starts 2026-02-10 08:12:42 -06:00
John Alanbrook
ad863fb89b postfix/prefix operators handled correctly 2026-02-10 08:12:27 -06:00
John Alanbrook
96f8157039 Merge branch 'mach' into mcode2 2026-02-10 07:38:35 -06:00
John Alanbrook
c4ff0bc109 intrinsics rewritten without ++, --, etc 2026-02-10 07:19:45 -06:00
John Alanbrook
877250b1d8 decomposed mcode 2026-02-10 07:12:27 -06:00
John Alanbrook
747227de40 better parse errors 2026-02-10 06:51:26 -06:00
John Alanbrook
3f7e34cd7a more useful parse errors 2026-02-10 06:08:15 -06:00
John Alanbrook
cef5c50169 add is_letter intrinsic 2026-02-10 06:00:47 -06:00
John Alanbrook
0428424ec7 Merge branch 'mach' into mcode2 2026-02-10 05:53:51 -06:00
John Alanbrook
78e64c5067 optimize parse 2026-02-10 05:53:49 -06:00
John Alanbrook
ff11c49c39 optimize tokenize 2026-02-10 05:52:19 -06:00
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
John Alanbrook
a8a271e014 Merge branch 'syntax' into ast 2026-02-05 20:39:56 -06:00
John Alanbrook
91761c03e6 push/pop syntax 2026-02-05 20:39:53 -06:00
John Alanbrook
5a479cc765 function literal in record literal 2026-02-05 20:32:57 -06:00
John Alanbrook
97a003e025 errors 2026-02-05 20:12:06 -06:00
John Alanbrook
20f14abd17 string templates 2026-02-05 19:34:06 -06:00
John Alanbrook
19ba184fec default params for functions 2026-02-05 18:44:40 -06:00
John Alanbrook
7909b11f6b better errors 2026-02-05 18:35:48 -06:00
John Alanbrook
27229c675c add parser and tokenizer errors 2026-02-05 18:14:49 -06:00
John Alanbrook
64d234ee35 Merge branch 'syntax' into ast 2026-02-05 17:45:15 -06:00
John Alanbrook
e861d73eec mkarecord 2026-02-05 17:45:13 -06:00
John Alanbrook
a24331aae5 tokenize 2026-02-05 11:21:34 -06:00
John Alanbrook
c1cb922b64 more comprehensive ast 2026-02-05 10:59:56 -06:00
John Alanbrook
aacb0b48bf more vm tests 2026-02-05 10:44:53 -06:00
John Alanbrook
b38aec95b6 Merge branch 'syntax' into ast 2026-02-05 10:29:29 -06:00
John Alanbrook
b29d3c2fe0 add vm tests 2026-02-05 10:29:09 -06:00
John Alanbrook
1cc3005b68 better jump labels 2026-02-05 10:28:13 -06:00
John Alanbrook
b86cd042fc vm unit tests 2026-02-05 10:21:16 -06:00
John Alanbrook
8b7af0c22a vm bytecode output 2026-02-05 10:14:14 -06:00
John Alanbrook
f71f6a296b register vm 2026-02-05 06:55:45 -06:00
John Alanbrook
9bd764b11b add go 2026-02-05 03:10:06 -06:00
John Alanbrook
058cdfd2e4 groundwork for vm 2026-02-05 02:59:16 -06:00
John Alanbrook
1ef837c6ff rm bound function stuff 2026-02-05 02:36:14 -06:00
John Alanbrook
cd21de3d70 rm realm concept on function 2026-02-05 02:33:50 -06:00
John Alanbrook
a98faa4dbb debugging 2026-02-05 02:27:26 -06:00
John Alanbrook
08559234c4 fix closures 2026-02-05 02:07:18 -06:00
John Alanbrook
c3dc27eac6 machine code 2026-02-04 23:45:51 -06:00
John Alanbrook
7170a9c7eb ast 2026-02-04 22:20:57 -06:00
John Alanbrook
a08ee50f84 serializable bytecode 2026-02-04 20:57:44 -06:00
John Alanbrook
ed7dd91c3f rm global 2026-02-04 18:57:45 -06:00
John Alanbrook
3abe20fee0 merge 2026-02-04 18:38:46 -06:00
John Alanbrook
a92a96118e remove eval parser; consolidate addintrinsic 2026-02-04 17:15:03 -06:00
John Alanbrook
4e407fe301 migrate nota, wota into quickjs.c 2026-02-04 17:03:48 -06:00
John Alanbrook
ab74cdc173 merge warningfix 2026-02-04 16:17:52 -06:00
John Alanbrook
2c9d039271 massive cleanup 2026-02-04 14:26:17 -06:00
John Alanbrook
80d314c58f Merge templatefix branch
Use PPretext for parser string building to avoid GC issues during parsing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:21:25 -06:00
John Alanbrook
611fba2b6f fix regexp parsing 2026-02-04 14:19:39 -06:00
John Alanbrook
f5fad52d47 Rewrite template literals with OP_format_template
Replace complex template literal handling with a simple format-based
approach. Template literals like `hello ${x}` now compile to:
  <push x>
  OP_format_template expr_count=1, cpool_idx=N
where cpool[N] = "hello {0}"

The opcode handler parses the format string, substitutes {N} placeholders
with stringified stack values, and produces the result string.

Key implementation details:
- Uses PPretext (parser pretext) with pjs_malloc to avoid GC issues
- Re-reads b->cpool[cpool_idx] after any GC-triggering operation
- Opcode layout is u16 expr_count followed by u32 cpool_idx - the u16
  must come first because compute_stack_size reads the pop count from
  position 1 for npop_u16 format opcodes

Removed:
- OP_template_concat opcode and handler
- Tagged template literal support (users can use format() directly)
- FuncCallType enum (FUNC_CALL_TEMPLATE case no longer needed)
- Complex template object creation logic in js_parse_template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:18:02 -06:00
John Alanbrook
2fc7d333ad new environment tact for engine 2026-02-04 14:12:57 -06:00
John Alanbrook
d4635f2a75 remove unused vars, fix warnings 2026-02-04 13:49:43 -06:00
John Alanbrook
19576533d9 no more global obj; eval w/ env 2026-02-03 23:07:30 -06:00
John Alanbrook
c08249b6f1 clean up var ref 2026-02-03 22:01:42 -06:00
John Alanbrook
dc348d023f optimize lref 2026-02-03 21:37:17 -06:00
John Alanbrook
fd5e4d155e varref cleanup 2026-02-03 20:09:35 -06:00
John Alanbrook
e734353722 Begin removal of varref 2026-02-03 19:13:17 -06:00
John Alanbrook
94f1645be1 new closure model 2026-02-03 19:04:38 -06:00
John Alanbrook
41e3a6d91a closures work 2026-02-03 18:42:14 -06:00
John Alanbrook
04c569eab1 fix parser 2026-02-03 18:24:06 -06:00
John Alanbrook
dc3c474b3a link 2026-02-03 18:01:31 -06:00
John Alanbrook
f1117bbd41 get_global 2026-02-03 17:35:24 -06:00
John Alanbrook
43faad95e0 new opcodes 2026-02-03 17:21:41 -06:00
John Alanbrook
acc9878b36 bytecode dump 2026-02-03 17:08:21 -06:00
John Alanbrook
03c45ee8b0 lexer 2026-02-03 16:59:20 -06:00
John Alanbrook
a171a0d2af -e flag run script 2026-02-03 16:06:18 -06:00
John Alanbrook
bb8d3930b3 fix many internals wrt gc 2026-02-03 06:55:26 -06:00
John Alanbrook
522ae6128a add FORCE_GC_AT_MALLOC logic 2026-02-03 03:00:46 -06:00
John Alanbrook
16c26e4bf2 fix object fwd growth 2026-02-03 02:53:06 -06:00
John Alanbrook
a9804785e0 fix gc 2026-02-03 02:44:52 -06:00
John Alanbrook
11ae703693 suite.c roots 2026-02-03 02:40:40 -06:00
John Alanbrook
f203278c3e more gc correctness 2026-02-03 02:31:14 -06:00
John Alanbrook
a80557283a fix poison heap 2026-02-03 02:06:29 -06:00
John Alanbrook
3e40885e07 extensive debugging; fixed forwarding error 2026-02-03 01:50:30 -06:00
John Alanbrook
e4b7de46f6 add function aware to gc 2026-02-03 01:20:24 -06:00
John Alanbrook
e680439a9b gc asan poison 2026-02-03 01:03:23 -06:00
John Alanbrook
ae11504e00 copy gc 2026-02-03 00:34:07 -06:00
John Alanbrook
893deaec23 suite.c all works 2026-02-02 23:39:12 -06:00
John Alanbrook
69b032d3dc rm gc 2026-02-02 23:02:50 -06:00
John Alanbrook
256a00c501 fix mem errors 2026-02-02 22:54:38 -06:00
John Alanbrook
8e166b8f98 gc aware 2026-02-02 22:46:07 -06:00
John Alanbrook
ddbdd00496 update text and object to be gc aware 2026-02-02 22:37:42 -06:00
John Alanbrook
bdf0461e1f extensive C testing 2026-02-02 21:26:46 -06:00
John Alanbrook
0b86af1d4c array gc 2026-02-02 20:33:11 -06:00
John Alanbrook
beac9608ea chase 2026-02-02 18:54:15 -06:00
John Alanbrook
a04bebd0d7 fix str 2026-02-02 10:38:48 -06:00
John Alanbrook
ce74f726dd compiles + runs 2026-02-02 10:24:02 -06:00
John Alanbrook
4d1ab60852 rm notion of object values 2026-02-02 09:54:36 -06:00
John Alanbrook
22ab6c8098 rm gc fns 2026-02-02 09:22:51 -06:00
John Alanbrook
9a9775690f rm function proto 2026-02-02 08:35:14 -06:00
John Alanbrook
be71ae3bba remove **Free type functions 2026-02-02 08:32:11 -06:00
John Alanbrook
f2a76cbb55 fix number rep 2026-02-02 07:56:53 -06:00
John Alanbrook
2d834c37b3 simplify eq 2026-02-02 06:56:46 -06:00
John Alanbrook
ba1b92aa78 rm freevalue and dupvalue 2026-02-02 06:27:08 -06:00
John Alanbrook
c356fe462d compiles 2026-02-02 06:15:10 -06:00
John Alanbrook
b23b918f97 compiles 2026-02-01 23:03:01 -06:00
John Alanbrook
e720152bcd gc plan 2026-02-01 20:58:42 -06:00
John Alanbrook
f093e6f5a3 reformat 2026-02-01 19:36:20 -06:00
John Alanbrook
4fc904de63 rm 2026-02-01 19:33:19 -06:00
John Alanbrook
e53f55fb23 pretext 2026-02-01 18:59:52 -06:00
John Alanbrook
6150406905 pretext 2026-02-01 18:34:35 -06:00
John Alanbrook
a189440769 stringbuffer 2026-02-01 17:58:11 -06:00
John Alanbrook
6c3c492446 rm jsstring and string_buffer 2026-02-01 12:34:38 -06:00
John Alanbrook
bb83327a52 transformation 2026-02-01 07:45:44 -06:00
John Alanbrook
c74bee89a7 plan 2026-01-31 14:59:31 -06:00
John Alanbrook
271a3d6724 before gc rewrite 2026-01-31 14:43:21 -06:00
John Alanbrook
c5ccc66e51 simpler enumeration of property names 2026-01-31 12:13:35 -06:00
John Alanbrook
3a0ea31896 rm dump profile 2026-01-31 12:01:33 -06:00
John Alanbrook
67e82fd12c merge objects and recs 2026-01-31 11:40:16 -06:00
John Alanbrook
3c59087c0c merge jsobject to jsrecord 2026-01-31 11:36:07 -06:00
John Alanbrook
b79e07f57b more atom removal 2026-01-31 11:14:02 -06:00
John Alanbrook
fdcb374403 fix formatting 2026-01-31 10:33:29 -06:00
John Alanbrook
03feb370fd rm atoms 2026-01-31 06:51:19 -06:00
John Alanbrook
6712755940 edit lexer atoms 2026-01-30 21:33:10 -06:00
John Alanbrook
b3f3bc8a5f rm atoms 2026-01-30 20:16:08 -06:00
John Alanbrook
a49b94e0a1 phase3 2026-01-30 17:02:50 -06:00
John Alanbrook
24ecff3f1c rm atom' 2026-01-30 09:58:02 -06:00
John Alanbrook
3ccaf68a5b begin rm atoms 2026-01-30 02:12:05 -06:00
John Alanbrook
64933260d4 docs 2026-01-29 22:07:48 -06:00
John Alanbrook
561ab9d917 mem 2026-01-29 20:33:38 -06:00
John Alanbrook
bcd6e641a5 initial try 2026-01-27 19:17:44 -06:00
John Alanbrook
2857581271 cleanup 2026-01-27 10:42:49 -06:00
John Alanbrook
378ad6dc98 tracy 2026-01-26 20:13:44 -06:00
John Alanbrook
06f7791159 use internal calls for internals 2026-01-26 11:44:21 -06:00
John Alanbrook
086508bacd quicken 2026-01-25 20:29:49 -06:00
John Alanbrook
8325253f1a fixed function arity 2026-01-25 13:51:34 -06:00
John Alanbrook
802c94085b rm flags in c fn calls 2026-01-25 09:45:13 -06:00
John Alanbrook
f9170b33e5 rm old function as object code 2026-01-24 09:55:24 -06:00
John Alanbrook
20f10ab887 initial attempt change functions to intrinsic type 2026-01-24 08:57:20 -06:00
John Alanbrook
d8b13548d2 objects directly used as properties now instead of shadow symbol table 2026-01-23 23:33:52 -06:00
John Alanbrook
0d93741c31 fix array free 2026-01-23 22:54:49 -06:00
John Alanbrook
c6440ff98c tag array coverage 2026-01-23 22:51:46 -06:00
John Alanbrook
a01b48dabc add rc trace" 2026-01-23 17:22:07 -06:00
John Alanbrook
beea76949c rm bigint 2026-01-23 15:05:57 -06:00
John Alanbrook
36833db2c9 ArrayLength returns correct value 2026-01-23 11:40:23 -06:00
John Alanbrook
b7615bb801 fix error messaging 2026-01-23 11:28:06 -06:00
John Alanbrook
e6838338fc fix failing tests 2026-01-23 11:03:32 -06:00
John Alanbrook
4cf0ce00de fix tests 2026-01-23 05:58:47 -06:00
John Alanbrook
0714017547 atoms 2026-01-23 05:44:02 -06:00
John Alanbrook
b60e79ccad rm integer atoms 2026-01-22 19:12:12 -06:00
John Alanbrook
420c2b859a rm getset 2026-01-22 09:55:02 -06:00
John Alanbrook
6c1f53ec5f rm autoinit 2026-01-22 09:16:01 -06:00
John Alanbrook
45d82438ca intrinsic arrays 2026-01-22 08:30:41 -06:00
John Alanbrook
addb38da65 intrinsic arrays 2026-01-21 19:38:38 -06:00
John Alanbrook
aa847ddf6e no more string class type 2026-01-21 17:36:19 -06:00
John Alanbrook
4da63db16e rm constructor 2026-01-21 13:38:38 -06:00
John Alanbrook
bfdd920178 rm some more fns 2026-01-21 10:41:29 -06:00
John Alanbrook
26fce3a5a8 rm array proto funcs 2026-01-21 00:52:18 -06:00
John Alanbrook
ef49606098 rm numeric 2026-01-20 20:09:06 -06:00
John Alanbrook
854d94e5c3 rm constructors 2026-01-20 16:46:30 -06:00
John Alanbrook
dc02d6899d prune eq 2026-01-20 16:38:24 -06:00
John Alanbrook
b28ef39562 rm string coercion 2026-01-20 14:13:16 -06:00
John Alanbrook
2841e91f40 add property get 2026-01-20 12:04:16 -06:00
John Alanbrook
8d601dfce3 rm constructors 2026-01-20 12:01:40 -06:00
John Alanbrook
2b60e3a242 rm dependence on tostring 2026-01-20 10:08:35 -06:00
John Alanbrook
823183c510 length 2026-01-20 08:36:28 -06:00
John Alanbrook
c051a99e75 restore toml cache regression 2026-01-20 05:33:44 -06:00
John Alanbrook
b3c0837d49 rm new 2026-01-20 05:20:45 -06:00
John Alanbrook
ff18682485 rm for ... in 2026-01-19 18:56:54 -06:00
John Alanbrook
9b3891c126 rm spreads and iterators 2026-01-19 16:01:39 -06:00
John Alanbrook
38a3697e28 meme 2026-01-19 14:39:55 -06:00
John Alanbrook
cbf99295da rm of 2026-01-19 01:06:45 -06:00
John Alanbrook
5271688dd4 length 2026-01-18 11:22:17 -06:00
John Alanbrook
98cb2c3239 switch to length fn 2026-01-18 10:35:05 -06:00
John Alanbrook
e695810e64 fix build 2026-01-18 09:59:54 -06:00
John Alanbrook
bbd2d298ba arrfor" 2026-01-18 08:54:48 -06:00
John Alanbrook
a7a323a74e rm index and indexof 2026-01-17 16:21:02 -06:00
John Alanbrook
97ece8e5cb text extract 2026-01-17 15:48:43 -06:00
John Alanbrook
45ee4a337c add not 2026-01-17 11:12:54 -06:00
John Alanbrook
ce7d83ec91 rm fns 2026-01-16 20:55:57 -06:00
John Alanbrook
b46406f755 rm js fns 2026-01-16 18:15:29 -06:00
John Alanbrook
ac91495679 rm top level fns 2026-01-15 18:16:34 -06:00
John Alanbrook
5018901acb cache config in package.cm 2026-01-15 15:54:58 -06:00
John Alanbrook
78a46b4a72 rm map/set 2026-01-15 14:39:37 -06:00
John Alanbrook
66a9ca27e2 rm new 2026-01-13 22:16:28 -06:00
John Alanbrook
ac4b47f075 rm function proto funcs 2026-01-11 23:34:35 -06:00
John Alanbrook
6b9f75247e log is a proxy now 2026-01-11 23:22:15 -06:00
John Alanbrook
b039b0c4ba fn proxy 2026-01-11 23:13:58 -06:00
John Alanbrook
ffe7b61ae2 remove property access for bytecode functions 2026-01-11 13:22:36 -06:00
John Alanbrook
17b9aaaf51 fix issue where linked packages would not load dylibs correctly 2026-01-11 11:49:48 -06:00
John Alanbrook
5fd29366a6 skip relink when content hasn't changed 2026-01-09 11:51:05 -06:00
John Alanbrook
f16586eaa2 fix fetch 2026-01-09 11:49:22 -06:00
John Alanbrook
86a70bce3a fd check blob stone 2026-01-09 08:23:40 -06:00
John Alanbrook
e04b15973a link 2026-01-09 07:36:48 -06:00
John Alanbrook
d044bde4f9 clean up cmd line 2026-01-09 05:37:37 -06:00
John Alanbrook
8403883b9d blob w32 2026-01-08 21:00:56 -06:00
John Alanbrook
69245f82db remove instanceof operator 2026-01-08 12:26:11 -06:00
John Alanbrook
8203f6d1c3 fixes 2026-01-08 12:07:32 -06:00
John Alanbrook
ef94b55058 update 2026-01-08 09:10:47 -06:00
John Alanbrook
3a3e77eccd remove number and boolean prototypes 2026-01-07 07:06:02 -06:00
John Alanbrook
438c90acb5 ignore 2026-01-06 20:31:49 -06:00
John Alanbrook
dd309b1a37 remove isa 2026-01-06 11:17:07 -06:00
John Alanbrook
63cf76dcf9 remove typeof' 2026-01-06 11:08:27 -06:00
John Alanbrook
df07069c38 return null in bad retrieval, throw on bad insert 2026-01-06 09:16:15 -06:00
John Alanbrook
eba0727247 object cannot use anything but string and obj as keys 2026-01-06 09:11:47 -06:00
John Alanbrook
b0f0a5f63f remove some unneeded functions 2026-01-03 08:25:32 -06:00
John Alanbrook
d12d77c22c objects now work as private non enumerable keys 2026-01-02 16:28:02 -06:00
John Alanbrook
d6468e7fd2 remove old math code 2026-01-02 15:41:29 -06:00
238 changed files with 54872 additions and 49279 deletions

View File

@@ -1,6 +0,0 @@
[modules]
[modules.extramath]
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
url = "https://gitea.pockle.world/john/extramath@master"
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"

View File

@@ -1,9 +1,20 @@
BasedOnStyle: GNU
Language: C
IndentWidth: 2
TabWidth: 2
UseTab: Never
ContinuationIndentWidth: 2 # Indents continuation lines by 2 spaces
ContinuationIndentWidth: 2
AllowShortFunctionsOnASingleLine: true
AllowShortBlocksOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
BreakBeforeBraces: Attach
ColumnLimit: 0
BreakFunctionDefinitionParameters: false
BinPackParameters: false
BinPackArguments: false
# --- Fix the "static T\nname(...)" style ---
AlwaysBreakAfterDefinitionReturnType: None
BreakAfterReturnType: None

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.mach binary merge=ours

3
.gitignore vendored
View File

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

123
CLAUDE.md Normal file
View File

@@ -0,0 +1,123 @@
# ƿit (pit) Language Project
## Building
Recompile after changes: `make`
Bootstrap from scratch (first time): `make bootstrap`
Run `cell --help` to see all CLI flags.
## Code Style
All code uses 2 spaces for indentation. K&R style for C and Javascript.
## ƿit Script Quick Reference
ƿit script files: `.ce` (actors) and `.cm` (modules). The syntax is similar to JavaScript with important differences listed below.
### 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

@@ -5,16 +5,24 @@
# or manually build with meson once.
#
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
#
# See BUILDING.md for details on the bootstrap process and .mach files.
CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# .cm sources that compile to .mach bytecode
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
internal/bootstrap.cm internal/engine.cm
maker: install
makecell:
cell pack core -o cell
cp cell /opt/homebrew/bin/
# Install core: symlink this directory to ~/.cell/core
install: bootstrap $(CELL_SHOP)
install: bootstrap .mach.stamp $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)"
rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE_PACKAGE)
@@ -38,6 +46,16 @@ libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Regenerate .mach bytecode when any .cm source changes
.mach.stamp: $(MACH_SOURCES)
./cell --core . regen.cm
@touch $@
# Force-regenerate all .mach bytecode files
regen:
./cell --core . regen.cm
@touch .mach.stamp
# Create the cell shop directories
$(CELL_SHOP):
mkdir -p $(CELL_SHOP)
@@ -55,7 +73,7 @@ static:
# Bootstrap: build cell from scratch using meson (only needed once)
# Also installs core scripts to ~/.cell/core
bootstrap:
bootstrap:
meson setup build_bootstrap -Dbuildtype=debugoptimized
meson compile -C build_bootstrap
cp build_bootstrap/cell .
@@ -66,7 +84,7 @@ bootstrap:
# Clean build artifacts
clean:
rm -rf $(CELL_SHOP)/build build_bootstrap
rm -f cell cell_main libcell_runtime.dylib
rm -f cell cell_main libcell_runtime.dylib .mach.stamp
# Ensure dynamic build directory exists
$(CELL_SHOP)/build/dynamic: $(CELL_SHOP)
@@ -77,4 +95,4 @@ meson:
meson setup build_dbg -Dbuildtype=debugoptimized
meson install -C build_dbg
.PHONY: cell static bootstrap clean meson install
.PHONY: cell static bootstrap clean meson install regen

99
add.ce
View File

@@ -1,28 +1,103 @@
// cell add <locator> [alias] - Add and install a package with its dependencies
// cell add <locator> [alias] - Add a dependency to the current package
//
// Usage:
// cell add <locator> Add a dependency using default alias
// cell add <locator> <alias> Add a dependency with custom alias
//
// This adds the dependency to cell.toml and installs it to the shop.
var shop = use('internal/shop')
var pkg = use('package')
var build = use('build')
var fd = use('fd')
if (args.length < 1) {
var locator = null
var alias = null
array(args, function(arg) {
if (arg == '--help' || arg == '-h') {
log.console("Usage: cell add <locator> [alias]")
log.console("")
log.console("Add a dependency to the current package.")
log.console("")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add ../local-package")
$stop()
} else if (!starts_with(arg, '-')) {
if (!locator) {
locator = arg
} else if (!alias) {
alias = arg
}
}
})
if (!locator) {
log.console("Usage: cell add <locator> [alias]")
log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/prosperon@main")
log.console(" cell add github.com/user/repo@v1.0.0 myalias")
$stop()
return
}
var locator = args[0]
// Resolve relative paths to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
var alias = args.length > 1 ? args[1] : null
shop.get(locator, alias)
// Generate default alias from locator
if (!alias) {
// Use the last component of the locator as alias
var parts = array(locator, '/')
alias = parts[length(parts) - 1]
// Remove any version suffix
if (search(alias, '@') != null) {
alias = array(alias, '@')[0]
}
}
$stop()
// Check we're in a package directory
var cwd = fd.realpath('.')
if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)")
$stop()
}
log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml
try {
pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml")
} catch (e) {
log.error("Failed to update cell.toml: " + e)
$stop()
}
// Install to shop
try {
shop.get(locator)
shop.extract(locator)
// Build scripts
shop.build_package_scripts(locator)
// Build C code if any
try {
var target = build.detect_host_target()
build.build_dynamic(locator, target, 'release')
} catch (e) {
// Not all packages have C code
}
log.console(" Installed to shop")
} catch (e) {
log.error("Failed to install: " + e)
$stop()
}
log.console("Added " + alias + " (" + locator + ")")
$stop()

View File

@@ -8,14 +8,14 @@ static JSClassID js_writer_class_id;
static void js_reader_finalizer(JSRuntime *rt, JSValue val) {
mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id);
mz_zip_reader_end(zip);
js_free_rt(rt,zip);
js_free_rt(zip);
}
static void js_writer_finalizer(JSRuntime *rt, JSValue val) {
mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id);
mz_zip_writer_finalize_archive(zip);
mz_zip_writer_end(zip);
js_free_rt(rt,zip);
js_free_rt(zip);
}
static JSClassDef js_reader_class = {
@@ -101,7 +101,7 @@ static JSValue js_miniz_compress(JSContext *js, JSValue this_val,
size_t in_len = 0;
const void *in_ptr = NULL;
if (JS_IsString(argv[0])) {
if (JS_IsText(argv[0])) {
/* String → UTF-8 bytes without the terminating NUL */
cstring = JS_ToCStringLen(js, &in_len, argv[0]);
if (!cstring)
@@ -319,7 +319,7 @@ JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv)
JS_FreeValue(js, arr);
return filename;
}
JS_SetPropertyUint32(js, arr, arr_index++, filename);
JS_SetPropertyNumber(js, arr, arr_index++, filename);
}
return arr;
@@ -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);

261
bench.ce
View File

@@ -24,63 +24,45 @@ def MAX_BATCH_SIZE = 100000000 // 100M iterations max per batch
// Statistical functions
function median(arr) {
if (arr.length == 0) return 0
var sorted = arr.slice().sort(function(a, b) { return a - b })
var mid = number.floor(arr.length / 2)
if (arr.length % 2 == 0) {
if (length(arr) == 0) return 0
var sorted = sort(arr)
var mid = floor(length(arr) / 2)
if (length(arr) % 2 == 0) {
return (sorted[mid - 1] + sorted[mid]) / 2
}
return sorted[mid]
}
function mean(arr) {
if (arr.length == 0) return 0
if (length(arr) == 0) return 0
var sum = 0
for (var i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum / arr.length
arrfor(arr, function(val) {
sum += val
})
return sum / length(arr)
}
function stddev(arr, mean_val) {
if (arr.length < 2) return 0
if (length(arr) < 2) return 0
var sum_sq_diff = 0
for (var i = 0; i < arr.length; i++) {
var diff = arr[i] - mean_val
arrfor(arr, function(val) {
var diff = val - mean_val
sum_sq_diff += diff * diff
}
return math.sqrt(sum_sq_diff / (arr.length - 1))
})
return math.sqrt(sum_sq_diff / (length(arr) - 1))
}
function percentile(arr, p) {
if (arr.length == 0) return 0
var sorted = arr.slice().sort(function(a, b) { return a - b })
var idx = number.floor(arr.length * p / 100)
if (idx >= arr.length) idx = arr.length - 1
if (length(arr) == 0) return 0
var sorted = sort(arr)
var idx = floor(arr) * p / 100
if (idx >= length(arr)) idx = length(arr) - 1
return sorted[idx]
}
function min_val(arr) {
if (arr.length == 0) return 0
var m = arr[0]
for (var i = 1; i < arr.length; i++) {
if (arr[i] < m) m = arr[i]
}
return m
}
function max_val(arr) {
if (arr.length == 0) return 0
var m = arr[0]
for (var i = 1; i < arr.length; i++) {
if (arr[i] > m) m = arr[i]
}
return m
}
// Parse arguments similar to test.ce
function parse_args() {
if (args.length == 0) {
if (length(args) == 0) {
if (!testlib.is_valid_package('.')) {
log.console('No cell.toml found in current directory')
return false
@@ -99,7 +81,7 @@ function parse_args() {
}
if (args[0] == 'package') {
if (args.length < 2) {
if (length(args) < 2) {
log.console('Usage: cell bench package <name> [bench]')
log.console(' cell bench package all')
return false
@@ -115,7 +97,7 @@ function parse_args() {
var lock = shop.load_lock()
if (lock[name]) {
target_pkg = name
} else if (name.startsWith('/') && testlib.is_valid_package(name)) {
} else if (starts_with(name, '/') && testlib.is_valid_package(name)) {
target_pkg = name
} else {
if (testlib.is_valid_package('.')) {
@@ -132,7 +114,7 @@ function parse_args() {
}
}
if (args.length >= 3) {
if (length(args) >= 3) {
target_bench = args[2]
}
@@ -144,7 +126,7 @@ function parse_args() {
var bench_path = args[0]
// Normalize path - add benches/ prefix if not present
if (!bench_path.startsWith('benches/') && !bench_path.startsWith('/')) {
if (!starts_with(bench_path, 'benches/') && !starts_with(bench_path, '/')) {
if (!fd.is_file(bench_path + '.cm') && !fd.is_file(bench_path)) {
if (fd.is_file('benches/' + bench_path + '.cm') || fd.is_file('benches/' + bench_path)) {
bench_path = 'benches/' + bench_path
@@ -177,19 +159,18 @@ function collect_benches(package_name, specific_bench) {
var files = pkg.list_files(package_name)
var bench_files = []
for (var i = 0; i < files.length; i++) {
var f = files[i]
if (f.startsWith("benches/") && f.endsWith(".cm")) {
arrfor(files, function(f) {
if (starts_with(f, "benches/") && ends_with(f, ".cm")) {
if (specific_bench) {
var bench_name = f.substring(0, f.length - 3)
var bench_name = text(f, 0, -3)
var match_name = specific_bench
if (!match_name.startsWith('benches/')) match_name = 'benches/' + match_name
var match_base = match_name.endsWith('.cm') ? match_name.substring(0, match_name.length - 3) : match_name
if (bench_name != match_base) continue
if (!starts_with(match_name, 'benches/')) match_name = 'benches/' + match_name
var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (bench_name != match_base) return
}
bench_files.push(f)
push(bench_files, f)
}
}
})
return bench_files
}
@@ -203,7 +184,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
// Find a batch size that takes at least MIN_SAMPLE_NS
while (n < MAX_BATCH_SIZE) {
// Ensure n is a valid number before calling
if (typeof n != 'number' || n < 1) {
if (!is_number(n) || n < 1) {
n = 1
break
}
@@ -217,7 +198,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
// Double the batch size
var new_n = n * 2
// Check if multiplication produced a valid number
if (typeof new_n != 'number' || new_n > MAX_BATCH_SIZE) {
if (!is_number(new_n) || new_n > MAX_BATCH_SIZE) {
n = MAX_BATCH_SIZE
break
}
@@ -225,12 +206,12 @@ function calibrate_batch_size(bench_fn, is_batch) {
}
// Adjust to target sample duration
if (dt > 0 && dt < TARGET_SAMPLE_NS && typeof n == 'number' && typeof dt == 'number') {
if (dt > 0 && dt < TARGET_SAMPLE_NS && is_number(n) && is_number(dt)) {
var calc = n * TARGET_SAMPLE_NS / dt
if (typeof calc == 'number' && calc > 0) {
var target_n = number.floor(calc)
if (is_number(calc) && calc > 0) {
var target_n = floor(calc)
// Check if floor returned a valid number
if (typeof target_n == 'number' && target_n > 0) {
if (is_number(target_n) && target_n > 0) {
if (target_n > MAX_BATCH_SIZE) target_n = MAX_BATCH_SIZE
if (target_n < MIN_BATCH_SIZE) target_n = MIN_BATCH_SIZE
n = target_n
@@ -239,7 +220,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
}
// Safety check - ensure we always return a valid batch size
if (typeof n != 'number' || n < 1) {
if (!is_number(n) || n < 1) {
n = 1
}
@@ -254,7 +235,7 @@ function run_single_bench(bench_fn, bench_name) {
// 1. Object with { setup, run, teardown } - structured format
// 2. Function that accepts (n) - batch format
// 3. Function that accepts () - legacy format
var is_structured = typeof bench_fn == 'object' && bench_fn.run
var is_structured = is_object(bench_fn) && bench_fn.run
var is_batch = false
var batch_size = 1
var setup_fn = null
@@ -285,7 +266,7 @@ function run_single_bench(bench_fn, bench_name) {
batch_size = calibrate_batch_size(calibrate_fn, is_batch)
// Safety check for structured benchmarks
if (typeof batch_size != 'number' || batch_size < 1) {
if (!is_number(batch_size) || batch_size < 1) {
batch_size = 1
}
} else {
@@ -307,8 +288,9 @@ function run_single_bench(bench_fn, bench_name) {
// Warmup phase
for (var i = 0; i < WARMUP_BATCHES; i++) {
// Ensure batch_size is valid before warmup
if (typeof batch_size != 'number' || batch_size < 1) {
log.console(`WARNING: batch_size became ${typeof batch_size} = ${batch_size}, resetting to 1`)
if (!is_number(batch_size) || batch_size < 1) {
var type_str = is_null(batch_size) ? 'null' : is_number(batch_size) ? 'number' : is_text(batch_size) ? 'text' : is_object(batch_size) ? 'object' : is_array(batch_size) ? 'array' : is_function(batch_size) ? 'function' : is_logical(batch_size) ? 'logical' : 'unknown'
log.console(`WARNING: batch_size became ${type_str} = ${batch_size}, resetting to 1`)
batch_size = 1
}
@@ -332,7 +314,7 @@ function run_single_bench(bench_fn, bench_name) {
// Measurement phase - collect SAMPLES timing samples
for (var i = 0; i < SAMPLES; i++) {
// Double-check batch_size is valid (should never happen, but defensive)
if (typeof batch_size != 'number' || batch_size < 1) {
if (!is_number(batch_size) || batch_size < 1) {
batch_size = 1
}
@@ -348,7 +330,7 @@ function run_single_bench(bench_fn, bench_name) {
if (teardown_fn) teardown_fn(state)
var ns_per_op = is_batch ? duration / batch_size : duration
timings_per_op.push(ns_per_op)
push(timings_per_op, ns_per_op)
} else {
var start = os.now()
if (is_batch) {
@@ -359,15 +341,15 @@ function run_single_bench(bench_fn, bench_name) {
var duration = os.now() - start
var ns_per_op = is_batch ? duration / batch_size : duration
timings_per_op.push(ns_per_op)
push(timings_per_op, ns_per_op)
}
}
// Calculate statistics
var mean_ns = mean(timings_per_op)
var median_ns = median(timings_per_op)
var min_ns = min_val(timings_per_op)
var max_ns = max_val(timings_per_op)
var min_ns = reduce(timings_per_op, min)
var max_ns = reduce(timings_per_op, max)
var stddev_ns = stddev(timings_per_op, mean_ns)
var p95_ns = percentile(timings_per_op, 95)
var p99_ns = percentile(timings_per_op, 99)
@@ -375,20 +357,20 @@ function run_single_bench(bench_fn, bench_name) {
// Calculate ops/s from median
var ops_per_sec = 0
if (median_ns > 0) {
ops_per_sec = number.floor(1000000000 / median_ns)
ops_per_sec = floor(1000000000 / median_ns)
}
return {
name: bench_name,
batch_size: batch_size,
samples: SAMPLES,
mean_ns: number.round(mean_ns),
median_ns: number.round(median_ns),
min_ns: number.round(min_ns),
max_ns: number.round(max_ns),
stddev_ns: number.round(stddev_ns),
p95_ns: number.round(p95_ns),
p99_ns: number.round(p99_ns),
mean_ns: round(mean_ns),
median_ns: round(median_ns),
min_ns: round(min_ns),
max_ns: round(max_ns),
stddev_ns: round(stddev_ns),
p95_ns: round(p95_ns),
p99_ns: round(p99_ns),
ops_per_sec: ops_per_sec
}
}
@@ -396,17 +378,17 @@ function run_single_bench(bench_fn, bench_name) {
// Format nanoseconds for display
function format_ns(ns) {
if (ns < 1000) return `${ns}ns`
if (ns < 1000000) return `${number.round(ns / 1000 * 100) / 100}µs`
if (ns < 1000000000) return `${number.round(ns / 1000000 * 100) / 100}ms`
return `${number.round(ns / 1000000000 * 100) / 100}s`
if (ns < 1000000) return `${round(ns / 1000 * 100) / 100}µs`
if (ns < 1000000000) return `${round(ns / 1000000 * 100) / 100}ms`
return `${round(ns / 1000000000 * 100) / 100}s`
}
// Format ops/sec for display
function format_ops(ops) {
if (ops < 1000) return `${ops} ops/s`
if (ops < 1000000) return `${number.round(ops / 1000 * 100) / 100}K ops/s`
if (ops < 1000000000) return `${number.round(ops / 1000000 * 100) / 100}M ops/s`
return `${number.round(ops / 1000000000 * 100) / 100}G ops/s`
if (ops < 1000000) return `${round(ops / 1000 * 100) / 100}K ops/s`
if (ops < 1000000000) return `${round(ops / 1000000 * 100) / 100}M ops/s`
return `${round(ops / 1000000000 * 100) / 100}G ops/s`
}
// Run benchmarks for a package
@@ -419,14 +401,13 @@ function run_benchmarks(package_name, specific_bench) {
total: 0
}
if (bench_files.length == 0) return pkg_result
if (length(bench_files) == 0) return pkg_result
if (package_name) log.console(`Running benchmarks for ${package_name}`)
else log.console(`Running benchmarks for local package`)
for (var i = 0; i < bench_files.length; i++) {
var f = bench_files[i]
var mod_path = f.substring(0, f.length - 3)
arrfor(bench_files, function(f) {
var mod_path = text(f, 0, -3)
var file_result = {
name: f,
@@ -439,24 +420,22 @@ function run_benchmarks(package_name, specific_bench) {
bench_mod = shop.use(mod_path, use_pkg)
var benches = []
if (typeof bench_mod == 'function') {
benches.push({name: 'main', fn: bench_mod})
} else if (typeof bench_mod == 'object') {
for (var k in bench_mod) {
if (typeof bench_mod[k] == 'function') {
benches.push({name: k, fn: bench_mod[k]})
}
}
if (is_function(bench_mod)) {
push(benches, {name: 'main', fn: bench_mod})
} else if (is_object(bench_mod)) {
arrfor(array(bench_mod), function(k) {
if (is_function(bench_mod[k]))
push(benches, {name: k, fn: bench_mod[k]})
})
}
if (benches.length > 0) {
if (length(benches) > 0) {
log.console(` ${f}`)
for (var j = 0; j < benches.length; j++) {
var b = benches[j]
arrfor(benches, function(b) {
try {
var result = run_single_bench(b.fn, b.name)
result.package = pkg_result.package
file_result.benchmarks.push(result)
push(file_result.benchmarks, result)
pkg_result.total++
log.console(` ${result.name}`)
@@ -473,10 +452,10 @@ function run_benchmarks(package_name, specific_bench) {
name: b.name,
error: e.toString()
}
file_result.benchmarks.push(error_result)
push(file_result.benchmarks, error_result)
pkg_result.total++
}
}
})
}
} catch (e) {
log.console(` Error loading ${f}: ${e}`)
@@ -485,14 +464,14 @@ function run_benchmarks(package_name, specific_bench) {
name: "load_module",
error: `Error loading module: ${e}`
}
file_result.benchmarks.push(error_result)
push(file_result.benchmarks, error_result)
pkg_result.total++
}
if (file_result.benchmarks.length > 0) {
pkg_result.files.push(file_result)
if (length(file_result.benchmarks) > 0) {
push(pkg_result.files, file_result)
}
}
})
return pkg_result
}
@@ -502,29 +481,29 @@ var all_results = []
if (all_pkgs) {
if (testlib.is_valid_package('.')) {
all_results.push(run_benchmarks(null, null))
push(all_results, run_benchmarks(null, null))
}
var packages = shop.list_packages()
for (var i = 0; i < packages.length; i++) {
all_results.push(run_benchmarks(packages[i], null))
}
arrfor(packages, function(pkg) {
push(all_results, run_benchmarks(pkg, null))
})
} else {
all_results.push(run_benchmarks(target_pkg, target_bench))
push(all_results, run_benchmarks(target_pkg, target_bench))
}
// Calculate totals
var total_benches = 0
for (var i = 0; i < all_results.length; i++) {
total_benches += all_results[i].total
}
arrfor(all_results, function(result) {
total_benches += result.total
})
log.console(`----------------------------------------`)
log.console(`Benchmarks: ${total_benches} total`)
// Generate reports
function generate_reports() {
var timestamp = text(number.floor(time.number()))
var timestamp = text(floor(time.number()))
var report_dir = shop.get_reports_dir() + '/bench_' + timestamp
testlib.ensure_dir(report_dir)
@@ -534,34 +513,28 @@ Total benchmarks: ${total_benches}
=== SUMMARY ===
`
for (var i = 0; i < all_results.length; i++) {
var pkg_res = all_results[i]
if (pkg_res.total == 0) continue
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
txt_report += `Package: ${pkg_res.package}\n`
for (var j = 0; j < pkg_res.files.length; j++) {
var f = pkg_res.files[j]
arrfor(pkg_res.files, function(f) {
txt_report += ` ${f.name}\n`
for (var k = 0; k < f.benchmarks.length; k++) {
var b = f.benchmarks[k]
arrfor(f.benchmarks, function(b) {
if (b.error) {
txt_report += ` ERROR ${b.name}: ${b.error}\n`
} else {
txt_report += ` ${b.name}: ${format_ns(b.median_ns)}/op (${format_ops(b.ops_per_sec)})\n`
}
}
}
}
})
})
})
txt_report += `\n=== DETAILED RESULTS ===\n`
for (var i = 0; i < all_results.length; i++) {
var pkg_res = all_results[i]
if (pkg_res.total == 0) continue
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
for (var j = 0; j < pkg_res.files.length; j++) {
var f = pkg_res.files[j]
for (var k = 0; k < f.benchmarks.length; k++) {
var b = f.benchmarks[k]
if (b.error) continue
arrfor(pkg_res.files, function(f) {
arrfor(f.benchmarks, function(b) {
if (b.error) return
txt_report += `\n${pkg_res.package}::${b.name}\n`
txt_report += ` batch_size: ${b.batch_size} samples: ${b.samples}\n`
@@ -573,30 +546,28 @@ Total benchmarks: ${total_benches}
txt_report += ` p95: ${format_ns(b.p95_ns)}\n`
txt_report += ` p99: ${format_ns(b.p99_ns)}\n`
txt_report += ` ops/s: ${format_ops(b.ops_per_sec)}\n`
}
}
}
})
})
})
testlib.ensure_dir(report_dir)
fd.slurpwrite(`${report_dir}/bench.txt`, stone(new blob(txt_report)))
fd.slurpwrite(`${report_dir}/bench.txt`, stone(blob(txt_report)))
log.console(`Report written to ${report_dir}/bench.txt`)
// Generate JSON per package
for (var i = 0; i < all_results.length; i++) {
var pkg_res = all_results[i]
if (pkg_res.total == 0) continue
arrfor(all_results, function(pkg_res) {
if (pkg_res.total == 0) return
var pkg_benches = []
for (var j = 0; j < pkg_res.files.length; j++) {
var f = pkg_res.files[j]
for (var k = 0; k < f.benchmarks.length; k++) {
pkg_benches.push(f.benchmarks[k])
}
}
arrfor(pkg_res.files, function(f) {
arrfor(f.benchmarks, function(benchmark) {
push(pkg_benches, benchmark)
})
})
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
fd.slurpwrite(json_path, stone(new blob(json.encode(pkg_benches))))
}
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_benches))))
})
}
generate_reports()

View File

@@ -20,14 +20,14 @@ function make_shapes(n) {
for (var i = 0; i < n; i++) {
var o = { a: i }
o[`p${i}`] = i
out.push(o)
push(out, o)
}
return out
}
function make_packed_array(n) {
var a = []
for (var i = 0; i < n; i++) a.push(i)
for (var i = 0; i < n; i++) push(a, i)
return a
}
@@ -203,8 +203,8 @@ return {
var x = 0
for (var j = 0; j < n; j++) {
var a = []
for (var i = 0; i < 256; i++) a.push(i)
x = (x + a.length) | 0
for (var i = 0; i < 256; i++) push(a, i)
x = (x + length(a)) | 0
}
return blackhole(sink, x)
},
@@ -216,7 +216,7 @@ return {
for (var j = 0; j < n; j++) {
var s = ""
for (var i = 0; i < 16; i++) s = s + "x"
x = (x + s.length) | 0
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},

View File

@@ -1,5 +1,5 @@
function mainThread() {
var maxDepth = number.max(6, Number(arg[0] || 16));
var maxDepth = max(6, Number(arg[0] || 16));
var stretchDepth = maxDepth + 1;
var check = itemCheck(bottomUpTree(stretchDepth));
@@ -7,7 +7,7 @@ function mainThread() {
var longLivedTree = bottomUpTree(maxDepth);
for (let depth = 4; depth <= maxDepth; depth += 2) {
for (var depth = 4; depth <= maxDepth; depth += 2) {
var iterations = 1 << maxDepth - depth + 4;
work(iterations, depth);
}
@@ -16,8 +16,8 @@ function mainThread() {
}
function work(iterations, depth) {
let check = 0;
for (let i = 0; i < iterations; i++)
var check = 0;
for (var i = 0; i < iterations; i++)
check += itemCheck(bottomUpTree(depth));
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
}
@@ -34,8 +34,8 @@ function itemCheck(node) {
function bottomUpTree(depth) {
return depth > 0
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: new TreeNode(null, null);
? TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
: TreeNode(null, null);
}
mainThread()

View File

@@ -2,8 +2,8 @@ var blob = use('blob')
var math = use('math/radians')
function eratosthenes (n) {
var sieve = new blob(n, true)
var sqrtN = number.whole(math.sqrt(n));
var sieve = blob(n, true)
var sqrtN = whole(math.sqrt(n));
for (i = 2; i <= sqrtN; i++)
if (sieve.read_logical(i))
@@ -17,7 +17,7 @@ var sieve = eratosthenes(10000000);
stone(sieve)
var c = 0
for (var i = 0; i < sieve.length; i++)
for (var i = 0; i < length(sieve); i++)
if (sieve.read_logical(i)) c++
log.console(c)

View File

@@ -1,6 +1,6 @@
function fannkuch(n) {
var perm1 = [n]
for (let i = 0; i < n; i++) perm1[i] = i
for (var i = 0; i < n; i++) perm1[i] = i
var perm = [n]
var count = [n]
var f = 0, flips = 0, nperm = 0, checksum = 0
@@ -18,7 +18,7 @@ function fannkuch(n) {
while (k != 0) {
i = 0
while (2*i < k) {
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
var t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
i += 1
}
k = perm[0]
@@ -34,10 +34,10 @@ function fannkuch(n) {
log.console( checksum )
return flips
}
let p0 = perm1[0]
var p0 = perm1[0]
i = 0
while (i < r) {
let j = i + 1
var j = i + 1
perm1[i] = perm1[j]
i = j
}

View File

@@ -7,9 +7,9 @@ function fib(n) {
var now = time.number()
var arr = [1,2,3,4,5]
for (var i in arr) {
arrfor(arr, function(i) {
log.console(fib(28))
}
})
log.console(`elapsed: ${time.number()-now}`)

View File

@@ -109,12 +109,12 @@ function benchArrayOps() {
var pushTime = measureTime(function() {
var arr = [];
for (var i = 0; i < iterations.medium; i++) {
arr.push(i);
push(arr, i);
}
});
var arr = [];
for (var i = 0; i < 10000; i++) arr.push(i);
for (var i = 0; i < 10000; i++) push(arr, i);
var accessTime = measureTime(function() {
var sum = 0;
@@ -126,7 +126,7 @@ function benchArrayOps() {
var iterateTime = measureTime(function() {
var sum = 0;
for (var j = 0; j < 1000; j++) {
for (var i = 0; i < arr.length; i++) {
for (var i = 0; i < length(arr); i++) {
sum += arr[i];
}
}
@@ -151,13 +151,12 @@ function benchObjectCreation() {
});
function Point(x, y) {
this.x = x;
this.y = y;
return {x,y}
}
var defructorTime = measureTime(function() {
for (var i = 0; i < iterations.medium; i++) {
var p = new Point(i, i * 2);
var p = Point(i, i * 2);
}
});
@@ -199,19 +198,19 @@ function benchStringOps() {
var strings = [];
for (var i = 0; i < 1000; i++) {
strings.push("string" + i);
push(strings, "string" + i);
}
var joinTime = measureTime(function() {
for (var i = 0; i < iterations.complex; i++) {
var result = strings.join(",");
var result = text(strings, ",");
}
});
var splitTime = measureTime(function() {
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
for (var i = 0; i < iterations.medium; i++) {
var parts = str.split(",");
var parts = array(str, ",");
}
});
@@ -239,7 +238,7 @@ function benchArithmetic() {
var result = 1.5;
for (var i = 0; i < iterations.simple; i++) {
result = math.sine(result) + math.cosine(i * 0.01);
result = math.sqrt(number.abs(result)) + 0.1;
result = math.sqrt(abs(result)) + 0.1;
}
});
@@ -270,13 +269,13 @@ function benchClosures() {
var closureCreateTime = measureTime(function() {
var funcs = [];
for (var i = 0; i < iterations.medium; i++) {
funcs.push(makeAdder(i));
push(funcs, makeAdder(i));
}
});
var adders = [];
for (var i = 0; i < 1000; i++) {
adders.push(makeAdder(i));
push(adders, makeAdder(i));
}
var closureCallTime = measureTime(function() {

View File

@@ -8,15 +8,15 @@ var w = h
log.console(`P4\n${w} ${h}`);
for (let y = 0; y < h; ++y) {
for (var y = 0; y < h; ++y) {
// Create a blob for the row - we need w bits
var row = new blob(w);
var row = blob(w);
for (let x = 0; x < w; ++x) {
for (var x = 0; x < w; ++x) {
zr = zi = tr = ti = 0;
cr = 2 * x / w - 1.5;
ci = 2 * y / h - 1;
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
for (var i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
zi = 2 * zr * zi + ci;
zr = tr - ti + cr;
tr = zr * zr;

View File

@@ -3,17 +3,11 @@ var SOLAR_MASS = 4 * pi * pi;
var DAYS_PER_YEAR = 365.24;
function Body(x, y, z, vx, vy, vz, mass) {
this.x = x;
this.y = y;
this.z = z;
this.vx = vx;
this.vy = vy;
this.vz = vz;
this.mass = mass;
return {x, y, z, vx, vy, vz, mass};
}
function Jupiter() {
return new Body(
return Body(
4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01,
@@ -25,7 +19,7 @@ function Jupiter() {
}
function Saturn() {
return new Body(
return Body(
8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01,
@@ -37,7 +31,7 @@ function Saturn() {
}
function Uranus() {
return new Body(
return Body(
1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01,
@@ -49,7 +43,7 @@ function Uranus() {
}
function Neptune() {
return new Body(
return Body(
1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01,
@@ -61,7 +55,7 @@ function Neptune() {
}
function Sun() {
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
return Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
}
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
@@ -70,7 +64,7 @@ function offsetMomentum() {
var px = 0;
var py = 0;
var pz = 0;
var size = bodies.length;
var size = length(bodies);
for (var i = 0; i < size; i++) {
var body = bodies[i];
var mass = body.mass;
@@ -86,7 +80,7 @@ function offsetMomentum() {
}
function advance(dt) {
var size = bodies.length;
var size = length(bodies);
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];
@@ -127,7 +121,7 @@ function advance(dt) {
function energy() {
var e = 0;
var size = bodies.length;
var size = length(bodies);
for (var i = 0; i < size; i++) {
var bodyi = bodies[i];

View File

@@ -9,7 +9,7 @@ var newarr = []
var accstr = ""
for (var i = 0; i < 10000; i++) {
accstr += i;
newarr.push(i.toString())
newarrpush(i.toString())
}
// Arrays to store timing results
var jsonDecodeTimes = [];
@@ -19,34 +19,35 @@ var notaDecodeTimes = [];
var notaSizes = [];
// Run 100 tests
for (let i = 0; i < 100; i++) {
for (var i = 0; i < 100; i++) {
// JSON Decode test
let start = os.now();
var start = os.now();
var jll = json.decode(ll);
jsonDecodeTimes.push((os.now() - start) * 1000);
jsonDecodeTimespush((os.now() - start) * 1000);
// JSON Encode test
start = os.now();
let jsonStr = JSON.stringify(jll);
jsonEncodeTimes.push((os.now() - start) * 1000);
var jsonStr = JSON.stringify(jll);
jsonEncodeTimespush((os.now() - start) * 1000);
// NOTA Encode test
start = os.now();
var nll = nota.encode(jll);
notaEncodeTimes.push((os.now() - start) * 1000);
notaEncodeTimespush((os.now() - start) * 1000);
// NOTA Decode test
start = os.now();
var oll = nota.decode(nll);
notaDecodeTimes.push((os.now() - start) * 1000);
notaDecodeTimespush((os.now() - start) * 1000);
}
// Calculate statistics
function getStats(arr) {
def avg = arr.reduce((a, b) => a + b) / arr.length;
def min = number.min(...arr);
def max = number.max(...arr);
return { avg, min, max };
return {
avg: reduce(arr, (a,b) => a+b, 0) / length(arr),
min: reduce(arr, min),
max: reduce(arr, max)
};
}
// Pretty print results

View File

@@ -1,13 +1,13 @@
const math = require('math/radians');
def math = use('math/radians');
function A(i,j) {
return 1/((i+j)*(i+j+1)/2+i+1);
}
function Au(u,v) {
for (var i=0; i<u.length; ++i) {
for (var i=0; i<length(u); ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
for (var j=0; j<length(u); ++j)
t += A(i,j) * u[j];
v[i] = t;
@@ -15,9 +15,9 @@ function Au(u,v) {
}
function Atu(u,v) {
for (var i=0; i<u.length; ++i) {
for (var i=0; i<length(u); ++i) {
var t = 0;
for (var j=0; j<u.length; ++j)
for (var j=0; j<length(u); ++j)
t += A(j,i) * u[j];
v[i] = t;

View File

@@ -14,18 +14,18 @@
// Helper to run a function repeatedly and measure total time in seconds.
// Returns elapsed time in seconds.
function measureTime(fn, iterations) {
let t1 = os.now();
for (let i = 0; i < iterations; i++) {
var t1 = os.now();
for (var i = 0; i < iterations; i++) {
fn();
}
let t2 = os.now();
var t2 = os.now();
return t2 - t1;
}
// We'll define a function that does `encode -> decode` for a given value:
function roundTripWota(value) {
let encoded = wota.encode(value);
let decoded = wota.decode(encoded);
var encoded = wota.encode(value);
var decoded = wota.decode(encoded);
// Not doing a deep compare here, just measuring performance.
// (We trust the test suite to verify correctness.)
}
@@ -63,15 +63,9 @@ def benchmarks = [
{
name: "Large Array (1k numbers)",
// A thousand random numbers
data: [ Array.from({length:1000}, (_, i) => i * 0.5) ],
data: [ array(1000, i => i *0.5) ],
iterations: 1000
},
{
name: "Large Binary Blob (256KB)",
// A 256KB ArrayBuffer
data: [ new Uint8Array(256 * 1024).buffer ],
iterations: 200
}
];
// Print a header
@@ -79,28 +73,23 @@ log.console("Wota Encode/Decode Benchmark");
log.console("===================\n");
// We'll run each benchmark scenario in turn.
for (let bench of benchmarks) {
// We'll measure how long it takes to do 'iterations' *for each test value*
// in bench.data. The total loop count is `bench.iterations * bench.data.length`.
// Then we compute an overall encode+decode throughput (ops/s).
let totalIterations = bench.iterations * bench.data.length;
arrfor(benchmarks, function(bench) {
var totalIterations = bench.iterations * length(bench.data);
// We'll define a function that does a roundTrip for *each* data item in bench.data
// to measure in one loop iteration. Then we multiply by bench.iterations.
function runAllData() {
for (let val of bench.data) {
roundTripWota(val);
}
arrfor(bench.data, roundTripWota)
}
let elapsedSec = measureTime(runAllData, bench.iterations);
let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
var elapsedSec = measureTime(runAllData, bench.iterations);
var opsPerSec = (totalIterations / elapsedSec).toFixed(1);
log.console(`${bench.name}:`);
log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
log.console(` Iterations: ${bench.iterations} × ${length(bench.data)} data items = ${totalIterations}`);
log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
}
})
// All done
log.console("Benchmark completed.\n");

View File

@@ -13,7 +13,7 @@
//
// Parse command line arguments
if (arg.length != 2) {
if (length(arg) != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
$stop()
}
@@ -32,7 +32,7 @@ def libraries = [
decode: wota.decode,
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
getSize(encoded) {
return encoded.length;
return length(encoded);
}
},
{
@@ -41,7 +41,7 @@ def libraries = [
decode: nota.decode,
// nota also produces an ArrayBuffer:
getSize(encoded) {
return encoded.length;
return length(encoded);
}
},
{
@@ -50,9 +50,8 @@ def libraries = [
decode: json.decode,
// json produces a JS string. We'll measure its UTF-16 code unit length
// as a rough "size". Alternatively, you could convert to UTF-8 for
// a more accurate byte size. Here we just use `string.length`.
getSize(encodedStr) {
return encodedStr.length;
return length(encodedStr);
}
}
];
@@ -98,7 +97,7 @@ def benchmarks = [
},
{
name: "large_array",
data: [ Array.from({length:1000}, (_, i) => i) ],
data: [ array(1000, i => i) ],
iterations: 1000
},
];
@@ -108,9 +107,9 @@ def benchmarks = [
////////////////////////////////////////////////////////////////////////////////
function measureTime(fn) {
let start = os.now();
var start = os.now();
fn();
let end = os.now();
var end = os.now();
return (end - start); // in seconds
}
@@ -128,19 +127,19 @@ function runBenchmarkForLibrary(lib, bench) {
// Pre-store the encoded results for all items so we can measure decode time
// in a separate pass. Also measure total size once.
let encodedList = [];
let totalSize = 0;
var encodedList = [];
var totalSize = 0;
// 1) Measure ENCODING
let encodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
var encodeTime = measureTime(() => {
for (var i = 0; i < bench.iterations; i++) {
// For each data item, encode it
for (let j = 0; j < bench.data.length; j++) {
let e = lib.encode(bench.data[j]);
for (var j = 0; j < length(bench.data); j++) {
var e = lib.encode(bench.data[j]);
// store only in the very first iteration, so we can decode them later
// but do not store them every iteration or we blow up memory.
if (i == 0) {
encodedList.push(e);
push(encodedList, e);
totalSize += lib.getSize(e);
}
}
@@ -148,13 +147,9 @@ function runBenchmarkForLibrary(lib, bench) {
});
// 2) Measure DECODING
let decodeTime = measureTime(() => {
for (let i = 0; i < bench.iterations; i++) {
// decode everything we stored during the first iteration
for (let e of encodedList) {
let decoded = lib.decode(e);
// not verifying correctness here, just measuring speed
}
var decodeTime = measureTime(() => {
for (var i = 0; i < bench.iterations; i++) {
arrfor(encodedList, lib.decode)
}
});
@@ -166,18 +161,18 @@ function runBenchmarkForLibrary(lib, bench) {
////////////////////////////////////////////////////////////////////////////////
// Find the requested library and scenario
var lib = libraries.find(l => l.name == lib_name);
var bench = benchmarks.find(b => b.name == scenario_name);
var lib = libraries[find(libraries, l => l.name == lib_name)];
var bench = benchmarks[find(benchmarks, b => b.name == scenario_name)];
if (!lib) {
log.console('Unknown library:', lib_name);
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
log.console('Available libraries:', text(array(libraries, l => l.name), ', '));
$stop()
}
if (!bench) {
log.console('Unknown scenario:', scenario_name);
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
log.console('Available scenarios:', text(array(benchmarks, b => b.name), ', '));
$stop()
}
@@ -185,7 +180,7 @@ if (!bench) {
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
// Output json for easy parsing by hyperfine or other tools
var totalOps = bench.iterations * bench.data.length;
var totalOps = bench.iterations * length(bench.data);
var result = {
lib: lib_name,
scenario: scenario_name,

42
bootstrap.ce Normal file
View File

@@ -0,0 +1,42 @@
// 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"},
{src: "internal/engine.cm", name: "engine", out: "internal/engine.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

@@ -1,9 +1,11 @@
// cell build [options] - Build dynamic libraries locally for the current machine
// cell build [<locator>] - Build dynamic libraries locally for the current machine
//
// Usage:
// cell build Build dynamic libraries for all packages
// cell build -p <pkg> Build dynamic library for specific package
// cell build Build dynamic libraries for all packages in shop
// cell build . Build dynamic library for current directory package
// cell build <locator> Build dynamic library for specific package
// cell build -t <target> Cross-compile dynamic libraries for target platform
// cell build -b <type> Build type: release (default), debug, or minsize
var build = use('build')
var shop = use('internal/shop')
@@ -12,25 +14,28 @@ var fd = use('fd')
var target = null
var target_package = null
var buildtype = 'debug'
var buildtype = 'release'
var force_rebuild = false
var dry_run = false
for (var i = 0; i < args.length; i++) {
for (var i = 0; i < length(args); i++) {
if (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < args.length) {
if (i + 1 < length(args)) {
target = args[++i]
} else {
log.error('-t requires a target')
$stop()
}
} else if (args[i] == '-p' || args[i] == '--package') {
if (i + 1 < args.length) {
// Legacy support for -p flag
if (i + 1 < length(args)) {
target_package = args[++i]
} else {
log.error('-p requires a package name')
$stop()
}
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < args.length) {
if (i + 1 < length(args)) {
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
@@ -40,13 +45,30 @@ for (var i = 0; i < args.length; i++) {
log.error('-b requires a buildtype (release, debug, minsize)')
$stop()
}
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') {
log.console('Available targets:')
var targets = build.list_targets()
for (var t = 0; t < targets.length; t++) {
for (var t = 0; t < length(targets); t++) {
log.console(' ' + targets[t])
}
$stop()
} else if (!starts_with(args[i], '-') && !target_package) {
// Positional argument - treat as package locator
target_package = args[i]
}
}
// Resolve local paths to absolute paths
if (target_package) {
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) {
var resolved = fd.realpath(target_package)
if (resolved) {
target_package = resolved
}
}
}
@@ -58,16 +80,16 @@ if (!target) {
if (target && !build.has_target(target)) {
log.error('Invalid target: ' + target)
log.console('Available targets: ' + build.list_targets().join(', '))
log.console('Available targets: ' + text(build.list_targets(), ', '))
$stop()
}
var packages = shop.list_packages()
log.console('Preparing packages...')
for (var package of packages) {
if (package == 'core') continue
arrfor(packages, function(package) {
if (package == 'core') return
shop.extract(package)
}
})
if (target_package) {
// Build single package
@@ -88,7 +110,7 @@ if (target_package) {
var success = 0
var failed = 0
for (var i = 0; i < results.length; i++) {
for (var i = 0; i < length(results); i++) {
if (results[i].library) {
success++
} else if (results[i].error) {

295
build.cm
View File

@@ -28,15 +28,15 @@ function get_local_dir() {
// Replace sigils in a string
// Currently supports: $LOCAL -> .cell/local full path
function replace_sigils(str) {
return str.replaceAll('$LOCAL', get_local_dir())
return replace(str, '$LOCAL', get_local_dir())
}
// Replace sigils in an array of flags
function replace_sigils_array(flags) {
var result = []
for (var i = 0; i < flags.length; i++) {
result.push(replace_sigils(flags[i]))
}
arrfor(flags, function(flag) {
push(result, replace_sigils(flag))
})
return result
}
@@ -73,7 +73,7 @@ Build.detect_host_target = function() {
// ============================================================================
function content_hash(str) {
var bb = stone(new blob(str))
var bb = stone(blob(str))
return text(crypto.blake2(bb, 32), 'h')
}
@@ -83,14 +83,12 @@ function get_build_dir() {
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = path.split('/')
var current = path.startsWith('/') ? '/' : ''
for (var i = 0; i < parts.length; i++) {
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
for (var i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
if (!fd.stat(current).isDirectory) fd.mkdir(current)
}
}
@@ -107,7 +105,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
var src_path = pkg_dir + '/' + file
if (!fd.is_file(src_path)) {
throw new Error('Source file not found: ' + src_path)
throw Error('Source file not found: ' + src_path)
}
// Get flags (with sigil replacement)
@@ -123,33 +121,32 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
// Add buildtype-specific flags
if (buildtype == 'release') {
cmd_parts.push('-O3', '-DNDEBUG')
cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG'])
} else if (buildtype == 'debug') {
cmd_parts.push('-O2', '-g')
cmd_parts = array(cmd_parts, ['-O2', '-g'])
} else if (buildtype == 'minsize') {
cmd_parts.push('-Os', '-DNDEBUG')
cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG'])
}
cmd_parts.push('-DCELL_USE_NAME=' + sym_name)
cmd_parts.push('-I"' + pkg_dir + '"')
push(cmd_parts, '-DCELL_USE_NAME=' + sym_name)
push(cmd_parts, '-I"' + pkg_dir + '"')
// Add package CFLAGS (resolve relative -I paths)
for (var i = 0; i < cflags.length; i++) {
var flag = cflags[i]
if (flag.startsWith('-I') && !flag.startsWith('-I/')) {
flag = '-I"' + pkg_dir + '/' + flag.substring(2) + '"'
arrfor(cflags, function(flag) {
if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) {
flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"'
}
cmd_parts.push(flag)
}
push(cmd_parts, flag)
})
// Add target CFLAGS
for (var i = 0; i < target_cflags.length; i++) {
cmd_parts.push(target_cflags[i])
}
arrfor(target_cflags, function(flag) {
push(cmd_parts, flag)
})
cmd_parts.push('"' + src_path + '"')
push(cmd_parts, '"' + src_path + '"')
var cmd_str = cmd_parts.join(' ')
var cmd_str = text(cmd_parts, ' ')
// Content hash: command + file content
var file_content = fd.slurp(src_path)
@@ -170,7 +167,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
log.console('Compiling ' + file)
var ret = os.system(full_cmd)
if (ret != 0) {
throw new Error('Compilation failed: ' + file)
throw Error('Compilation failed: ' + file)
}
return obj_path
@@ -182,10 +179,10 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
var c_files = pkg_tools.get_c_files(pkg, target, exclude_main)
var objects = []
for (var i = 0; i < c_files.length; i++) {
var obj = Build.compile_file(pkg, c_files[i], target, buildtype)
objects.push(obj)
}
arrfor(c_files, function(file) {
var obj = Build.compile_file(pkg, file, target, buildtype)
push(objects, obj)
})
return objects
}
@@ -193,24 +190,51 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
// ============================================================================
// Dynamic library building
// ============================================================================
// Compute link key from all inputs that affect the dylib output
function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
// Sort objects for deterministic hash
var sorted_objects = sort(objects)
// Build a string representing all link inputs
var parts = []
push(parts, 'target:' + target)
push(parts, 'cc:' + cc)
arrfor(sorted_objects, function(obj) {
// Object paths are content-addressed, so the path itself is the hash
push(parts, 'obj:' + obj)
})
arrfor(ldflags, function(flag) {
push(parts, 'ldflag:' + flag)
})
arrfor(target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + flag)
})
return content_hash(text(parts, '\n'))
}
// Build a dynamic library for a package
// Output goes to .cell/lib/<package_name>.<ext>
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
// Uses content-addressed store + symlink for caching
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
if (objects.length == 0) {
if (length(objects) == 0) {
log.console('No C files in ' + pkg)
return null
}
var lib_dir = shop.get_lib_dir()
var store_dir = lib_dir + '/store'
ensure_dir(lib_dir)
ensure_dir(store_dir)
var lib_name = shop.lib_name_for_package(pkg)
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
var lib_path = lib_dir + '/' + lib_name + dylib_ext
var stable_path = lib_dir + '/' + lib_name + dylib_ext
// Get link flags (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var target_ldflags = toolchains[target].c_link_args || []
@@ -218,65 +242,92 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
var pkg_dir = shop.get_package_dir(pkg)
var local_dir = get_local_dir()
var tc = toolchains[target]
// Resolve relative -L paths in ldflags for hash computation
var resolved_ldflags = []
arrfor(ldflags, function(flag) {
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
}
push(resolved_ldflags, flag)
})
// Compute link key
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
// Check if already linked in store
if (fd.is_file(store_path)) {
// Ensure symlink points to the store file
if (fd.is_link(stable_path)) {
var current_target = fd.readlink(stable_path)
if (current_target == store_path) {
// Already up to date
return stable_path
}
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// Build link command
var cmd_parts = [cc, '-shared', '-fPIC']
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') {
// Allow undefined symbols - they will be resolved when dlopen'd into the main executable
cmd_parts.push('-undefined', 'dynamic_lookup')
// Dead-strip unused code
cmd_parts.push('-Wl,-dead_strip')
// rpath for .cell/local libraries
cmd_parts.push('-Wl,-rpath,@loader_path/../local')
cmd_parts.push('-Wl,-rpath,' + local_dir)
cmd_parts = array(cmd_parts, [
'-undefined', 'dynamic_lookup',
'-Wl,-dead_strip',
'-Wl,-install_name,' + stable_path,
'-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'linux') {
// Allow undefined symbols at link time
cmd_parts.push('-Wl,--allow-shlib-undefined')
// Garbage collect unused sections
cmd_parts.push('-Wl,--gc-sections')
// rpath for .cell/local libraries
cmd_parts.push('-Wl,-rpath,$ORIGIN/../local')
cmd_parts.push('-Wl,-rpath,' + local_dir)
cmd_parts = array(cmd_parts, [
'-Wl,--allow-shlib-undefined',
'-Wl,--gc-sections',
'-Wl,-rpath,$ORIGIN/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
// Windows DLLs: use --allow-shlib-undefined for mingw
cmd_parts.push('-Wl,--allow-shlib-undefined')
push(cmd_parts, '-Wl,--allow-shlib-undefined')
}
// Add .cell/local to library search path
cmd_parts.push('-L"' + local_dir + '"')
for (var i = 0; i < objects.length; i++) {
cmd_parts.push('"' + objects[i] + '"')
}
push(cmd_parts, '-L"' + local_dir + '"')
arrfor(objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
// Do NOT link against core library - symbols resolved at dlopen time
// Add LDFLAGS (resolve relative -L paths)
for (var i = 0; i < ldflags.length; i++) {
var flag = ldflags[i]
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
}
cmd_parts.push(flag)
}
for (var i = 0; i < target_ldflags.length; i++) {
cmd_parts.push(target_ldflags[i])
}
cmd_parts.push('-o', '"' + lib_path + '"')
var cmd_str = cmd_parts.join(' ')
log.console('Linking ' + lib_path)
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + store_path + '"')
var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + lib_name + dylib_ext)
var ret = os.system(cmd_str)
if (ret != 0) {
throw new Error('Linking failed: ' + pkg)
throw Error('Linking failed: ' + pkg)
}
return lib_path
// Update symlink to point to the new store file
if (fd.is_link(stable_path)) {
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
// ============================================================================
@@ -292,38 +343,36 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
var seen_flags = {}
// Compile all packages
for (var i = 0; i < packages.length; i++) {
var pkg = packages[i]
arrfor(packages, function(pkg) {
var is_core = (pkg == 'core')
// For core, include main.c; for others, exclude it
var objects = Build.build_package(pkg, target, !is_core, buildtype)
for (var j = 0; j < objects.length; j++) {
all_objects.push(objects[j])
}
arrfor(objects, function(obj) {
push(all_objects, obj)
})
// Collect LDFLAGS (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var pkg_dir = shop.get_package_dir(pkg)
// Deduplicate based on the entire LDFLAGS string for this package
var ldflags_key = pkg + ':' + ldflags.join(' ')
var ldflags_key = pkg + ':' + text(ldflags, ' ')
if (!seen_flags[ldflags_key]) {
seen_flags[ldflags_key] = true
for (var j = 0; j < ldflags.length; j++) {
var flag = ldflags[j]
arrfor(ldflags, function(flag) {
// Resolve relative -L paths
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
}
all_ldflags.push(flag)
}
push(all_ldflags, flag)
})
}
}
})
if (all_objects.length == 0) {
throw new Error('No object files to link')
if (length(all_objects) == 0) {
throw Error('No object files to link')
}
// Link
@@ -331,32 +380,32 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
var target_ldflags = toolchains[target].c_link_args || []
var exe_ext = toolchains[target].system == 'windows' ? '.exe' : ''
if (!output.endsWith(exe_ext) && exe_ext) {
if (!ends_with(output, exe_ext) && exe_ext) {
output = output + exe_ext
}
var cmd_parts = [cc]
for (var i = 0; i < all_objects.length; i++) {
cmd_parts.push('"' + all_objects[i] + '"')
}
arrfor(all_objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
for (var i = 0; i < all_ldflags.length; i++) {
cmd_parts.push(all_ldflags[i])
}
arrfor(all_ldflags, function(flag) {
push(cmd_parts, flag)
})
for (var i = 0; i < target_ldflags.length; i++) {
cmd_parts.push(target_ldflags[i])
}
arrfor(target_ldflags, function(flag) {
push(cmd_parts, flag)
})
cmd_parts.push('-o', '"' + output + '"')
push(cmd_parts, '-o', '"' + output + '"')
var cmd_str = cmd_parts.join(' ')
var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + output)
var ret = os.system(cmd_str)
if (ret != 0) {
throw new Error('Linking failed with command: ' + cmd_str)
throw Error('Linking failed with command: ' + cmd_str)
}
log.console('Built ' + output)
@@ -375,30 +424,30 @@ Build.build_all_dynamic = function(target, buildtype = 'release') {
var results = []
// Build core first
if (packages.indexOf('core') >= 0) {
if (find(packages, 'core') != null) {
try {
var lib = Build.build_dynamic('core', target, buildtype)
results.push({ package: 'core', library: lib })
push(results, { package: 'core', library: lib })
} catch (e) {
log.error('Failed to build core: ' + text(e))
results.push({ package: 'core', error: e })
push(results, { package: 'core', error: e })
}
}
// Build other packages
for (var i = 0; i < packages.length; i++) {
var pkg = packages[i]
if (pkg == 'core') continue
arrfor(packages, function(pkg) {
if (pkg == 'core') return
try {
var lib = Build.build_dynamic(pkg, target, buildtype)
results.push({ package: pkg, library: lib })
push(results, { package: pkg, library: lib })
} catch (e) {
log.error('Failed to build ' + pkg + ': ')
log.error(e)
results.push({ package: pkg, error: e })
log.console(e.message)
log.console(e.stack)
push(results, { package: pkg, error: e })
}
}
})
return results
}

189
cellfs.cm
View File

@@ -17,30 +17,7 @@ var writepath = "."
function normalize_path(path) {
if (!path) return ""
// Remove leading/trailing slashes and normalize
return path.replace(/^\/+|\/+$/g, "")
}
// Helper to get directory from path
function dirname(path) {
var idx = path.lastIndexOf("/")
if (idx == -1) return ""
return path.substring(0, idx)
}
// Helper to get basename from path
function basename(path) {
var idx = path.lastIndexOf("/")
if (idx == -1) return path
return path.substring(idx + 1)
}
// Helper to join paths
function join_paths(base, rel) {
base = base.replace(/\/+$/, "")
rel = rel.replace(/^\/+/, "")
if (!base) return rel
if (!rel) return base
return base + "/" + rel
return replace(path, /^\/+|\/+$/, "")
}
// Check if a file exists in a specific mount
@@ -59,7 +36,7 @@ function mount_exists(mount, path) {
return false
}
} else { // fs
var full_path = join_paths(mount.source, path)
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isFile || st.isDirectory
@@ -86,7 +63,7 @@ function is_directory(path) {
return false;
}
} else { // fs
var full_path = join_paths(mount.source, path)
var full_path = fd.join_paths(mount.source, path)
try {
var st = fd.stat(full_path)
return st.isDirectory
@@ -102,44 +79,50 @@ function resolve(path, must_exist) {
path = normalize_path(path)
// Check for named mount
if (path.startsWith("@")) {
var idx = path.indexOf("/")
if (starts_with(path, "@")) {
var idx = search(path, "/")
var mount_name = ""
var rel_path = ""
if (idx == -1) {
mount_name = path.substring(1)
if (idx == null) {
mount_name = text(path, 1)
rel_path = ""
} else {
mount_name = path.substring(1, idx)
rel_path = path.substring(idx + 1)
mount_name = text(path, 1, idx)
rel_path = text(path, idx + 1)
}
// Find named mount
var mount = null
for (var m of mounts) {
arrfor(mounts, function(m) {
if (m.name == mount_name) {
mount = m
break
return true
}
}
}, false, true)
if (!mount) {
throw new Error("Unknown mount point: @" + mount_name)
throw Error("Unknown mount point: @" + mount_name)
}
return { mount: mount, path: rel_path }
}
// Search path
for (var mount of mounts) {
var found_mount = null
arrfor(mounts, function(mount) {
if (mount_exists(mount, path)) {
return { mount: mount, path: path }
found_mount = { mount: mount, path: path }
return true
}
}, false, true)
if (found_mount) {
return found_mount
}
if (must_exist) {
throw new Error("File not found in any mount: " + path)
throw Error("File not found in any mount: " + path)
}
}
@@ -174,8 +157,8 @@ function mount(source, name) {
mount_info.zip_blob = blob // keep blob alive
} else {
var zip = miniz.read(blob)
if (!zip || typeof zip.count != 'function') {
throw new Error("Invalid archive file (not zip or qop): " + source)
if (!is_object(zip) || !is_function(zip.count)) {
throw Error("Invalid archive file (not zip or qop): " + source)
}
mount_info.type = 'zip'
@@ -183,36 +166,32 @@ function mount(source, name) {
mount_info.zip_blob = blob // keep blob alive
}
} else {
throw new Error("Unsupported mount source type: " + source)
throw Error("Unsupported mount source type: " + source)
}
mounts.push(mount_info)
push(mounts, mount_info)
}
// Unmount
function unmount(name_or_source) {
for (var i = 0; i < mounts.length; i++) {
if (mounts[i].name == name_or_source || mounts[i].source == name_or_source) {
mounts.splice(i, 1)
return
}
}
throw new Error("Mount not found: " + name_or_source)
mounts = filter(mounts, function(mount) {
return mount.name != name_or_source && mount.source != name_or_source
})
}
// Read file
function slurp(path) {
var res = resolve(path, true)
if (!res) throw new Error("File not found: " + path)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
return res.mount.handle.slurp(res.path)
} else if (res.mount.type == 'qop') {
var data = res.mount.handle.read(res.path)
if (!data) throw new Error("File not found in qop: " + path)
if (!data) throw Error("File not found in qop: " + path)
return data
} else {
var full_path = join_paths(res.mount.source, res.path)
var full_path = fd.join_paths(res.mount.source, res.path)
return fd.slurp(full_path)
}
}
@@ -229,7 +208,7 @@ function slurpwrite(path, data) {
// Check existence
function exists(path) {
var res = resolve(path, false)
if (path.startsWith("@")) {
if (starts_with(path, "@")) {
return mount_exists(res.mount, res.path)
}
return res != null
@@ -238,7 +217,7 @@ function exists(path) {
// Stat
function stat(path) {
var res = resolve(path, true)
if (!res) throw new Error("File not found: " + path)
if (!res) throw Error("File not found: " + path)
if (res.mount.type == 'zip') {
var mod = res.mount.handle.mod(res.path)
@@ -249,14 +228,14 @@ function stat(path) {
}
} else if (res.mount.type == 'qop') {
var s = res.mount.handle.stat(res.path)
if (!s) throw new Error("File not found in qop: " + path)
if (!s) throw Error("File not found in qop: " + path)
return {
filesize: s.size,
modtime: s.modtime,
isDirectory: s.isDirectory
}
} else {
var full_path = join_paths(res.mount.source, res.path)
var full_path = fd.join_paths(res.mount.source, res.path)
var s = fd.stat(full_path)
return {
filesize: s.size,
@@ -268,7 +247,7 @@ function stat(path) {
// Get search paths
function searchpath() {
return mounts.slice()
return array(mounts)
}
// Mount a package using the shop system
@@ -282,7 +261,7 @@ function mount_package(name) {
var dir = shop.get_package_dir(name)
if (!dir) {
throw new Error("Package not found: " + name)
throw Error("Package not found: " + name)
}
mount(dir, name)
@@ -296,16 +275,16 @@ function match(str, pattern) {
function rm(path) {
var res = resolve(path, true)
if (res.mount.type != 'fs') throw new Error("Cannot delete from non-fs mount")
if (res.mount.type != 'fs') throw Error("Cannot delete from non-fs mount")
var full_path = join_paths(res.mount.source, res.path)
var full_path = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full_path)
if (st.isDirectory) fd.rmdir(full_path)
else fd.unlink(full_path)
}
function mkdir(path) {
var full = join_paths(writepath, path)
var full = fd.join_paths(writepath, path)
fd.mkdir(full)
}
@@ -324,7 +303,7 @@ function prefdir(org, app) {
function realdir(path) {
var res = resolve(path, false)
if (!res) return null
return join_paths(res.mount.source, res.path)
return fd.join_paths(res.mount.source, res.path)
}
function enumerate(path, recurse) {
@@ -337,21 +316,21 @@ function enumerate(path, recurse) {
var list = fd.readdir(curr_full)
if (!list) return
for (var item of list) {
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
results.push(item_rel)
push(results, item_rel)
if (recurse) {
var st = fd.stat(join_paths(curr_full, item))
var st = fd.stat(fd.join_paths(curr_full, item))
if (st.isDirectory) {
visit(join_paths(curr_full, item), item_rel)
visit(fd.join_paths(curr_full, item), item_rel)
}
}
}
})
}
if (res.mount.type == 'fs') {
var full = join_paths(res.mount.source, res.path)
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
@@ -359,29 +338,29 @@ function enumerate(path, recurse) {
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = prefix.length
var prefix_len = length(prefix)
// Use a set to avoid duplicates if we are simulating directories
var seen = {}
for (var p of all) {
if (p.startsWith(prefix)) {
var rel = p.substring(prefix_len)
if (rel.length == 0) continue
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!recurse) {
var slash = rel.indexOf('/')
if (slash != -1) {
rel = rel.substring(0, slash)
var slash = search(rel, '/')
if (slash != null) {
rel = text(rel, 0, slash)
}
}
if (!seen[rel]) {
seen[rel] = true
results.push(rel)
push(results, rel)
}
}
}
})
}
return results
@@ -393,17 +372,25 @@ function globfs(globs, dir) {
var results = []
function check_neg(path) {
for (var g of globs) {
if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true;
}
return false;
var result = false
arrfor(globs, function(g) {
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function check_pos(path) {
for (var g of globs) {
if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true;
}
return false;
var result = false
arrfor(globs, function(g) {
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
result = true
return true
}
}, false, true)
return result
}
function visit(curr_full, rel_prefix) {
@@ -412,10 +399,10 @@ function globfs(globs, dir) {
var list = fd.readdir(curr_full)
if (!list) return
for (var item of list) {
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_full = join_paths(curr_full, item)
var child_full = fd.join_paths(curr_full, item)
var st = fd.stat(child_full)
if (st.isDirectory) {
@@ -424,14 +411,14 @@ function globfs(globs, dir) {
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
results.push(item_rel)
push(results, item_rel)
}
}
}
})
}
if (res.mount.type == 'fs') {
var full = join_paths(res.mount.source, res.path)
var full = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full)
if (st && st.isDirectory) {
visit(full, "")
@@ -439,18 +426,18 @@ function globfs(globs, dir) {
} else if (res.mount.type == 'qop') {
var all = res.mount.handle.list()
var prefix = res.path ? res.path + "/" : ""
var prefix_len = prefix.length
var prefix_len = length(prefix)
for (var p of all) {
if (p.startsWith(prefix)) {
var rel = p.substring(prefix_len)
if (rel.length == 0) continue
arrfor(all, function(p) {
if (starts_with(p, prefix)) {
var rel = text(p, prefix_len)
if (length(rel) == 0) return
if (!check_neg(rel) && check_pos(rel)) {
results.push(rel)
push(results, rel)
}
}
}
})
}
return results

226
clean.ce
View File

@@ -1,26 +1,218 @@
// cell clean - Remove build artifacts from global shop
// cell clean [<scope>] - Remove cached material to force refetch/rebuild
//
// Usage:
// cell clean Clean build outputs for current directory package
// cell clean . Clean build outputs for current directory package
// cell clean <locator> Clean build outputs for specific package
// cell clean shop Clean entire shop
// cell clean world Clean all world packages
//
// Options:
// --build Remove build outputs only (default)
// --fetch Remove fetched sources only
// --all Remove both build outputs and fetched sources
// --deep Apply to full dependency closure
// --dry-run Show what would be deleted
var fd = use('fd')
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var build_dir = shop.get_shop_path() + '/build'
var scope = null
var clean_build = false
var clean_fetch = false
var deep = false
var dry_run = false
if (!fd.is_dir(build_dir)) {
log.console("No build directory found at " + build_dir)
$stop()
return
for (var i = 0; i < length(args); i++) {
if (args[i] == '--build') {
clean_build = true
} else if (args[i] == '--fetch') {
clean_fetch = true
} else if (args[i] == '--all') {
clean_build = true
clean_fetch = true
} else if (args[i] == '--deep') {
deep = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell clean [<scope>] [options]")
log.console("")
log.console("Remove cached material to force refetch/rebuild.")
log.console("")
log.console("Scopes:")
log.console(" <locator> Clean specific package")
log.console(" shop Clean entire shop")
log.console(" world Clean all world packages")
log.console("")
log.console("Options:")
log.console(" --build Remove build outputs only (default)")
log.console(" --fetch Remove fetched sources only")
log.console(" --all Remove both build outputs and fetched sources")
log.console(" --deep Apply to full dependency closure")
log.console(" --dry-run Show what would be deleted")
$stop()
} else if (!starts_with(args[i], '-')) {
scope = args[i]
}
}
log.console("Cleaning build artifacts...")
// Remove the build directory
try {
fd.rm(build_dir)
log.console("Build directory removed: " + build_dir)
} catch (e) {
log.error(e)
// Default to --build if nothing specified
if (!clean_build && !clean_fetch) {
clean_build = true
}
log.console("Clean complete!")
// Default scope to current directory
if (!scope) {
scope = '.'
}
$stop()
// Resolve local paths for single package scope
var is_shop_scope = (scope == 'shop')
var is_world_scope = (scope == 'world')
if (!is_shop_scope && !is_world_scope) {
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) {
var resolved = fd.realpath(scope)
if (resolved) {
scope = resolved
}
}
}
var files_to_delete = []
var dirs_to_delete = []
// Gather packages to clean
var packages_to_clean = []
if (is_shop_scope) {
packages_to_clean = shop.list_packages()
} else if (is_world_scope) {
// For now, world is the same as shop
packages_to_clean = shop.list_packages()
} else {
// Single package
push(packages_to_clean, scope)
if (deep) {
try {
var deps = pkg.gather_dependencies(scope)
arrfor(deps, function(dep) {
push(packages_to_clean, dep)
})
} catch (e) {
// Skip if can't read dependencies
}
}
}
// Gather files to clean
var lib_dir = shop.get_lib_dir()
var build_dir = shop.get_build_dir()
var packages_dir = replace(shop.get_package_dir(''), /\/$/, '') // Get base packages dir
if (clean_build) {
if (is_shop_scope) {
// Clean entire build and lib directories
if (fd.is_dir(build_dir)) {
push(dirs_to_delete, build_dir)
}
if (fd.is_dir(lib_dir)) {
push(dirs_to_delete, lib_dir)
}
} else {
// Clean specific package libraries
arrfor(packages_to_clean, function(p) {
if (p == 'core') return
var lib_name = shop.lib_name_for_package(p)
var dylib_ext = '.dylib'
var lib_path = lib_dir + '/' + lib_name + dylib_ext
if (fd.is_file(lib_path)) {
push(files_to_delete, lib_path)
}
// Also check for .so and .dll
var so_path = lib_dir + '/' + lib_name + '.so'
var dll_path = lib_dir + '/' + lib_name + '.dll'
if (fd.is_file(so_path)) {
push(files_to_delete, so_path)
}
if (fd.is_file(dll_path)) {
push(files_to_delete, dll_path)
}
})
}
}
if (clean_fetch) {
if (is_shop_scope) {
// Clean entire packages directory (dangerous!)
if (fd.is_dir(packages_dir)) {
push(dirs_to_delete, packages_dir)
}
} else {
// Clean specific package directories
arrfor(packages_to_clean, function(p) {
if (p == 'core') return
var pkg_dir = shop.get_package_dir(p)
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
push(dirs_to_delete, pkg_dir)
}
})
}
}
// Execute or report
if (dry_run) {
log.console("Would delete:")
if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) {
log.console(" (nothing to clean)")
} else {
arrfor(files_to_delete, function(f) {
log.console(" [file] " + f)
})
arrfor(dirs_to_delete, function(d) {
log.console(" [dir] " + d)
})
}
} else {
var deleted_count = 0
arrfor(files_to_delete, function(f) {
try {
fd.unlink(f)
log.console("Deleted: " + f)
deleted_count++
} catch (e) {
log.error("Failed to delete " + f + ": " + e)
}
})
arrfor(dirs_to_delete, function(d) {
try {
if (fd.is_link(d)) {
fd.unlink(d)
} else {
fd.rmdir(d, 1) // recursive
}
log.console("Deleted: " + d)
deleted_count++
} catch (e) {
log.error("Failed to delete " + d + ": " + e)
}
})
if (deleted_count == 0) {
log.console("Nothing to clean.")
} else {
log.console("")
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
}
}
$stop()

View File

@@ -7,7 +7,7 @@ var fd = use('fd')
var http = use('http')
var miniz = use('miniz')
if (args.length < 2) {
if (length(args) < 2) {
log.console("Usage: cell clone <origin> <path>")
log.console("Clones a cell package to a local path and links it.")
$stop()
@@ -18,7 +18,7 @@ var origin = args[0]
var target_path = args[1]
// Resolve target path to absolute
if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith('../')) {
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) {
var resolved = fd.realpath(target_path)
if (resolved) {
target_path = resolved
@@ -27,12 +27,12 @@ if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith
var cwd = fd.realpath('.')
if (target_path == '.') {
target_path = cwd
} else if (target_path.startsWith('./')) {
target_path = cwd + target_path.substring(1)
} else if (target_path.startsWith('../')) {
} else if (starts_with(target_path, './')) {
target_path = cwd + text(target_path, 1)
} else if (starts_with(target_path, '../')) {
// Go up one directory from cwd
var parent = cwd.substring(0, cwd.lastIndexOf('/'))
target_path = parent + target_path.substring(2)
var parent = fd.dirname(cwd)
target_path = parent + text(target_path, 2)
}
}
}
@@ -92,14 +92,13 @@ try {
for (var i = 0; i < count; i++) {
if (zip.is_directory(i)) continue
var filename = zip.get_filename(i)
var parts = filename.split('/')
if (parts.length <= 1) continue
// Skip the first directory (repo-commit prefix)
parts.shift()
var rel_path = parts.join('/')
var first_slash = search(filename, '/')
if (first_slash == null) continue
if (first_slash + 1 >= length(filename)) continue
var rel_path = text(filename, first_slash + 1)
var full_path = target_path + '/' + rel_path
var dir_path = full_path.substring(0, full_path.lastIndexOf('/'))
var dir_path = fd.dirname(full_path)
// Ensure directory exists
if (!fd.is_dir(dir_path)) {

102
compile.ce Normal file
View File

@@ -0,0 +1,102 @@
// compile.ce — compile a .cm module to native .dylib via QBE
//
// Usage:
// cell --core . compile.ce <file.cm>
//
// Produces <file>.dylib in the current directory.
var fd = use('fd')
var os = use('os')
if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>')
return
}
var file = args[0]
var base = file
if (ends_with(base, '.cm')) {
base = text(base, 0, length(base) - 3)
}
var safe = replace(replace(base, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var tmp = '/tmp/qbe_' + safe
var ssa_path = tmp + '.ssa'
var s_path = tmp + '.s'
var o_path = tmp + '.o'
var rt_o_path = '/tmp/qbe_rt.o'
var dylib_path = base + '.dylib'
var cwd = fd.getcwd()
var rc = 0
// Step 1: emit QBE IL
print('emit qbe...')
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
if (rc != 0) {
print('failed to emit qbe il')
return
}
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
// Use awk via shell to avoid blob/slurpwrite issues with long strings
print('post-process...')
var awk_cmd = `awk '
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
print "@_dead_" dead_id; dead_id++; need_label=0
}
/^@/ || /^}/ || NF==0 { need_label=0 }
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
{ print }
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
rc = os.system(awk_cmd)
if (rc != 0) {
print('post-process failed')
return
}
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// Delegates to cell_rt_module_entry which heap-allocates a frame
// (so closures survive) and calls cell_main.
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
rc = os.system(wrapper_cmd)
if (rc != 0) {
print('wrapper append failed')
return
}
// Step 3: compile QBE IL to assembly
print('qbe compile...')
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
if (rc != 0) {
print('qbe compilation failed')
return
}
// Step 4: assemble
print('assemble...')
rc = os.system('cc -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('assembly failed')
return
}
// Step 5: compile runtime stubs (cached — skip if already built)
if (!fd.is_file(rt_o_path)) {
print('compile runtime stubs...')
rc = os.system('cc -c ' + cwd + '/qbe_rt.c -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('runtime stubs compilation failed')
return
}
}
// Step 6: link dylib
print('link...')
rc = os.system('cc -shared -fPIC -undefined dynamic_lookup ' + o_path + ' ' + rt_o_path + ' -o ' + cwd + '/' + dylib_path)
if (rc != 0) {
print('linking failed')
return
}
print('built: ' + dylib_path)

View File

@@ -31,30 +31,30 @@ function print_help() {
// Parse a dot-notation key into path segments
function parse_key(key) {
return key.split('.')
return array(key, '.')
}
// Get a value from nested object using path
function get_nested(obj, path) {
var current = obj
for (var segment of path) {
if (!current || typeof current != 'object') return null
arrfor(path, function(segment) {
if (is_null(current) || !is_object(current)) return null
current = current[segment]
}
})
return current
}
// Set a value in nested object using path
function set_nested(obj, path, value) {
var current = obj
for (var i = 0; i < path.length - 1; i++) {
for (var i = 0; i < length(path) - 1; i++) {
var segment = path[i]
if (!current[segment] || typeof current[segment] != 'object') {
if (is_null(current[segment]) || !is_object(current[segment])) {
current[segment] = {}
}
current = current[segment]
}
current[path[path.length - 1]] = value
current[path[length(path) - 1]] = value
}
// Parse value string into appropriate type
@@ -64,7 +64,7 @@ function parse_value(str) {
if (str == 'false') return false
// Number (including underscores)
var num_str = str.replace(/_/g, '')
var num_str = replace(str, /_/g, '')
if (/^-?\d+$/.test(num_str)) return parseInt(num_str)
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str)
@@ -74,29 +74,29 @@ function parse_value(str) {
// Format value for display
function format_value(val) {
if (typeof val == 'string') return '"' + val + '"'
if (typeof val == 'number' && val >= 1000) {
if (is_text(val)) return '"' + val + '"'
if (is_number(val) && val >= 1000) {
// Add underscores to large numbers
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '_')
return replace(val.toString(), /\B(?=(\d{3})+(?!\d))/g, '_')
}
return text(val)
}
// Print configuration tree recursively
function print_config(obj, prefix = '') {
for (var key in obj) {
arrfor(array(obj), function(key) {
var val = obj[key]
var full_key = prefix ? prefix + '.' + key : key
if (isa(val, object))
if (is_object(val))
print_config(val, full_key)
else
log.console(full_key + ' = ' + format_value(val))
}
})
}
// Main command handling
if (args.length == 0) {
if (length(args) == 0) {
print_help()
$stop()
return
@@ -110,6 +110,9 @@ if (!config) {
}
var command = args[0]
var key
var path
var value
switch (command) {
case 'help':
@@ -125,14 +128,14 @@ switch (command) {
break
case 'get':
if (args.length < 2) {
if (length(args) < 2) {
log.error("Usage: cell config get <key>")
$stop()
return
}
var key = args[1]
var path = parse_key(key)
var value = get_nested(config, path)
key = args[1]
path = parse_key(key)
value = get_nested(config, path)
if (value == null) {
log.error("Key not found: " + key)
@@ -143,9 +146,9 @@ switch (command) {
log.console(key + ' = ' + format_value(value))
}
break
case 'set':
if (args.length < 3) {
if (length(args) < 3) {
log.error("Usage: cell config set <key> <value>")
$stop()
return
@@ -161,8 +164,8 @@ switch (command) {
'ar_timer', 'actor_memory', 'net_service',
'reply_timeout', 'actor_max', 'stack_max'
]
if (!valid_system_keys.includes(path[1])) {
log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', '))
if (find(valid_system_keys, path[1]) == null) {
log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', '))
$stop()
return
}
@@ -172,10 +175,10 @@ switch (command) {
pkg.save_config(config)
log.console("Set " + key + " = " + format_value(value))
break
case 'actor':
// Handle actor-specific configuration
if (args.length < 3) {
if (length(args) < 3) {
log.error("Usage: cell config actor <name> <command> [options]")
$stop()
return
@@ -190,7 +193,7 @@ switch (command) {
switch (actor_cmd) {
case 'list':
if (array(config.actors[actor_name]).length == 0) {
if (length(array(config.actors[actor_name])) == 0) {
log.console("No configuration for actor: " + actor_name)
} else {
log.console("# Configuration for actor: " + actor_name)
@@ -200,14 +203,14 @@ switch (command) {
break
case 'get':
if (args.length < 4) {
if (length(args) < 4) {
log.error("Usage: cell config actor <name> get <key>")
$stop()
return
}
var key = args[3]
var path = parse_key(key)
var value = get_nested(config.actors[actor_name], path)
key = args[3]
path = parse_key(key)
value = get_nested(config.actors[actor_name], path)
if (value == null) {
log.error("Key not found for actor " + actor_name + ": " + key)
@@ -217,21 +220,21 @@ switch (command) {
break
case 'set':
if (args.length < 5) {
if (length(args) < 5) {
log.error("Usage: cell config actor <name> set <key> <value>")
$stop()
return
}
var key = args[3]
key = args[3]
var value_str = args[4]
var path = parse_key(key)
var value = parse_value(value_str)
path = parse_key(key)
value = parse_value(value_str)
set_nested(config.actors[actor_name], path, value)
pkg.save_config(config)
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
break
default:
log.error("Unknown actor command: " + actor_cmd)
log.console("Valid commands: list, get, set")

View File

@@ -231,7 +231,7 @@ JSValue js_crypto_unlock(JSContext *js, JSValue self, int argc, JSValue *argv) {
static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("shared", 2, js_crypto_shared),
JS_CFUNC_DEF("blake2", 1, js_crypto_blake2),
JS_CFUNC_DEF("blake2", 2, js_crypto_blake2),
JS_CFUNC_DEF("sign", 2, js_crypto_sign),
JS_CFUNC_DEF("verify", 3, js_crypto_verify),
JS_CFUNC_DEF("lock", 3, js_crypto_lock),

View File

@@ -4,7 +4,7 @@
JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js)))
// Return a backtrace of the current call stack.
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js,NULL))
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js))
// Return the closure variables for a given function.
JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0]))
@@ -21,7 +21,7 @@ JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]))
// Return an array of functions in the current backtrace.
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL))
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js))
static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0),

View File

@@ -1,8 +1,6 @@
#include "cell.h"
JSC_CCALL(os_gc, JS_RunGC(JS_GetRuntime(js)) )
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
// Compute the approximate size of a single JS value in memory.
@@ -15,8 +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));
JS_SetPropertyStr(js,ret,"atom_count",number2js(js,mu.atom_count));
JS_SetPropertyStr(js,ret,"atom_size",number2js(js,mu.atom_size));
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));
@@ -38,47 +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.");
ret = JS_Eval(js,str2,strlen(str2),str, 0);
)
// 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_Eval(js, str2, strlen(str2), str, JS_EVAL_FLAG_COMPILE_ONLY | JS_EVAL_FLAG_BACKTRACE_BARRIER);
)
// Evaluate a function object in the current QuickJS context.
JSC_CCALL(js_eval_compile,
JS_DupValue(js,argv[0]);
ret = JS_EvalFunction(js, argv[0]);
)
// 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,14 +47,7 @@ JSC_CCALL(js_fn_info,
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, calc_mem, 0),
MIST_FUNC_DEF(os, mem_limit, 1),
MIST_FUNC_DEF(os, gc_threshold, 1),
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(os, gc, 0),
MIST_FUNC_DEF(os, eval, 2),
MIST_FUNC_DEF(js, compile, 2),
MIST_FUNC_DEF(js, eval_compile, 1),
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),
};
@@ -108,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
isa(42, number) // true
isa("hi", text) // true
isa([1,2], array) // true
isa({}, 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/)
text.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

513
docs/functions.md Normal file
View File

@@ -0,0 +1,513 @@
---
title: "Built-in Functions"
description: "Intrinsic constants and functions"
weight: 60
type: "docs"
---
The intrinsics are constants and functions that are built into the language. The `use` statement is not needed to access them.
A programmer is not obliged to consult the list of intrinsics before naming a new variable or input. New intrinsics may be added to ƿit without breaking existing programs.
## Constants
### false
The value of `1 == 0`. One of the two logical values.
### true
The value of `1 == 1`. One of the two logical values.
### null
The value of `1 / 0`. The null value is an empty immutable object. All attempts to obtain a value from null by refinement will produce null.
Any attempt to modify null will disrupt. Any attempt to call null as a function will disrupt.
null is the value of missing input values, missing fields in records, and invalid numbers.
### pi
An approximation of circumference / diameter: 3.1415926535897932.
## Creator Functions
The creator functions are **polymorphic** — they examine the types of their arguments to decide what to do. The first argument's type selects the behavior. All return null if their inputs are not suitable.
### array
The `array` function creates arrays from various inputs. Its behavior depends on the type of the first argument:
**From a number** — create an array of that size:
`array(number)` — All elements are initialized to null.
`array(number, initial_value)` — All elements are initialized to initial_value. If initial_value is a function, it is called for each element. If the function has arity >= 1, it is passed the element number.
```javascript
array(3) // [null, null, null]
array(3, 0) // [0, 0, 0]
array(5, i => i * 2) // [0, 2, 4, 6, 8]
```
**From an array** — copy, map, concat, or slice:
`array(array)` — Copy. Make a mutable copy of the array.
`array(array, function, reverse, exit)` — Map. Call the function with each element, collecting return values in a new array. The function is passed each element and its element number.
```javascript
array([1, 2, 3], x => x * 10) // [10, 20, 30]
```
If reverse is true, starts with the last element and works backwards.
If exit is not null, when the function returns the exit value, array returns early. The exit value is not stored into the new array.
`array(array, another_array)` — Concat. Produce a new array concatenating both.
```javascript
array([1, 2], [3, 4]) // [1, 2, 3, 4]
```
`array(array, from, to)` — Slice. Make a mutable copy of part of an array. If from is negative, add length(array). If to is negative, add length(array).
```javascript
array([1, 2, 3, 4, 5], 1, 4) // [2, 3, 4]
array([1, 2, 3], -2) // [2, 3]
```
**From a record** — get keys:
`array(record)` — Keys. Make an array containing all text keys in the record.
```javascript
array({a: 1, b: 2}) // ["a", "b"]
```
**From text** — split into characters or substrings:
`array(text)` — Split text into an array of individual characters (grapheme clusters). This is the standard way to iterate over characters.
```javascript
array("hello") // ["h", "e", "l", "l", "o"]
array("ƿit") // ["ƿ", "i", "t"]
```
`array(text, separator)` — Split text by a separator string into an array of subtexts.
```javascript
array("a,b,c", ",") // ["a", "b", "c"]
```
`array(text, length)` — Dice text into an array of subtexts of a given length.
### logical
```javascript
logical(0) // false
logical(false) // false
logical("false") // false
logical(null) // false
logical(1) // true
logical(true) // true
logical("true") // true
```
All other values return null.
### number
The `number` function converts values to numbers. Its behavior depends on the type of the first argument:
`number(logical)` — Returns 1 or 0.
`number(number)` — Returns the number.
`number(text, radix)` — Convert text to number. Radix is 2 thru 37 (default: 10).
`number(text, format)` — Parse formatted numbers:
| Format | Radix | Separator | Decimal point |
|--------|-------|-----------|---------------|
| `""` | 10 | | .period |
| `"n"` | 10 | | .period |
| `"u"` | 10 | _underbar | .period |
| `"d"` | 10 | ,comma | .period |
| `"s"` | 10 | space | .period |
| `"v"` | 10 | .period | ,comma |
| `"l"` | 10 | locale | locale |
| `"b"` | 2 | | |
| `"o"` | 8 | | |
| `"h"` | 16 | | |
| `"j"` | auto | | |
```javascript
number("123,456,789.10", "d") // 123456789.1
number("123.456.789,10", "v") // 123456789.1
number("666", "o") // 438
number("666", "h") // 1638
```
### text
The `text` function converts values to text. Its behavior depends on the type of the first argument:
**From an array** — join elements into text:
`text(array, separator)` — Convert array to text. Elements are concatenated with the separator (default: empty text).
```javascript
text(["h", "e", "l", "l", "o"]) // "hello"
text(["a", "b", "c"], ", ") // "a, b, c"
```
**From a number** — format as text:
`text(number, radix)` — Convert number to text. Radix is 2 thru 37 (default: 10).
`text(number, format)` — Format a number as text:
**Real styles:**
| Style | Description | Separator | Decimal |
|-------|-------------|-----------|---------|
| `"e"` | Scientific | | .period |
| `"n"` | Number | | .period |
| `"s"` | Space | space | .period |
| `"u"` | Underbar | _underbar | .period |
| `"d"` | Decimal | ,comma | .period |
| `"c"` | Comma | .period | ,comma |
| `"l"` | Locale | locale | locale |
**Integer styles:**
| Style | Base | Separator |
|-------|------|-----------|
| `"i"` | 10 | _underbar |
| `"b"` | 2 | _underbar |
| `"o"` | 8 | _underbar |
| `"h"` | 16 | _underbar |
| `"t"` | 32 | _underbar |
The format text is: `separation_digit` + `style_letter` + `places_digits`
```javascript
var data = 123456789.1
text(data) // "123456789.1"
text(data, "3s4") // "123 456 789.1000"
text(data, "d2") // "123,456,789.10"
text(data, "e") // "1.234567891e8"
text(data, "i") // "123456789"
text(data, "8b") // "111_01011011_11001101_00010101"
text(data, "h") // "75BCD15"
text(12, "4b8") // "0000_1100"
```
**From text** — extract a substring:
`text(text, from, to)` — Extract a substring. If from/to are negative, add length(text).
```javascript
var my_text = "miskatonic"
text(my_text, 0, 3) // "mis"
text(my_text, 5) // "tonic"
text(my_text, -3) // "nic"
```
### record
The `record` function creates and manipulates records (objects). Its behavior depends on the type of the first argument:
**From a record** — copy, merge, or select:
`record(record)` — Copy. Make a mutable copy.
`record(record, another_record)` — Combine. Copy a record, then put all fields of another into the copy.
`record(record, array_of_keys)` — Select. New record with only the named fields.
**From an array of keys** — create a new record:
`record(array_of_keys)` — Set. New record using array as keys, each value is true.
`record(array_of_keys, value)` — Value Set. Each field value is value.
`record(array_of_keys, function)` — Functional Value Set. The function is called for each key to produce field values.
## Sensory Functions
The sensory functions always return a logical value. In ƿit, they use the `is_` prefix:
```javascript
is_array([]) // true
is_blob(blob.make()) // true
is_function(x => x) // true
is_integer(42) // true
is_logical(true) // true
is_null(null) // true
is_number(3.14) // true
is_object({}) // true
is_text("hello") // true
```
Additional type checks: `is_character`, `is_data`, `is_digit`, `is_false`, `is_fit`, `is_letter`, `is_lower`, `is_pattern`, `is_stone`, `is_true`, `is_upper`, `is_whitespace`.
## Standard Functions
### abs(number)
Absolute value. Returns null for non-numbers.
### apply(function, array)
Execute the function, passing the array elements as input values. If the array is longer than the function's arity, it disrupts.
### ceiling(number, place)
Round up. If place is 0 or null, round to the smallest integer >= number.
```javascript
ceiling(12.3775) // 13
ceiling(12.3775, -2) // 12.38
ceiling(-12.3775) // -12
```
### character(value)
If text, returns the first character. If a non-negative 32-bit integer, returns the character from that codepoint.
### codepoint(text)
Returns the codepoint number of the first character.
### ends_with(text, suffix)
Returns `true` if the text ends with the given suffix.
```javascript
ends_with("hello.ce", ".ce") // true
ends_with("hello.cm", ".ce") // false
```
### every(array, function)
Returns `true` if every element satisfies the predicate.
```javascript
every([2, 4, 6], x => x % 2 == 0) // true
every([2, 3, 6], x => x % 2 == 0) // false
```
### extract(text, pattern, from, to)
Match text to pattern. Returns a record of saved fields, or null if no match.
### fallback(requestor_array)
Returns a requestor that tries each requestor in order until one succeeds. Returns a cancel function. See [Requestors](/docs/requestors/) for usage.
### filter(array, function)
Call function for each element. When it returns true, the element is included in the new array.
```javascript
filter([0, 1.25, 2, 3.5, 4, 5.75], is_integer) // [0, 2, 4]
```
### find(array, function, reverse, from)
Call function for each element. If it returns true, return the element number. If the second argument is not a function, it is compared directly to elements. Returns null if not found.
```javascript
find([1, 2, 3], x => x > 1) // 1
find([1, 2, 3], 2) // 1
```
### floor(number, place)
Round down. If place is 0 or null, round to the greatest integer <= number.
```javascript
floor(12.3775) // 12
floor(12.3775, -2) // 12.37
floor(-12.3775) // -13
```
### for(array, function, reverse, exit)
Call function with each element and its element number. If exit is not null and the function returns the exit value, return early.
### format(text, collection, transformer)
Substitute `{key}` placeholders in text with values from a collection (array or record).
```javascript
format("{0} in {1}!", ["Malmborg", "Plano"])
// "Malmborg in Plano!"
```
### fraction(number)
Returns the fractional part of a number. See also `whole`.
### length(value)
| Value | Result |
|-------|--------|
| array | number of elements |
| blob | number of bits |
| text | number of codepoints |
| function | number of named inputs (arity) |
| record | record.length() |
| other | null |
### lower(text)
Returns text with all uppercase characters converted to lowercase.
### max(number, number)
Returns the larger of two numbers. Returns null if either is not a number.
### min(number, number)
Returns the smaller of two numbers.
### modulo(dividend, divisor)
Result is `dividend - (divisor * floor(dividend / divisor))`. Result has the sign of the divisor.
### neg(number)
Negate. Reverse the sign of a number.
### normalize(text)
Unicode normalize.
### not(logical)
Returns the opposite logical. Returns null for non-logicals.
### parallel(requestor_array, throttle, need)
Returns a requestor that starts all requestors in the array. Results are collected into an array matching the input order. Optional throttle limits concurrent requestors. Optional need specifies the minimum number of successes required. See [Requestors](/docs/requestors/) for usage.
### print(value)
Print a value to standard output.
```javascript
print("hello")
print(42)
print(`result: ${x}`)
```
### race(requestor_array, throttle, need)
Like parallel, but returns as soon as the needed number of results are obtained. Default need is 1. Unfinished requestors are cancelled. See [Requestors](/docs/requestors/) for usage.
### reduce(array, function, initial, reverse)
Reduce an array to a single value by applying a function to pairs of elements.
```javascript
reduce([1, 2, 3, 4, 5, 6, 7, 8, 9], (a, b) => a + b) // 45
```
### remainder(dividend, divisor)
For fit integers: `dividend - ((dividend // divisor) * divisor)`.
### replace(text, target, replacement, limit)
Return text with target replaced by replacement. Target can be text or pattern. Replacement can be text or a function.
### reverse(array)
Returns a new array with elements in the opposite order.
### round(number, place)
Round to nearest.
```javascript
round(12.3775) // 12
round(12.3775, -2) // 12.38
```
### search(text, target, from)
Search text for target. Returns character position or null.
### sequence(requestor_array)
Returns a requestor that processes each requestor in order. Each result becomes the input to the next. The last result is the final result. See [Requestors](/docs/requestors/) for usage.
### sign(number)
Returns -1, 0, or 1.
### some(array, function)
Returns `true` if any element satisfies the predicate. Stops at the first match.
```javascript
some([1, 2, 3], x => x > 2) // true
some([1, 2, 3], x => x > 5) // false
```
### sort(array, select)
Returns a new sorted array. Sort keys must be all numbers or all texts. Sort is ascending and stable.
| select type | Sort key | Description |
|-------------|----------|-------------|
| null | element itself | Simple sort |
| text | element[select] | Sort by record field |
| number | element[select] | Sort by array index |
| array | select[index] | External sort keys |
```javascript
sort(["oats", "peas", "beans", "barley"])
// ["barley", "beans", "oats", "peas"]
sort([{n: 3}, {n: 1}], "n")
// [{n: 1}, {n: 3}]
```
### starts_with(text, prefix)
Returns `true` if the text starts with the given prefix.
```javascript
starts_with("hello world", "hello") // true
starts_with("hello world", "world") // false
```
### stone(value)
Petrify the value, making it permanently immutable. The operation is deep — all nested objects and arrays are also frozen. Returns the value.
### trim(text, reject)
Remove characters from both ends. Default removes whitespace.
### trunc(number, place)
Truncate toward zero.
```javascript
trunc(12.3775) // 12
trunc(-12.3775) // -12
```
### upper(text)
Returns text with all lowercase characters converted to uppercase.
### whole(number)
Returns the whole part of a number. See also `fraction`.

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 JavaScript-like syntax with the actor model of computation.
## 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
- [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 `do` 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
@@ -46,98 +51,98 @@ number("0xff", "j") // 255
## Methods
### number.abs(n)
### abs(n)
Absolute value.
```javascript
number.abs(-5) // 5
number.abs(5) // 5
abs(-5) // 5
abs(5) // 5
```
### number.sign(n)
### sign(n)
Returns -1, 0, or 1.
```javascript
number.sign(-5) // -1
number.sign(0) // 0
number.sign(5) // 1
sign(-5) // -1
sign(0) // 0
sign(5) // 1
```
### number.floor(n, place)
### floor(n, place)
Round down.
```javascript
number.floor(4.9) // 4
number.floor(4.567, 2) // 4.56
floor(4.9) // 4
floor(4.567, 2) // 4.56
```
### number.ceiling(n, place)
### ceiling(n, place)
Round up.
```javascript
number.ceiling(4.1) // 5
number.ceiling(4.123, 2) // 4.13
ceiling(4.1) // 5
ceiling(4.123, 2) // 4.13
```
### number.round(n, place)
### round(n, place)
Round to nearest.
```javascript
number.round(4.5) // 5
number.round(4.567, 2) // 4.57
round(4.5) // 5
round(4.567, 2) // 4.57
```
### number.trunc(n, place)
### trunc(n, place)
Truncate toward zero.
```javascript
number.trunc(4.9) // 4
number.trunc(-4.9) // -4
trunc(4.9) // 4
trunc(-4.9) // -4
```
### number.whole(n)
### whole(n)
Get the integer part.
```javascript
number.whole(4.9) // 4
number.whole(-4.9) // -4
whole(4.9) // 4
whole(-4.9) // -4
```
### number.fraction(n)
### fraction(n)
Get the fractional part.
```javascript
number.fraction(4.75) // 0.75
fraction(4.75) // 0.75
```
### number.min(...values)
### min(a, b)
Return the smallest value.
Return the smaller of two numbers.
```javascript
number.min(3, 1, 4, 1, 5) // 1
min(3, 5) // 3
```
### number.max(...values)
### max(a, b)
Return the largest value.
Return the larger of two numbers.
```javascript
number.max(3, 1, 4, 1, 5) // 5
max(3, 5) // 5
```
### number.remainder(dividend, divisor)
### remainder(dividend, divisor)
Compute remainder.
```javascript
number.remainder(17, 5) // 2
remainder(17, 5) // 2
```

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
@@ -70,18 +82,18 @@ text.search("hello world", "xyz") // null
text.search("hello hello", "hello", 1) // 6
```
### text.replace(text, target, replacement, limit)
### text.replace(text, target, replacement, cap)
Replace occurrences of `target` with `replacement`.
Replace occurrences of `target` with `replacement`. If `cap` is not specified, replaces all occurrences.
```javascript
text.replace("hello", "l", "L") // "heLLo"
text.replace("hello", "l", "L", 1) // "heLlo"
text.replace("hello", "l", "L") // "heLLo" (replaces all)
text.replace("hello", "l", "L", 1) // "heLlo" (replaces first only)
// With function
text.replace("hello", "l", function(match, pos) {
return pos == 2 ? "L" : match
}) // "heLlo"
}) // "heLLo" (replaces all by default)
```
### text.format(text, collection, transformer)
@@ -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")}`)
```

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]})
```

296
docs/spec/c-runtime.md Normal file
View File

@@ -0,0 +1,296 @@
---
title: "C Runtime for Native Code"
description: "Minimum C runtime surface for QBE-generated native code"
---
## Overview
QBE-generated native code calls into a C runtime for anything that touches the heap, dispatches dynamically, or requires GC awareness. The design principle: **native code handles control flow and integer math directly; everything else is a runtime call.**
This document defines the runtime boundary — what must be in C, what QBE handles inline, and how to organize the C code to serve both the mcode interpreter and native code cleanly.
## The Boundary
### What native code does inline (no C calls)
These operations compile to straight QBE instructions with no runtime involvement:
- **Integer arithmetic**: `add`, `sub`, `mul` on NaN-boxed ints (shift right 1, operate, shift left 1)
- **Integer comparisons**: extract int with shift, compare, produce tagged bool
- **Control flow**: jumps, branches, labels, function entry/exit
- **Slot access**: load/store to frame slots via `%fp` + offset
- **NaN-box tagging**: integer tagging (`n << 1`), bool constants (`0x03`/`0x23`), null (`0x07`)
- **Type tests**: `JS_IsInt` (LSB check), `JS_IsNumber`, `JS_IsText`, `JS_IsNull` — these are bit tests on the value, no heap access needed
### What requires a C call
Anything that:
1. **Allocates** (arrays, records, strings, frames, function objects)
2. **Touches the heap** (property get/set, array indexing, closure access)
3. **Dispatches on type at runtime** (dynamic load/store, polymorphic arithmetic)
4. **Calls user functions** (frame setup, argument passing, invocation)
5. **Does string operations** (concatenation, comparison, conversion)
## Runtime Functions
### Tier 1: Essential (must exist for any program to run)
These are called by virtually every QBE program.
#### Intrinsic Lookup
```c
// Look up a built-in function by name. Called once per intrinsic per callsite.
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name);
```
Maps name → C function pointer wrapped in JSValue. This is the primary entry point for all built-in functions (`print`, `text`, `length`, `is_array`, etc). The native code never calls intrinsics directly — it always goes through `get_intrinsic``frame``invoke`.
#### Function Calls
```c
// Allocate a call frame with space for nr_args arguments.
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int nr_args);
// Set argument idx in the frame.
void cell_rt_setarg(JSValue frame, int idx, JSValue val);
// Execute the function. Returns the result.
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame);
```
This is the universal calling convention. Every function call — user functions, intrinsics, methods — goes through frame/setarg/invoke. The frame allocates a `JSFrameRegister` on the GC heap, setarg fills slots, invoke dispatches.
**Tail call variants:**
```c
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int nr_args);
void cell_rt_goinvoke(JSContext *ctx, JSValue frame);
```
Same as frame/invoke but reuse the caller's stack position.
### Tier 2: Property Access (needed by any program using records or arrays)
```c
// Record field by constant name.
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name);
void cell_rt_store_field(JSContext *ctx, JSValue obj, JSValue val, const char *name);
// Array element by integer index.
JSValue cell_rt_load_index(JSContext *ctx, JSValue obj, JSValue idx);
void cell_rt_store_index(JSContext *ctx, JSValue obj, JSValue idx, JSValue val);
// Dynamic — type of key unknown at compile time.
JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key);
void cell_rt_store_dynamic(JSContext *ctx, JSValue obj, JSValue key, JSValue val);
```
The typed variants (`load_field`/`load_index`) skip the key-type dispatch that `load_dynamic` must do. When parse and fold provide type information, QBE emit selects the typed variant and the streamline optimizer can narrow dynamic → typed.
**Implementation**: These are thin wrappers around existing `JS_GetPropertyStr`/`JS_GetPropertyNumber`/`JS_GetProperty` and their `Set` counterparts.
### Tier 3: Closures (needed by programs with nested functions)
```c
// Walk depth levels up the frame chain, read slot.
JSValue cell_rt_get_closure(JSContext *ctx, JSValue fp, int depth, int slot);
// Walk depth levels up, write slot.
void cell_rt_put_closure(JSContext *ctx, JSValue fp, JSValue val, int depth, int slot);
```
Closure variables live in outer frames. `depth` is how many `caller` links to follow; `slot` is the register index in that frame.
### Tier 4: Object Construction (needed by programs creating arrays/records/functions)
```c
// Create a function object from a compiled function index.
// The native code loader must maintain a function table.
JSValue cell_rt_make_function(JSContext *ctx, int fn_id);
```
Array and record literals are currently compiled as intrinsic calls (`array(...)`, direct `{...}` construction) which go through the frame/invoke path. A future optimization could add:
```c
// Fast paths (optional, not yet needed)
JSValue cell_rt_new_array(JSContext *ctx, int len);
JSValue cell_rt_new_record(JSContext *ctx);
```
### Tier 5: Collection Operations
```c
// a[] = val (push) and var v = a[] (pop)
void cell_rt_push(JSContext *ctx, JSValue arr, JSValue val);
JSValue cell_rt_pop(JSContext *ctx, JSValue arr);
```
### Tier 6: Error Handling
```c
// Trigger disruption. Jumps to the disrupt handler or unwinds.
void cell_rt_disrupt(JSContext *ctx);
```
### Tier 7: Miscellaneous
```c
JSValue cell_rt_delete(JSContext *ctx, JSValue obj, JSValue key);
JSValue cell_rt_typeof(JSContext *ctx, JSValue val);
```
### Tier 8: String and Float Helpers (called from QBE inline code, not from qbe_emit)
These are called from the QBE IL that `qbe.cm` generates inline for arithmetic and comparison operations. They're not `cell_rt_` prefixed — they're lower-level:
```c
// Float arithmetic (when operands aren't both ints)
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_float_neg(JSContext *ctx, JSValue v);
JSValue qbe_float_inc(JSContext *ctx, JSValue v);
JSValue qbe_float_dec(JSContext *ctx, JSValue v);
// Float comparison (returns C int 0/1 for QBE branching)
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b);
// Bitwise ops on non-int values (convert to int32 first)
JSValue qbe_bnot(JSContext *ctx, JSValue v);
JSValue qbe_bitwise_and(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_bitwise_or(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b);
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b);
// String operations
JSValue JS_ConcatString(JSContext *ctx, JSValue a, JSValue b);
int js_string_compare_value(JSContext *ctx, JSValue a, JSValue b, int eq_only);
JSValue JS_NewString(JSContext *ctx, const char *str);
JSValue __JS_NewFloat64(JSContext *ctx, double d);
int JS_ToBool(JSContext *ctx, JSValue v);
// String/number type tests (inline-able but currently calls)
int JS_IsText(JSValue v);
int JS_IsNumber(JSValue v);
// Tolerant equality (== on mixed types)
JSValue cell_rt_eq_tol(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b);
// Text ordering comparisons
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b);
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b);
```
## What Exists vs What Needs Writing
### Already exists (in qbe_helpers.c)
All `qbe_float_*`, `qbe_bnot`, `qbe_bitwise_*`, `qbe_shift_*`, `qbe_to_bool` — these are implemented and working.
### Already exists (in runtime.c / quickjs.c) but not yet wrapped
The underlying operations exist but aren't exposed with the `cell_rt_` names:
| Runtime function | Underlying implementation |
|---|---|
| `cell_rt_load_field` | `JS_GetPropertyStr(ctx, obj, name)` |
| `cell_rt_load_index` | `JS_GetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx))` |
| `cell_rt_load_dynamic` | `JS_GetProperty(ctx, obj, key)` |
| `cell_rt_store_field` | `JS_SetPropertyStr(ctx, obj, name, val)` |
| `cell_rt_store_index` | `JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx), val)` |
| `cell_rt_store_dynamic` | `JS_SetProperty(ctx, obj, key, val)` |
| `cell_rt_delete` | `JS_DeleteProperty(ctx, obj, key)` |
| `cell_rt_push` | `JS_ArrayPush(ctx, &arr, val)` |
| `cell_rt_pop` | `JS_ArrayPop(ctx, arr)` |
| `cell_rt_typeof` | type tag switch → `JS_NewString` |
| `cell_rt_disrupt` | `JS_Throw(ctx, ...)` |
| `cell_rt_eq_tol` / `cell_rt_ne_tol` | comparison logic in mcode.c `eq_tol`/`ne_tol` handler |
| `cell_rt_lt_text` etc. | `js_string_compare_value` + wrap result |
### Needs new code
| Runtime function | What's needed |
|---|---|
| `cell_rt_get_intrinsic` | Look up intrinsic by name string, return JSValue function. Currently scattered across `js_cell_intrinsic_get` and the mcode handler. Needs a clean single entry point. |
| `cell_rt_frame` | Allocate `JSFrameRegister`, set function slot, set argc. Exists in mcode.c `frame` handler but not as a callable function. |
| `cell_rt_setarg` | Write to frame slot. Trivial: `frame->slots[idx + 1] = val` (slot 0 is `this`). |
| `cell_rt_invoke` | Call the function in the frame. Needs to dispatch: native C function vs mach bytecode vs mcode. This is the critical piece — it must handle all function types. |
| `cell_rt_goframe` / `cell_rt_goinvoke` | Tail call variants. Similar to frame/invoke but reuse caller frame. |
| `cell_rt_make_function` | Create function object from index. Needs a function table (populated by the native loader). |
| `cell_rt_get_closure` / `cell_rt_put_closure` | Walk frame chain. Exists inline in mcode.c `get`/`put` handlers. |
## Recommended C File Organization
```
source/
cell_runtime.c — NEW: all cell_rt_* functions (the native code API)
qbe_helpers.c — existing: float/bitwise/shift helpers for inline QBE
runtime.c — existing: JS_GetProperty, JS_SetProperty, etc.
quickjs.c — existing: core VM, GC, value representation
mcode.c — existing: mcode interpreter (can delegate to cell_runtime.c)
```
**`cell_runtime.c`** is the single file that defines the native code contract. It should:
1. Include `quickjs-internal.h` for access to value representation and heap types
2. Export all `cell_rt_*` functions with C linkage (no `static`)
3. Keep each function thin — delegate to existing `JS_*` functions where possible
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved
### Implementation Priority
**Phase 1** — Get "hello world" running natively:
- `cell_rt_get_intrinsic` (to find `print` and `text`)
- `cell_rt_frame`, `cell_rt_setarg`, `cell_rt_invoke` (to call them)
- A loader that takes QBE output → assembles → links → calls `cell_main`
**Phase 2** — Variables and arithmetic:
- All property access (`load_field`, `load_index`, `store_*`, `load_dynamic`)
- `cell_rt_make_function`, `cell_rt_get_closure`, `cell_rt_put_closure`
**Phase 3** — Full language:
- `cell_rt_push`, `cell_rt_pop`, `cell_rt_delete`, `cell_rt_typeof`
- `cell_rt_disrupt`
- `cell_rt_goframe`, `cell_rt_goinvoke`
- Text comparison wrappers (`cell_rt_lt_text`, etc.)
- Tolerant equality (`cell_rt_eq_tol`, `cell_rt_ne_tol`)
## Calling Convention
All `cell_rt_*` functions follow the same pattern:
- First argument is always `JSContext *ctx`
- Values are passed/returned as `JSValue` (64-bit, by value)
- Frame pointers are `JSValue` (tagged pointer to `JSFrameRegister`)
- String names are `const char *` (pointer to data section label)
- Integer constants (slot indices, arg counts) are `int` / `long`
Native code maintains `%ctx` (JSContext) and `%fp` (current frame pointer) as persistent values across the function body. All slot reads/writes go through `%fp` + offset.
## What Should NOT Be in the C Runtime
These are handled entirely by QBE-generated code:
- **Integer arithmetic and comparisons** — bit operations on NaN-boxed values
- **Control flow** — branches, loops, labels, jumps
- **Boolean logic** — `and`/`or`/`not` on tagged values
- **Constant loading** — integer constants are immediate, strings are data labels
- **Type guard branches** — the `is_int`/`is_text`/`is_null` checks are inline bit tests; the branch to the float or text path is just a QBE `jnz`
The `qbe.cm` macros already handle all of this. The arithmetic path looks like:
```
check both ints? → yes → inline int add → done
→ no → call qbe_float_add (or JS_ConcatString for text)
```
The C runtime is only called on the slow paths (float, text, dynamic dispatch). The fast path (integer arithmetic, comparisons, branching) is fully native.

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

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

@@ -0,0 +1,165 @@
---
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.
### Constant Pool Index Overflow
Named property instructions (`LOAD_FIELD`, `STORE_FIELD`, `DELETE`) use the iABC format where the constant pool key index occupies an 8-bit field (max 255). When a function references more than 256 unique property names, the serializer automatically falls back to a two-instruction sequence:
1. `LOADK tmp, key_index` — load the key string into a temporary register (iABx, 16-bit index)
2. `LOAD_DYNAMIC` / `STORE_DYNAMIC` / `DELETEINDEX` — use the register-based variant
This is transparent to the mcode compiler and streamline optimizer.

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

@@ -0,0 +1,143 @@
---
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) → Fold → Mcode (JSON) → Streamline → Mach VM (default)
→ Mcode Interpreter
→ QBE → Native
```
Mcode is produced by `mcode.cm`, which lowers the folded AST to JSON instruction arrays. The streamline optimizer (`streamline.cm`) then eliminates redundant operations. The result is serialized to binary bytecode by the Mach compiler (`mach.c`), interpreted directly by `mcode.c`, or lowered to QBE IL by `qbe_emit.cm` for native compilation. See [Compilation Pipeline](pipeline.md) for the full overview.
### Function Proxy Decomposition
When the compiler encounters a method call `obj.method(args)`, it emits a branching pattern to handle ƿit's function proxy protocol. An arity-2 function used as a proxy target receives the method name and argument array instead of a normal method call:
```json
["is_proxy", check, obj]
["jump_false", check, "record_path"]
// Proxy path: call obj(name, [args...]) with this=null
["access", name_slot, "method"]
["array", args_arr, N, arg0, arg1, ...]
["null", null_slot]
["frame", f, obj, 2]
["setarg", f, 0, null_slot]
["setarg", f, 1, name_slot]
["setarg", f, 2, args_arr]
["invoke", f, dest]
["jump", "done"]
["LABEL", "record_path"]
["load_field", method, obj, "method"]
["frame", f2, method, N]
["setarg", f2, 0, obj]
["setarg", f2, 1, arg0]
...
["invoke", f2, dest]
["LABEL", "done"]
```
The streamline optimizer can eliminate the dead branch when the type of `obj` is statically known.
## 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 (typically `[op, dest, ...args, line, col]`):
```json
["access", 3, 5, 1, 9]
["load_index", 10, 4, 9, 5, 11]
["store_dynamic", 4, 11, 12, 6, 3]
["frame", 15, 14, 1, 7, 7]
["setarg", 15, 0, 16, 7, 7]
["invoke", 15, 13, 7, 7]
```
### Typed Load/Store
Memory operations come in typed variants for optimization:
- `load_index dest, obj, idx` — array element by integer index
- `load_field dest, obj, key` — record property by string key
- `load_dynamic dest, obj, key` — unknown; dispatches at runtime
- `store_index obj, val, idx` — array element store
- `store_field obj, val, key` — record property store
- `store_dynamic obj, val, key` — unknown; dispatches at runtime
The compiler selects the appropriate variant based on `type_tag` and `access_kind` annotations from parse and fold.
### Decomposed Calls
Function calls are split into separate instructions:
- `frame dest, fn, argc` — allocate call frame
- `setarg frame, idx, val` — set argument
- `invoke frame, result` — execute the call
## 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` |

124
docs/spec/pipeline.md Normal file
View File

@@ -0,0 +1,124 @@
---
title: "Compilation Pipeline"
description: "Overview of the compilation stages and optimizations"
---
## Overview
The compilation pipeline transforms source code through several stages, each adding information or lowering the representation toward execution. All backends share the same path through mcode and streamline. There are three execution backends: the Mach register VM (default), the Mcode interpreter (debug), and native code via QBE (experimental).
```
Source → Tokenize → Parse → Fold → Mcode → Streamline → Mach VM (default)
→ Mcode Interpreter
→ QBE → Native
```
## Stages
### Tokenize (`tokenize.cm`)
Splits source text into tokens. Handles string interpolation by re-tokenizing template literal contents. Produces a token array with position information (line, column).
### Parse (`parse.cm`)
Converts tokens into an AST. Also performs semantic analysis:
- **Scope records**: For each scope (global, function), builds a record mapping variable names to their metadata: `make` (var/def/function/input), `function_nr`, `nr_uses`, `closure` flag, and `level`.
- **Type tags**: When the right-hand side of a `def` is a syntactically obvious type, stamps `type_tag` on the scope record entry. Derivable types: `"integer"`, `"number"`, `"text"`, `"array"`, `"record"`, `"function"`, `"logical"`, `"null"`.
- **Intrinsic resolution**: Names used but not locally bound are recorded in `ast.intrinsics`. Name nodes referencing intrinsics get `intrinsic: true`.
- **Access kind**: Subscript (`[`) nodes get `access_kind`: `"index"` for numeric subscripts, `"field"` for string subscripts, omitted otherwise.
- **Tail position**: Return statements where the expression is a call get `tail: true`.
### Fold (`fold.cm`)
Operates on the AST. Performs constant folding and type analysis:
- **Constant folding**: Evaluates arithmetic on known constants at compile time (e.g., `5 + 10` becomes `15`).
- **Constant propagation**: Tracks `def` bindings whose values are known constants.
- **Type propagation**: Extends `type_tag` through operations. When both operands of an arithmetic op have known types, the result type is known. Propagates type tags to reference sites.
- **Intrinsic specialization**: When an intrinsic call's argument types are known, stamps a `hint` on the call node. For example, `length(x)` where x is a known array gets `hint: "array_length"`. Type checks like `is_array(known_array)` are folded to `true`.
- **Purity marking**: Stamps `pure: true` on expressions with no side effects (literals, name references, arithmetic on pure operands).
- **Dead code elimination**: Removes unreachable branches when conditions are known constants.
### Mcode (`mcode.cm`)
Lowers the AST to a JSON-based intermediate representation with explicit operations. Key design principle: **every type check is an explicit instruction** so downstream optimizers can see and eliminate them.
- **Typed load/store**: Emits `load_index` (array by integer), `load_field` (record by string), or `load_dynamic` (unknown) based on type information from fold.
- **Decomposed calls**: Function calls are split into `frame` (create call frame) + `setarg` (set arguments) + `invoke` (execute call).
- **Intrinsic access**: Intrinsic functions are loaded via `access` with an intrinsic marker rather than global lookup.
See [Mcode IR](mcode.md) for instruction format details.
### Streamline (`streamline.cm`)
Optimizes the Mcode IR. Operates per-function:
- **Redundant instruction elimination**: Removes no-op patterns and redundant moves.
- **Dead code removal**: Eliminates instructions whose results are never used.
- **Type-based narrowing**: When type information is available, narrows `load_dynamic`/`store_dynamic` to typed variants.
### QBE Emit (`qbe_emit.cm`)
Lowers optimized Mcode IR to QBE intermediate language for native code compilation. Each Mcode function becomes a QBE function that calls into the cell runtime (`cell_rt_*` functions) for operations that require the runtime (allocation, intrinsic dispatch, etc.).
String constants are interned in a data section. Integer constants are NaN-boxed inline.
### QBE Macros (`qbe.cm`)
Provides operation implementations as QBE IL templates. Each arithmetic, comparison, and type operation is defined as a function that emits the corresponding QBE instructions, handling type dispatch (integer, float, text paths) with proper guard checks.
## Execution Backends
### Mach VM (default)
Binary 32-bit register VM. The Mach serializer (`mach.c`) converts streamlined mcode JSON into compact 32-bit bytecode with a constant pool. Used for production execution and bootstrapping.
```
./cell script.ce
```
Debug the mach bytecode output:
```
./cell --core . --dump-mach script.ce
```
### Mcode Interpreter
JSON-based interpreter. Used for debugging the compilation pipeline.
```
./cell --mcode script.ce
```
### QBE Native (experimental)
Generates QBE IL that can be compiled to native code.
```
./cell --emit-qbe script.ce > output.ssa
```
## Files
| File | Role |
|------|------|
| `tokenize.cm` | Lexer |
| `parse.cm` | Parser + semantic analysis |
| `fold.cm` | Constant folding + type analysis |
| `mcode.cm` | AST → Mcode IR lowering |
| `streamline.cm` | Mcode IR optimizer |
| `qbe_emit.cm` | Mcode IR → QBE IL emitter |
| `qbe.cm` | QBE IL operation templates |
| `internal/bootstrap.cm` | Pipeline orchestrator |
## Test Files
| File | Tests |
|------|-------|
| `parse_test.ce` | Type tags, access_kind, intrinsic resolution |
| `fold_test.ce` | Type propagation, purity, intrinsic hints |
| `mcode_test.ce` | Typed load/store, decomposed calls |
| `streamline_test.ce` | Optimization counts, IR before/after |
| `qbe_test.ce` | End-to-end QBE IL generation |

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
```

20
dump_mcode.cm Normal file
View File

@@ -0,0 +1,20 @@
var fd = use("fd")
var json = use("json")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var name = args[0]
var src = text(fd.slurp(name))
var tok = tokenize(src, name)
var ast = parse(tok.tokens, src, name, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
var out = json.encode(optimized)
var f = fd.open("/tmp/mcode_dump.json", "w")
fd.write(f, out)
fd.close(f)
print("wrote /tmp/mcode_dump.json")

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"]
}

View File

@@ -1,234 +0,0 @@
// HTTP Download Actor
// Handles download requests and progress queries
var http = use('http');
var os = use('os');
// Actor state
var state = {
downloading: false,
current_url: null,
total_bytes: 0,
downloaded_bytes: 0,
start_time: 0,
error: null,
connection: null,
download_msg: null,
chunks: []
};
// Helper to calculate progress percentage
function get_progress() {
if (state.total_bytes == 0) {
return 0;
}
return number.round((state.downloaded_bytes / state.total_bytes) * 100);
}
// Helper to format status response
function get_status() {
if (!state.downloading) {
return {
status: 'idle',
error: state.error
};
}
var elapsed = os.now() - state.start_time;
var bytes_per_sec = elapsed > 0 ? state.downloaded_bytes / elapsed : 0;
return {
status: 'downloading',
url: state.current_url,
progress: get_progress(),
downloaded_bytes: state.downloaded_bytes,
total_bytes: state.total_bytes,
elapsed_seconds: elapsed,
bytes_per_second: number.round(bytes_per_sec)
};
}
// Main message receiver
$receiver(function(msg) {
switch (msg.type) {
case 'download':
if (state.downloading) {
send(msg, {
type: 'error',
error: 'Already downloading',
current_url: state.current_url
});
return;
}
if (!msg.url) {
send(msg, {
type: 'error',
error: 'No URL provided'
});
return;
}
// Start download
state.downloading = true;
state.current_url = msg.url;
state.total_bytes = 0;
state.downloaded_bytes = 0;
state.start_time = os.now();
state.error = null;
state.download_msg = msg;
state.chunks = [];
try {
// Start the connection
state.connection = http.fetch_start(msg.url, msg.options || {});
if (!state.connection) {
throw new Error('Failed to start download');
}
// Schedule the first chunk read
$delay(read_next_chunk, 0);
} catch (e) {
state.error = e.toString();
state.downloading = false;
send(msg, {
type: 'error',
error: state.error,
url: msg.url
});
}
break;
case 'status':
log.console(`got status request. current is ${get_status()}`)
send(msg, {
type: 'status_response',
...get_status()
});
break;
case 'cancel':
if (state.downloading) {
// Cancel the download
if (state.connection) {
http.fetch_close(state.connection);
state.connection = null;
}
state.downloading = false;
state.current_url = null;
state.download_msg = null;
state.chunks = [];
send(msg, {
type: 'cancelled',
message: 'Download cancelled',
url: state.current_url
});
} else {
send(msg, {
type: 'error',
error: 'No download in progress'
});
}
break;
default:
send(msg, {
type: 'error',
error: 'Unknown message type: ' + msg.type
});
}
});
// Non-blocking chunk reader
function read_next_chunk() {
if (!state.downloading || !state.connection) {
return;
}
try {
var chunk = http.fetch_read_chunk(state.connection);
if (chunk == null) {
// Download complete
finish_download();
return;
}
// Store chunk
state.chunks.push(chunk);
// Update progress
var info = http.fetch_info(state.connection);
state.downloaded_bytes = info.bytes_read;
if (info.headers_complete && info.content_length > 0) {
state.total_bytes = info.content_length;
}
// Schedule next chunk read
$delay(read_next_chunk, 0);
} catch (e) {
// Error during download
state.error = e.toString();
if (state.connection) {
http.fetch_close(state.connection);
}
if (state.download_msg) {
send(state.download_msg, {
type: 'error',
error: state.error,
url: state.current_url
});
}
// Reset state
state.downloading = false;
state.connection = null;
state.download_msg = null;
state.chunks = [];
}
}
// Complete the download and send result
function finish_download() {
if (state.connection) {
http.fetch_close(state.connection);
}
// Combine all chunks into single ArrayBuffer
var total_size = 0;
for (var i = 0; i < state.chunks.length; i++) {
total_size += state.chunks[i].byteLength;
}
var result = new ArrayBuffer(total_size);
var view = new Uint8Array(result);
var offset = 0;
for (var i = 0; i < state.chunks.length; i++) {
var chunk_view = new Uint8Array(state.chunks[i]);
view.set(chunk_view, offset);
offset += state.chunks[i].byteLength;
}
// Send complete message
if (state.download_msg) {
send(state.download_msg, {
type: 'complete',
url: state.current_url,
data: result,
size: result.byteLength,
duration: os.now() - state.start_time
});
}
// Reset state
state.downloading = false;
state.connection = null;
state.current_url = null;
state.download_msg = null;
state.chunks = [];
}

View File

@@ -10,7 +10,7 @@ var match_id = 0;
$portal(e => {
log.console("NAT server: received connection request");
if (!isa(e.actor, actor))
if (!is_actor(e.actor))
send(e, {reason: "Must provide the actor you want to connect."});
if (waiting_client) {

93
fash.c Normal file
View File

@@ -0,0 +1,93 @@
/*
Fash64: Douglas Crockford (2017-02-02)
64-bit hash that uses the high 64 bits of a 128-bit product for feedback.
Notes:
- Requires a way to get the high half of a 64x64->128 multiply.
- Uses __uint128_t when available; otherwise uses MSVC _umul128.
*/
#include <stdint.h>
#include <stddef.h>
typedef struct fash64_state {
uint64_t result;
uint64_t sum;
} fash64_state;
enum {
FASH64_PRIME_11 = 11111111111111111027ull,
FASH64_PRIME_8 = 8888888888888888881ull,
FASH64_PRIME_3 = 3333333333333333271ull
};
static inline void fash64_mul_hi_lo(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo)
{
#if defined(__SIZEOF_INT128__)
__uint128_t p = (__uint128_t)a * (__uint128_t)b;
*lo = (uint64_t)p;
*hi = (uint64_t)(p >> 64);
#elif defined(_MSC_VER) && defined(_M_X64)
*lo = _umul128(a, b, hi);
#else
/* Portable fallback (no 128-bit type, no _umul128). */
uint64_t a0 = (uint32_t)a;
uint64_t a1 = a >> 32;
uint64_t b0 = (uint32_t)b;
uint64_t b1 = b >> 32;
uint64_t p00 = a0 * b0;
uint64_t p01 = a0 * b1;
uint64_t p10 = a1 * b0;
uint64_t p11 = a1 * b1;
uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10;
*lo = (p00 & 0xffffffffull) | (mid << 32);
*hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
#endif
}
static inline void fash64_begin(fash64_state *s)
{
s->result = (uint64_t)FASH64_PRIME_8;
s->sum = (uint64_t)FASH64_PRIME_3;
}
static inline void fash64_word(fash64_state *s, uint64_t word)
{
uint64_t high, low;
uint64_t mixed = s->result ^ word;
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
s->sum += high;
s->result = low ^ s->sum;
}
static inline void fash64_block(fash64_state *s, const uint64_t *block, size_t word_count)
{
for (size_t i = 0; i < word_count; i++) fash64_word(s, block[i]);
}
static inline uint64_t fash64_end(const fash64_state *s)
{
return s->result;
}
/* Convenience one-shot helper */
static inline uint64_t fash64_hash_words(const uint64_t *words, size_t word_count, uint64_t extra_word)
{
fash64_state s;
fash64_begin(&s);
fash64_block(&s, words, word_count);
fash64_word(&s, extra_word);
return fash64_end(&s);
}
static inline uint64_t fash64_hash_one(uint64_t word)
{
uint64_t high, low;
uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word;
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
return low ^ ((uint64_t)FASH64_PRIME_3 + high);
}

0
fash.h Normal file
View File

53
fd.c
View File

@@ -50,7 +50,7 @@ JSC_SCALL(fd_open,
mode_t mode = 0644;
// Parse optional flags argument
if (argc > 1 && JS_IsString(argv[1])) {
if (argc > 1 && JS_IsText(argv[1])) {
const char *flag_str = JS_ToCString(js, argv[1]);
flags = 0;
@@ -78,7 +78,7 @@ JSC_CCALL(fd_write,
size_t len;
ssize_t wrote;
if (JS_IsString(argv[1])) {
if (JS_IsText(argv[1])) {
const char *data = JS_ToCStringLen(js, &len, argv[1]);
if (!data) return JS_EXCEPTION;
wrote = write(fd, data, len);
@@ -276,7 +276,7 @@ JSC_SCALL(fd_mkdir,
JSC_SCALL(fd_mv,
if (argc < 2)
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
else if (!JS_IsString(argv[1]))
else if (!JS_IsText(argv[1]))
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
else {
const char *new_path = JS_ToCString(js, argv[1]);
@@ -289,7 +289,7 @@ JSC_SCALL(fd_mv,
JSC_SCALL(fd_symlink,
if (argc < 2)
ret = JS_ThrowTypeError(js, "fd.symlink requires 2 arguments: target and link path");
else if (!JS_IsString(argv[1]))
else if (!JS_IsText(argv[1]))
ret = JS_ThrowTypeError(js, "second argument must be a string (link path)");
else {
const char *link_path = JS_ToCString(js, argv[1]);
@@ -502,10 +502,9 @@ JSC_SCALL(fd_readdir,
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
} else {
ret = JS_NewArray(js);
int i = 0;
do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, ffd.cFileName));
JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
@@ -515,10 +514,9 @@ JSC_SCALL(fd_readdir,
d = opendir(str);
if (d) {
ret = JS_NewArray(js);
int i = 0;
while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, dir->d_name));
JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name));
}
closedir(d);
} else {
@@ -559,22 +557,29 @@ JSC_CCALL(fd_slurpwrite,
size_t len;
const char *data = js_get_blob_data(js, &len, argv[1]);
if (data == (const char *)-1)
if (!data && len > 0)
return JS_EXCEPTION;
const char *str = JS_ToCString(js, argv[0]);
if (!str) return JS_EXCEPTION;
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
JS_FreeCString(js, str);
if (fd < 0)
return JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno));
if (fd < 0) {
ret = JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno));
JS_FreeCString(js, str);
return ret;
}
ssize_t written = write(fd, data, len);
close(fd);
if (written != (ssize_t)len)
return JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
if (written != (ssize_t)len) {
ret = JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
JS_FreeCString(js, str);
return ret;
}
JS_FreeCString(js, str);
return JS_NULL;
)
@@ -597,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else {
strcpy(item_rel, ffd.cFileName);
}
JS_SetPropertyUint32(js, results, (*result_count)++, JS_NewString(js, item_rel));
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) {
struct stat st;
@@ -622,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else {
strcpy(item_rel, dir->d_name);
}
JS_SetPropertyUint32(js, results, (*result_count)++, JS_NewString(js, item_rel));
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) {
struct stat st;
@@ -663,17 +668,21 @@ JSC_CCALL(fd_realpath,
#ifdef _WIN32
char resolved[PATH_MAX];
DWORD len = GetFullPathNameA(path, PATH_MAX, resolved, NULL);
JS_FreeCString(js, path);
if (len == 0 || len >= PATH_MAX) {
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JSValue err = JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JS_FreeCString(js, path);
return err;
}
JS_FreeCString(js, path);
return JS_NewString(js, resolved);
#else
char *resolved = realpath(path, NULL);
JS_FreeCString(js, path);
if (!resolved) {
return JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JSValue err = JS_ThrowInternalError(js, "realpath failed for %s: %s", path, strerror(errno));
JS_FreeCString(js, path);
return err;
}
JS_FreeCString(js, path);
JSValue result = JS_NewString(js, resolved);
free(resolved);
return result;

77
fd.cm
View File

@@ -1,31 +1,68 @@
var fd = this
var fd = native
var wildstar = use('wildstar')
function last_pos(str, sep) {
var last = null
replace(str, sep, function(m, pos) {
last = pos
return m
})
return last
}
// Helper to join paths
function join_paths(base, rel) {
base = base.replace(/\/+$/, "")
rel = rel.replace(/^\/+/, "")
if (!base) return rel
if (!rel) return base
return base + "/" + rel
var b = replace(base, /\/+$/, "")
var r = replace(rel, /^\/+/, "")
if (!b) return r
if (!r) return b
return b + "/" + r
}
fd.join_paths = join_paths
fd.basename = function basename(path) {
var last = last_pos(path, '/')
if (last == null) return path
return text(path, last+1)
}
fd.dirname = function dirname(path) {
var last = last_pos(path, '/')
if (last == null) return ""
return text(path,0,last)
}
fd.stem = function stem(path) {
var last = last_pos(path, '.')
if (last == null) return path
return text(path,0,last)
}
fd.globfs = function(globs, dir) {
if (dir == null) dir = "."
var _dir = dir
if (_dir == null) _dir = "."
var results = []
function check_neg(path) {
for (var g of globs) {
if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true;
}
return false;
var found = false;
arrfor(globs, function(g) {
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
found = true;
return true;
}
}, null, true);
return found;
}
function check_pos(path) {
for (var g of globs) {
if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true;
}
return false;
var found = false;
arrfor(globs, function(g) {
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
found = true;
return true;
}
}, null, true);
return found;
}
function visit(curr_full, rel_prefix) {
@@ -34,7 +71,7 @@ fd.globfs = function(globs, dir) {
var list = fd.readdir(curr_full)
if (!list) return
for (var item of list) {
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_full = join_paths(curr_full, item)
@@ -46,15 +83,15 @@ fd.globfs = function(globs, dir) {
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
results.push(item_rel)
push(results, item_rel)
}
}
}
});
}
var st = fd.stat(dir)
var st = fd.stat(_dir)
if (st && st.isDirectory) {
visit(dir, "")
visit(_dir, "")
}
return results

View File

@@ -39,7 +39,7 @@ JSC_SCALL(fd_open,
FileOptions flags = kFileRead;
// Parse optional flags argument
if (argc > 1 && JS_IsString(argv[1])) {
if (argc > 1 && JS_IsText(argv[1])) {
const char *flag_str = JS_ToCString(js, argv[1]);
flags = 0;
@@ -70,7 +70,7 @@ JSC_CCALL(fd_write,
size_t len;
int wrote;
if (JS_IsString(argv[1])) {
if (JS_IsText(argv[1])) {
const char *data = JS_ToCStringLen(js, &len, argv[1]);
if (!data) return JS_EXCEPTION;
wrote = pd_file->write(fd, data, (unsigned int)len);
@@ -202,7 +202,7 @@ JSC_SCALL(fd_mkdir,
JSC_SCALL(fd_mv,
if (argc < 2)
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
else if (!JS_IsString(argv[1]))
else if (!JS_IsText(argv[1]))
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
else {
const char *new_path = JS_ToCString(js, argv[1]);
@@ -216,7 +216,7 @@ JSC_SCALL(fd_mv,
JSC_SCALL(fd_symlink,
// Not supported
if (argc >= 2 && JS_IsString(argv[1])) {
if (argc >= 2 && JS_IsText(argv[1])) {
// consume arg
JS_FreeCString(js, JS_ToCString(js, argv[1]));
}
@@ -324,7 +324,7 @@ static void listfiles_cb(const char *path, void *userdata) {
// Playdate listfiles returns just the name, but sometimes with slash for dir?
// Docs say "names of files".
JS_SetPropertyUint32(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path));
JS_SetPropertyNumber(ctx->js, ctx->array, ctx->index++, JS_NewString(ctx->js, path));
}
JSC_SCALL(fd_readdir,
@@ -427,7 +427,7 @@ static void enum_cb(const char *name, void *userdata) {
strcpy(item_rel, name);
}
JS_SetPropertyUint32(ctx->js, ctx->results, (*ctx->count)++, JS_NewString(ctx->js, item_rel));
JS_SetPropertyNumber(ctx->js, ctx->results, (*ctx->count)++, JS_NewString(ctx->js, item_rel));
if (ctx->recurse) {
// Check if directory

View File

@@ -13,7 +13,7 @@ var shop = use('internal/shop')
// Parse arguments
var target_pkg = null
for (var i = 0; i < args.length; i++) {
for (var i = 0; i < length(args); i++) {
if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell fetch [package]")
log.console("Fetch package zips from remote sources.")
@@ -24,7 +24,7 @@ for (var i = 0; i < args.length; i++) {
log.console("This command ensures that the zip files on disk match what's in")
log.console("the lock file. For local packages, this is a no-op.")
$stop()
} else if (!args[i].startsWith('-')) {
} else if (!starts_with(args[i], '-')) {
target_pkg = args[i]
}
}
@@ -35,52 +35,55 @@ var packages_to_fetch = []
if (target_pkg) {
// Fetch specific package
if (!all_packages.includes(target_pkg)) {
if (find(all_packages, target_pkg) == null) {
log.error("Package not found: " + target_pkg)
$stop()
}
packages_to_fetch.push(target_pkg)
push(packages_to_fetch, target_pkg)
} else {
// Fetch all packages
packages_to_fetch = all_packages
}
log.console("Fetching " + text(packages_to_fetch.length) + " package(s)...")
var remote_count = 0
arrfor(packages_to_fetch, function(pkg) {
var entry = lock[pkg]
if (pkg != 'core' && (!entry || entry.type != 'local'))
remote_count++
}, null, null)
var success_count = 0
var skip_count = 0
if (remote_count > 0)
log.console(`Fetching ${text(remote_count)} remote package(s)...`)
var downloaded_count = 0
var cached_count = 0
var fail_count = 0
for (var pkg of packages_to_fetch) {
var entry = lock[pkg]
// Skip local packages
if (entry && entry.type == 'local') {
skip_count++
continue
}
arrfor(packages_to_fetch, function(pkg) {
// Skip core (handled separately)
if (pkg == 'core') {
skip_count++
continue
}
if (pkg == 'core') return
var result = shop.fetch(pkg)
if (result) {
if (result.zip_blob) {
log.console("Fetched: " + pkg)
success_count++
} else {
skip_count++
}
} else {
log.error("Failed to fetch: " + pkg)
if (result.status == 'local') {
// Local packages are just symlinks, nothing to fetch
return
} else if (result.status == 'cached') {
cached_count++
} else if (result.status == 'downloaded') {
log.console(" Downloaded: " + pkg)
downloaded_count++
} else if (result.status == 'error') {
log.error(" Failed: " + pkg + (result.message ? " - " + result.message : ""))
fail_count++
}
}
}, null, null)
log.console("")
log.console("Fetch complete: " + text(success_count) + " fetched, " + text(skip_count) + " skipped, " + text(fail_count) + " failed")
var parts = []
if (downloaded_count > 0) push(parts, `${text(downloaded_count)} downloaded`)
if (cached_count > 0) push(parts, `${text(cached_count)} cached`)
if (fail_count > 0) push(parts, `${text(fail_count)} failed`)
if (length(parts) == 0) push(parts, "nothing to fetch")
log.console("Fetch complete: " + text(parts, ", "))
$stop()

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))

1013
fold.cm Normal file

File diff suppressed because it is too large Load Diff

BIN
fold.mach Normal file

Binary file not shown.

BIN
fold_new.mach Normal file

Binary file not shown.

403
gc_plan.md Normal file
View File

@@ -0,0 +1,403 @@
# Plan: Complete Copying GC Implementation
## Overview
Remove reference counting (DupValue/FreeValue) entirely and complete the Cheney copying garbage collector. Each JSContext will use bump allocation from a heap block, and when out of memory, request a new heap from JSRuntime's buddy allocator and copy live objects to the new heap.
## Target Architecture (from docs/memory.md)
### Object Types (simplified from current):
**Type 0 - Array**: `{ header, length, elements[] }`
**Type 1 - Blob**: `{ header, length, bits[] }`
**Type 2 - Text**: `{ header, length_or_hash, packed_chars[] }`
**Type 3 - Record**: `{ header, prototype, length, key_value_pairs[] }`
**Type 4 - Function**: `{ header, code_ptr, outer_frame_ptr }` - 3 words only, always stone
**Type 5 - Frame**: `{ header, function_ptr, caller_ptr, ret_addr, args[], closure_vars[], local_vars[], temps[] }`
**Type 6 - Code**: Lives in immutable memory only, never copied
**Type 7 - Forward**: Object has moved; cap56 contains new address
### Key Design Points:
- **JSFunction** is just a pointer to code and a pointer to the frame that created it (3 words)
- **Closure variables live in frames** - when a function returns, its frame is "reduced" to just the closure variables
- **Code objects are immutable** - stored in stone memory, never copied during GC
- **Frame reduction**: When a function returns, `caller` is set to zero, signaling the frame can be shrunk
## Current State (needs refactoring)
1. **Partial Cheney GC exists** at `source/quickjs.c:1844-2030`: `ctx_gc`, `gc_copy_value`, `gc_scan_object`
2. **744 calls to JS_DupValue/JS_FreeValue** scattered throughout (currently undefined, causing compilation errors)
3. **Current JSFunction** is bloated (has kind, name, union of cfunc/bytecode/bound) - needs simplification
4. **Current JSVarRef** is a separate object - should be eliminated, closures live in frames
5. **Bump allocator** in `js_malloc` (line 1495) with `heap_base`/`heap_free`/`heap_end`
6. **Buddy allocator** for memory blocks (lines 1727-1837)
7. **Header offset inconsistency** - some structs have header at offset 0, some at offset 8
## Implementation Steps
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
Add these near line 100 in `source/quickjs.c`:
```c
/* Copying GC - no reference counting needed */
#define JS_DupValue(ctx, v) (v)
#define JS_FreeValue(ctx, v) ((void)0)
#define JS_DupValueRT(rt, v) (v)
#define JS_FreeValueRT(rt, v) ((void)0)
```
This makes the code compile while keeping existing call sites (they become no-ops).
### Phase 2: Standardize Object Headers (offset 0)
Remove `JSGCObjectHeader` (ref counting remnant) and put `objhdr_t` at offset 0:
```c
typedef struct JSArray {
objhdr_t hdr; // offset 0
word_t length;
JSValue values[];
} JSArray;
typedef struct JSRecord {
objhdr_t hdr; // offset 0
JSRecord *proto;
word_t length;
slot slots[];
} JSRecord;
typedef struct JSText {
objhdr_t hdr; // offset 0
word_t length; // pretext: length, text: hash
word_t packed[];
} JSText;
typedef struct JSBlob {
objhdr_t hdr; // offset 0
word_t length;
uint8_t bits[];
} JSBlob;
/* Simplified JSFunction per memory.md - 3 words */
typedef struct JSFunction {
objhdr_t hdr; // offset 0, always stone
JSCode *code; // pointer to immutable code object
struct JSFrame *outer; // frame that created this function
} JSFunction;
/* JSFrame per memory.md */
typedef struct JSFrame {
objhdr_t hdr; // offset 0
JSFunction *function; // function being executed
struct JSFrame *caller; // calling frame (NULL = reduced/returned)
word_t ret_addr; // return instruction address
JSValue slots[]; // args, closure vars, locals, temps
} JSFrame;
/* JSCode - always in immutable (stone) memory */
typedef struct JSCode {
objhdr_t hdr; // offset 0, always stone
word_t arity; // max number of inputs
word_t frame_size; // capacity of activation frame
word_t closure_size; // reduced capacity for returned frames
word_t entry_point; // address to begin execution
word_t disruption_point;// address of disruption clause
uint8_t bytecode[]; // actual bytecode
} JSCode;
```
### Phase 3: Complete gc_object_size for All Types
Update `gc_object_size` (line 1850) to read header at offset 0:
```c
static size_t gc_object_size(void *ptr) {
objhdr_t hdr = *(objhdr_t*)ptr; // Header at offset 0
uint8_t type = objhdr_type(hdr);
uint64_t cap = objhdr_cap56(hdr);
switch (type) {
case OBJ_ARRAY:
return sizeof(JSArray) + cap * sizeof(JSValue);
case OBJ_BLOB:
return sizeof(JSBlob) + (cap + 7) / 8; // cap is bits
case OBJ_TEXT:
return sizeof(JSText) + ((cap + 1) / 2) * sizeof(uint64_t);
case OBJ_RECORD:
return sizeof(JSRecord) + (cap + 1) * sizeof(slot); // cap is mask
case OBJ_FUNCTION:
return sizeof(JSFunction); // 3 words
case OBJ_FRAME:
return sizeof(JSFrame) + cap * sizeof(JSValue); // cap is slot count
case OBJ_CODE:
return 0; // Code is never copied (immutable)
default:
return 64; // Conservative fallback
}
}
```
### Phase 4: Complete gc_scan_object for All Types
Update `gc_scan_object` (line 1924):
```c
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
objhdr_t hdr = *(objhdr_t*)ptr;
uint8_t type = objhdr_type(hdr);
switch (type) {
case OBJ_ARRAY: {
JSArray *arr = (JSArray*)ptr;
for (uint32_t i = 0; i < arr->length; i++) {
arr->values[i] = gc_copy_value(ctx, arr->values[i], to_free, to_end);
}
break;
}
case OBJ_RECORD: {
JSRecord *rec = (JSRecord*)ptr;
// Copy prototype
if (rec->proto) {
JSValue proto_val = JS_MKPTR(rec->proto);
proto_val = gc_copy_value(ctx, proto_val, to_free, to_end);
rec->proto = (JSRecord*)JS_VALUE_GET_PTR(proto_val);
}
// Copy table entries
uint32_t mask = objhdr_cap56(rec->hdr);
for (uint32_t i = 1; i <= mask; i++) { // Skip slot 0
JSValue k = rec->slots[i].key;
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
rec->slots[i].key = gc_copy_value(ctx, k, to_free, to_end);
rec->slots[i].value = gc_copy_value(ctx, rec->slots[i].value, to_free, to_end);
}
}
break;
}
case OBJ_FUNCTION: {
JSFunction *func = (JSFunction*)ptr;
// Code is immutable, don't copy - but outer frame needs copying
if (func->outer) {
JSValue outer_val = JS_MKPTR(func->outer);
outer_val = gc_copy_value(ctx, outer_val, to_free, to_end);
func->outer = (JSFrame*)JS_VALUE_GET_PTR(outer_val);
}
break;
}
case OBJ_FRAME: {
JSFrame *frame = (JSFrame*)ptr;
// Copy function pointer
if (frame->function) {
JSValue func_val = JS_MKPTR(frame->function);
func_val = gc_copy_value(ctx, func_val, to_free, to_end);
frame->function = (JSFunction*)JS_VALUE_GET_PTR(func_val);
}
// Copy caller (unless NULL = reduced frame)
if (frame->caller) {
JSValue caller_val = JS_MKPTR(frame->caller);
caller_val = gc_copy_value(ctx, caller_val, to_free, to_end);
frame->caller = (JSFrame*)JS_VALUE_GET_PTR(caller_val);
}
// Copy all slots (args, closure vars, locals, temps)
uint32_t slot_count = objhdr_cap56(frame->hdr);
for (uint32_t i = 0; i < slot_count; i++) {
frame->slots[i] = gc_copy_value(ctx, frame->slots[i], to_free, to_end);
}
break;
}
case OBJ_TEXT:
case OBJ_BLOB:
case OBJ_CODE:
// No internal references to scan
break;
}
}
```
### Phase 5: Fix gc_copy_value Forwarding
Update `gc_copy_value` (line 1883) for offset 0 headers:
```c
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
if (!JS_IsPtr(v)) return v; // Immediate value
void *ptr = JS_VALUE_GET_PTR(v);
// Stone memory - don't copy (includes Code objects)
objhdr_t hdr = *(objhdr_t*)ptr;
if (objhdr_s(hdr)) return v;
// Check if in current heap
if ((uint8_t*)ptr < ctx->heap_base || (uint8_t*)ptr >= ctx->heap_end)
return v; // External allocation
// Already forwarded?
if (objhdr_type(hdr) == OBJ_FORWARD) {
void *new_ptr = (void*)(uintptr_t)objhdr_cap56(hdr);
return JS_MKPTR(new_ptr);
}
// Copy object to new space
size_t size = gc_object_size(ptr);
void *new_ptr = *to_free;
*to_free += size;
memcpy(new_ptr, ptr, size);
// Leave forwarding pointer in old location
*(objhdr_t*)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
return JS_MKPTR(new_ptr);
}
```
### Phase 6: Complete GC Root Tracing
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
```c
static int ctx_gc(JSContext *ctx) {
// ... existing setup code ...
// Copy roots: global object, class prototypes, etc. (existing)
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
ctx->global_var_obj = gc_copy_value(ctx, ctx->global_var_obj, &to_free, to_end);
// ... other existing root copying ...
// Copy GC root stack (JS_PUSH_VALUE/JS_POP_VALUE)
for (JSGCRef *ref = ctx->top_gc_ref; ref; ref = ref->prev) {
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
}
// Copy GC root list (JS_AddGCRef/JS_DeleteGCRef)
for (JSGCRef *ref = ctx->last_gc_ref; ref; ref = ref->prev) {
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
}
// Copy current exception
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
// Cheney scan (existing)
// ...
}
```
### Phase 7: Trigger GC on Allocation Failure
Update `js_malloc` (line 1495):
```c
void *js_malloc(JSContext *ctx, size_t size) {
size = (size + 7) & ~7; // Align to 8 bytes
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
if (ctx_gc(ctx) < 0) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
// Retry after GC
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
JS_ThrowOutOfMemory(ctx);
return NULL;
}
}
void *ptr = ctx->heap_free;
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
return ptr;
}
```
### Phase 8: Frame Reduction (for closures)
When a function returns, "reduce" its frame to just closure variables:
```c
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
if (frame->caller == NULL) return; // Already reduced
JSCode *code = frame->function->code;
uint32_t closure_size = code->closure_size;
// Shrink capacity to just closure variables
frame->hdr = objhdr_make(closure_size, OBJ_FRAME, 0, 0, 0, 0);
frame->caller = NULL; // Signal: frame is reduced
}
```
### Phase 9: Remove Unused Reference Counting Code
Delete:
- `gc_decref`, `gc_decref_child` functions
- `gc_scan_incref_child`, `gc_scan_incref_child2` functions
- `JS_GCPhaseEnum`, `gc_phase` fields
- `JSGCObjectHeader` struct (merge into objhdr_t)
- `ref_count` fields from any remaining structs
- `mark_function_children_decref` function
- All `free_*` functions that rely on ref counting
## Files to Modify
1. **source/quickjs.c** - Main implementation:
- Add DupValue/FreeValue no-op macros (~line 100)
- Restructure JSArray, JSBlob, JSText, JSRecord (lines 468-499)
- Simplify JSFunction to 3-word struct (line 1205)
- Add JSFrame as heap object (new)
- Restructure JSCode/JSFunctionBytecode (line 1293)
- Fix gc_object_size (line 1850)
- Fix gc_copy_value (line 1883)
- Complete gc_scan_object (line 1924)
- Update ctx_gc for all roots (line 1966)
- Update js_malloc to trigger GC (line 1495)
- Delete ref counting code throughout
2. **source/quickjs.h** - Public API:
- Remove JSGCObjectHeader
- Update JSValue type checks if needed
- Ensure JS_IsStone works with offset 0 headers
## Execution Order
1. **First**: Add DupValue/FreeValue macros (enables compilation)
2. **Second**: Standardize struct layouts (header at offset 0)
3. **Third**: Fix gc_object_size and gc_copy_value
4. **Fourth**: Complete gc_scan_object for all types
5. **Fifth**: Update ctx_gc with complete root tracing
6. **Sixth**: Wire js_malloc to trigger GC
7. **Seventh**: Add frame reduction for closures
8. **Finally**: Remove ref counting dead code
## Verification
1. **Compile test**: `make` should succeed without errors
2. **Basic test**: Run simple scripts:
```js
var a = [1, 2, 3]
log.console(a[1])
```
3. **Stress test**: Allocate many objects to trigger GC:
```js
for (var i = 0; i < 100000; i++) {
var x = { value: i }
}
log.console("done")
```
4. **Closure test**: Test functions with closures survive GC:
```js
fn make_counter() {
var count = 0
fn inc() { count = count + 1; return count }
return inc
}
var c = make_counter()
log.console(c()) // 1
log.console(c()) // 2
```
5. **GC stress with closures**: Create many closures, trigger GC, verify they still work
## Key Design Decisions (Resolved)
1. **JSCode storage**: Lives in stone (immutable) memory, never copied during GC ✓
2. **Header offset**: Standardized to offset 0 for all heap objects ✓
3. **Closure variables**: Live in JSFrame objects; frames are "reduced" when functions return ✓
4. **JSVarRef**: Eliminated - closures reference their outer frame directly ✓

236
graph.ce Normal file
View File

@@ -0,0 +1,236 @@
// cell graph [<locator>] - Emit dependency graph
//
// Usage:
// cell graph Graph current directory package
// cell graph . Graph current directory package
// cell graph <locator> Graph specific package
// cell graph --world Graph all packages in shop (world set)
//
// Options:
// --format <fmt> Output format: tree (default), dot, json
// --resolved Show resolved view with links applied (default)
// --locked Show lock view without links
// --world Graph all packages in shop
var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd')
var json = use('json')
var target_locator = null
var format = 'tree'
var show_locked = false
var show_world = false
for (var i = 0; i < length(args); i++) {
if (args[i] == '--format' || args[i] == '-f') {
if (i + 1 < length(args)) {
format = args[++i]
if (format != 'tree' && format != 'dot' && format != 'json') {
log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
$stop()
}
} else {
log.error('--format requires a format type')
$stop()
}
} else if (args[i] == '--resolved') {
show_locked = false
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--world') {
show_world = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell graph [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
$stop()
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
}
}
var links = show_locked ? {} : link.load()
// Get effective locator (after links)
function get_effective(locator) {
return links[locator] || locator
}
// Build graph data structure
var nodes = {}
var edges = []
function add_node(locator) {
if (nodes[locator]) return
var lock = shop.load_lock()
var lock_entry = lock[locator]
var link_target = links[locator]
var info = shop.resolve_package_info(locator)
nodes[locator] = {
id: locator,
effective: get_effective(locator),
linked: link_target != null,
local: info == 'local',
commit: lock_entry && lock_entry.commit ? text(lock_entry.commit, 0, 8) : null
}
}
function gather_graph(locator, visited) {
if (visited[locator]) return
visited[locator] = true
add_node(locator)
try {
var deps = pkg.dependencies(locator)
if (deps) {
arrfor(array(deps), function(alias) {
var dep_locator = deps[alias]
add_node(dep_locator)
push(edges, { from: locator, to: dep_locator, alias: alias })
gather_graph(dep_locator, visited)
})
}
} catch (e) {
// Package might not have dependencies
}
}
// Gather graph from roots
var roots = []
if (show_world) {
// Use all packages in shop as roots
var packages = shop.list_packages()
arrfor(packages, function(p) {
if (p != 'core') {
push(roots, p)
}
})
} else {
// Default to current directory
if (!target_locator) {
target_locator = '.'
}
// Resolve local paths
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
var resolved = fd.realpath(target_locator)
if (resolved) {
target_locator = resolved
}
}
push(roots, target_locator)
}
arrfor(roots, function(root) {
gather_graph(root, {})
})
// Output based on format
if (format == 'tree') {
function print_tree(locator, prefix, is_last, visited) {
if (visited[locator]) {
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
return
}
visited[locator] = true
var node = nodes[locator]
var suffix = ""
if (node.linked) suffix += " -> " + node.effective
if (node.commit) suffix += " @" + node.commit
if (node.local) suffix += " (local)"
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
// Get children
var children = []
arrfor(edges, function(e) {
if (e.from == locator) {
push(children, e)
}
})
for (var i = 0; i < length(children); i++) {
var child_prefix = prefix + (is_last ? " " : "| ")
print_tree(children[i].to, child_prefix, i == length(children) - 1, visited)
}
}
for (var i = 0; i < length(roots); i++) {
log.console(roots[i])
var children = []
arrfor(edges, function(e) {
if (e.from == roots[i]) {
push(children, e)
}
})
for (var j = 0; j < length(children); j++) {
print_tree(children[j].to, "", j == length(children) - 1, {})
}
if (i < length(roots) - 1) log.console("")
}
} else if (format == 'dot') {
log.console("digraph dependencies {")
log.console(" rankdir=TB;")
log.console(" node [shape=box];")
log.console("")
// Node definitions
arrfor(array(nodes), function(id) {
var node = nodes[id]
var label = id
if (node.commit) label += "\\n@" + node.commit
var attrs = 'label="' + label + '"'
if (node.linked) attrs += ', style=dashed'
if (node.local) attrs += ', color=blue'
// Safe node ID for dot
var safe_id = replace(id, /[^a-zA-Z0-9]/g, '_')
log.console(' ' + safe_id + ' [' + attrs + '];')
})
log.console("")
// Edges
arrfor(edges, function(e) {
var from_id = replace(e.from, /[^a-zA-Z0-9]/g, '_')
var to_id = replace(e.to, /[^a-zA-Z0-9]/g, '_')
var label = e.alias != e.to ? 'label="' + e.alias + '"' : ''
log.console(' ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';')
})
log.console("}")
} else if (format == 'json') {
var output = {
nodes: [],
edges: []
}
arrfor(array(nodes), function(id) {
push(output.nodes, nodes[id])
})
output.edges = edges
log.console(json.encode(output))
}
$stop()

46
help.ce
View File

@@ -2,7 +2,7 @@
var fd = use('fd')
var command = args.length > 0 ? args[0] : null
var command = length(args) > 0 ? args[0] : null
// Display specific command help
if (command) {
@@ -27,21 +27,41 @@ if (stat && stat.isFile) {
log.console(content)
} else {
// Fallback if man file doesn't exist
log.console("cell - The Cell module system for Prosperon")
log.console("cell - The Cell package manager")
log.console("")
log.console("Usage: cell <command> [arguments]")
log.console("")
log.console("Commands:")
log.console(" init Initialize a new Cell project")
log.console(" get Fetch and add a module dependency")
log.console(" update Update a dependency to a new version")
log.console(" vendor Copy all dependencies locally")
log.console(" build Compile all modules to bytecode")
log.console(" patch Create a patch for a module")
log.console(" config Manage system and actor configurations")
log.console(" help Show this help message")
log.console("Package Management:")
log.console(" install <locator> Install a package and its dependencies")
log.console(" update [locator] Update packages from remote sources")
log.console(" remove <locator> Remove a package from the shop")
log.console(" add <locator> Add a dependency to current package")
log.console("")
log.console("Run 'cell help <command>' for more information on a command.")
log.console("Building:")
log.console(" build [locator] Build dynamic libraries for packages")
log.console(" clean [scope] Remove build artifacts")
log.console("")
log.console("Linking (Local Development):")
log.console(" link <origin> <target> Link a package to a local path")
log.console(" unlink <origin> Remove a package link")
log.console(" clone <origin> <path> Clone and link a package locally")
log.console("")
log.console("Information:")
log.console(" list [scope] List packages and dependencies")
log.console(" ls [locator] List modules and actors in a package")
log.console(" why <locator> Show reverse dependencies")
log.console(" search <query> Search for packages, modules, or actors")
log.console("")
log.console("Diagnostics:")
log.console(" resolve [locator] Print fully resolved dependency closure")
log.console(" graph [locator] Emit dependency graph (tree, dot, json)")
log.console(" verify [scope] Verify integrity and consistency")
log.console("")
log.console("Other:")
log.console(" help [command] Show help for a command")
log.console(" version Show cell version")
log.console("")
log.console("Run 'cell <command> --help' for more information on a command.")
}
$stop()
$stop()

View File

@@ -1,62 +1,185 @@
// cell install <locator> - Install a package to the shop
// Does not modify the current project's cell.toml
//
// Usage:
// cell install <locator> Install a package and its dependencies
// cell install . Install current directory package
//
// Options:
// --target <triple> Build for target platform
// --refresh Refresh floating refs before locking
// --dry-run Show what would be installed
var shop = use('internal/shop')
var build = use('build')
var pkg = use('package')
var fd = use('fd')
if (args.length < 1) {
log.console("Usage: cell install <locator>")
if (length(args) < 1) {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
$stop()
return
}
var locator = args[0]
var locator = null
var target_triple = null
var refresh = false
var dry_run = false
for (var i = 0; i < length(args); i++) {
if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < length(args)) {
target_triple = args[++i]
} else {
log.error('--target requires a triple')
$stop()
}
} else if (args[i] == '--refresh') {
refresh = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Install a package and its dependencies to the shop.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
$stop()
} else if (!starts_with(args[i], '-')) {
locator = args[i]
}
}
if (!locator) {
log.console("Usage: cell install <locator>")
$stop()
}
// Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted to absolute paths
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
// Default target
if (!target_triple) {
target_triple = build.detect_host_target()
}
log.console("Installing " + locator + "...")
var pkg = use('package')
// Gather all packages that will be installed
var packages_to_install = []
var skipped_packages = []
var visited = {}
// Recursive install function that handles dependencies
function install_package(pkg_locator, visited) {
function gather_packages(pkg_locator) {
if (visited[pkg_locator]) return
visited[pkg_locator] = true
// First, add to lock.toml
shop.update(pkg_locator)
// Extract/symlink the package so we can read its cell.toml
shop.extract(pkg_locator)
// Now get direct dependencies and install them first
// Check if this is a local path that doesn't exist
if (starts_with(pkg_locator, '/') && !fd.is_dir(pkg_locator)) {
push(skipped_packages, pkg_locator)
log.console(" Skipping missing local package: " + pkg_locator)
return
}
push(packages_to_install, pkg_locator)
// Try to read dependencies
try {
// For packages not yet extracted, we need to update and extract first to read deps
var lock = shop.load_lock()
if (!lock[pkg_locator]) {
if (!dry_run) {
var update_result = shop.update(pkg_locator)
if (update_result) {
shop.extract(pkg_locator)
} else {
// Update failed - package might not be fetchable
log.console("Warning: Could not fetch " + pkg_locator)
return
}
}
} else {
// Package is in lock, ensure it's extracted
if (!dry_run) {
shop.extract(pkg_locator)
}
}
var deps = pkg.dependencies(pkg_locator)
if (deps) {
for (var alias in deps) {
arrfor(array(deps), function(alias) {
var dep_locator = deps[alias]
log.console("Installing dependency " + dep_locator)
install_package(dep_locator, visited)
}
gather_packages(dep_locator)
})
}
} catch (e) {
// Package might not have dependencies or cell.toml issue
log.console("Warning: Could not read dependencies for " + pkg_locator + ": " + e.message)
if (!dry_run) {
log.console(`Warning: Could not read dependencies for ${pkg_locator}: ${e.message}`)
}
}
// Build the package after all dependencies are installed
build.build_package(pkg_locator)
}
install_package(locator, {})
log.console("Installed " + locator)
// Gather all packages
gather_packages(locator)
if (dry_run) {
log.console("Would install:")
arrfor(packages_to_install, function(p) {
var lock = shop.load_lock()
var exists = lock[p] != null
log.console(" " + p + (exists ? " (already installed)" : ""))
})
if (length(skipped_packages) > 0) {
log.console("")
log.console("Would skip (missing local paths):")
arrfor(skipped_packages, function(p) {
log.console(" " + p)
})
}
$stop()
}
// Install each package
function install_package(pkg_locator) {
// Update lock entry
shop.update(pkg_locator)
// Extract/symlink the package
shop.extract(pkg_locator)
// Build scripts
shop.build_package_scripts(pkg_locator)
// Build C code
try {
build.build_dynamic(pkg_locator, target_triple, 'release')
} catch (e) {
// Not all packages have C code
}
}
arrfor(packages_to_install, function(p) {
log.console(" Installing " + p + "...")
install_package(p)
})
var summary = "Installed " + text(length(packages_to_install)) + " package(s)."
if (length(skipped_packages) > 0) {
summary += " Skipped " + text(length(skipped_packages)) + " missing local path(s)."
}
log.console(summary)
$stop()

283
internal/bootstrap.cm Normal file
View File

@@ -0,0 +1,283 @@
// Hidden vars come from env:
// CLI mode (cell_init): os, args, core_path, shop_path, emit_qbe, dump_mach
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path, shop_path
// 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 (bootstrap modules have no source fallback)
function boot_load(name, env) {
var mach_path = core_path + '/' + name + ".mach"
var data = null
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
return mach_load(data, env)
}
print("error: missing bootstrap bytecode: " + mach_path + "\n")
disrupt
}
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)
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
// Always load mcode compiler module
var mcode_mod = boot_load("mcode", boot_env)
use_cache['mcode'] = mcode_mod
var streamline_mod = null
var qbe_emit_mod = null
// Warn if any .cm source is newer than its .mach bytecode
function check_mach_stale() {
var pairs = [
["tokenize.cm", "tokenize.mach"],
["parse.cm", "parse.mach"],
["fold.cm", "fold.mach"],
["mcode.cm", "mcode.mach"],
["streamline.cm", "streamline.mach"],
["qbe.cm", "qbe.mach"],
["qbe_emit.cm", "qbe_emit.mach"],
["internal/bootstrap.cm", "internal/bootstrap.mach"],
["internal/engine.cm", "internal/engine.mach"]
]
var stale = []
var _i = 0
var cm_path = null
var mach_path = null
var cm_stat = null
var mach_stat = null
while (_i < length(pairs)) {
cm_path = core_path + '/' + pairs[_i][0]
mach_path = core_path + '/' + pairs[_i][1]
if (fd.is_file(cm_path) && fd.is_file(mach_path)) {
cm_stat = fd.stat(cm_path)
mach_stat = fd.stat(mach_path)
if (cm_stat.mtime > mach_stat.mtime) {
push(stale, pairs[_i][0])
}
}
_i = _i + 1
}
if (length(stale) > 0) {
print("warning: bytecode is stale for: " + text(stale, ", ") + "\n")
print("run 'make regen' or './cell --core . regen.cm' to update\n")
}
}
check_mach_stale()
// 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
}
// Load a module from .mach bytecode, falling back to source compilation
function load_module(name, env) {
var mach_path = core_path + '/' + name + ".mach"
var data = null
var src_path = null
var src = null
var ast = null
var compiled = null
var optimized = null
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
return mach_load(data, env)
}
src_path = core_path + '/' + name + ".cm"
src = text(fd.slurp(src_path))
ast = analyze(src, src_path)
compiled = mcode_mod(ast)
optimized = streamline_mod(compiled)
return mach_eval_mcode(name, json.encode(optimized), env)
}
// Load optimization pipeline modules (needs analyze to be defined)
var qbe_macros = null
streamline_mod = load_module("streamline", boot_env)
use_cache['streamline'] = streamline_mod
if (emit_qbe) {
qbe_macros = load_module("qbe", boot_env)
qbe_emit_mod = load_module("qbe_emit", boot_env)
use_cache['qbe'] = qbe_macros
use_cache['qbe_emit'] = qbe_emit_mod
}
// Run AST through mcode pipeline → register VM
function run_ast(name, ast, env) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
var qbe_il = null
if (emit_qbe) {
qbe_il = qbe_emit_mod(optimized, qbe_macros)
print(qbe_il)
return null
}
if (dump_mach) {
mach_dump_mcode(name, json.encode(optimized), env)
return null
}
return mach_eval_mcode(name, json.encode(optimized), env)
}
// use() with ƿit pipeline for .cm modules
function use_fn(path) {
var file_path = null
var mach_path = null
var data = null
var script = null
var ast = null
var result = null
if (use_cache[path])
return use_cache[path]
// Try .mach bytecode first (CWD then core_path)
mach_path = path + '.mach'
if (!fd.is_file(mach_path))
mach_path = core_path + '/' + path + '.mach'
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
result = mach_load(data, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm source (CWD then core_path)
file_path = path + '.cm'
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_fn})
use_cache[path] = result
return result
}
// Fallback to embedded C module
result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
}
// Helper to load engine.cm and run it with given env
function load_engine(env) {
var engine_path = core_path + '/internal/engine.mach'
var data = null
var engine_src = null
var engine_ast = null
if (fd.is_file(engine_path)) {
data = fd.slurp(engine_path)
return mach_load(data, env)
}
engine_path = core_path + '/internal/engine.cm'
engine_src = text(fd.slurp(engine_path))
engine_ast = analyze(engine_src, engine_path)
return run_ast('engine', engine_ast, env)
}
// Detect mode and route
// CLI mode has 'args'; actor spawn mode has 'init'
var program = null
var user_args = []
var _j = 0
var script_file = null
var script = null
var ast = null
if (args != null) {
// CLI mode — parse args
program = args[0]
_j = 1
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
// Resolve script file: try .cm then .ce in CWD then core_path
script_file = program
if (!ends_with(script_file, '.ce') && !ends_with(script_file, '.cm'))
script_file = program + '.cm'
if (!fd.is_file(script_file))
script_file = core_path + '/' + program + '.cm'
if (!fd.is_file(script_file))
script_file = program + '.ce'
if (!fd.is_file(script_file))
script_file = core_path + '/' + program + '.ce'
if (ends_with(script_file, '.ce')) {
// Actor script — delegate to engine
load_engine({
os: os, actorsym: actorsym,
init: {program: program, arg: user_args},
core_path: core_path, shop_path: shop_path, json: json,
analyze: analyze, run_ast_fn: run_ast
})
} else {
// Module script — run directly
script = text(fd.slurp(script_file))
ast = analyze(script, script_file)
run_ast(program, ast, {use: use_fn, args: user_args, json: json})
}
} else {
// Actor spawn mode — load engine.cm with full actor env
load_engine({
os: os, actorsym: actorsym, init: init,
core_path: core_path, shop_path: shop_path, json: json, nota: nota, wota: wota,
analyze: analyze, run_ast_fn: run_ast
})
}

BIN
internal/bootstrap.mach Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

BIN
internal/engine.mach Normal file

Binary file not shown.

View File

@@ -1,63 +0,0 @@
#include "cell.h"
static JSValue js_json_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_ThrowTypeError(ctx, "json.encode requires at least 1 argument");
JSValue global = JS_GetGlobalObject(ctx);
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify");
JSValue args[3];
args[0] = argv[0]; // value
args[1] = (argc > 1) ? argv[1] : JS_NULL; // replacer
args[2] = (argc > 2) ? argv[2] : JS_NewInt32(ctx, 1); // space, default 1
JSValue result = JS_Call(ctx, stringify, json, 3, args);
JS_FreeValue(ctx, stringify);
JS_FreeValue(ctx, json);
JS_FreeValue(ctx, global);
if (argc <= 2) JS_FreeValue(ctx, args[2]);
return result;
}
static JSValue js_json_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_ThrowTypeError(ctx, "json.decode requires at least 1 argument");
if (!JS_IsString(argv[0])) {
JSValue err = JS_NewError(ctx);
JS_DefinePropertyValueStr(ctx, err, "message",
JS_NewString(ctx, "couldn't parse text: not a string"),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return JS_Throw(ctx, err);
}
JSValue global = JS_GetGlobalObject(ctx);
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
JSValue parse = JS_GetPropertyStr(ctx, json, "parse");
JSValue args[2];
args[0] = argv[0]; // text
args[1] = (argc > 1) ? argv[1] : JS_NULL; // reviver
JSValue result = JS_Call(ctx, parse, json, argc > 1 ? 2 : 1, args);
JS_FreeValue(ctx, parse);
JS_FreeValue(ctx, json);
JS_FreeValue(ctx, global);
return result;
}
static const JSCFunctionListEntry js_json_funcs[] = {
JS_CFUNC_DEF("encode", 1, js_json_encode),
JS_CFUNC_DEF("decode", 1, js_json_decode),
};
JSValue js_json_use(JSContext *js) {
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_json_funcs, sizeof(js_json_funcs)/sizeof(JSCFunctionListEntry));
return export;
}

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