aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
commit1c53304d03414890af0da7432d1aea87e754a14a (patch)
tree97190d86cf548c9bc63f68c86a4d2a4f09809713 /src
parenta (diff)
feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics
Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor<I,O>, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests
Diffstat (limited to 'src')
-rwxr-xr-xsrc/baremetal/run.sh4
-rw-r--r--src/compiler/compiler.zc3954
-rw-r--r--src/core/core.c606
-rw-r--r--src/core/core.zc19
-rw-r--r--src/core/io_linux.c33
-rw-r--r--src/core/io_win.c4
-rw-r--r--src/core/memmap.h39
-rw-r--r--src/core/stack.h5
-rw-r--r--src/js/extract_genome.js8
-rw-r--r--src/js/extract_lsp.js8
-rw-r--r--src/js/tinyvm.ts33
-rw-r--r--src/lib/actor.zc280
-rw-r--r--src/lib/icon.zc623
-rw-r--r--src/lib/std.zc327
-rw-r--r--src/lsp/server.zc2400
-rw-r--r--src/micro/micro.c172
-rw-r--r--src/micro/port_linux.c167
-rw-r--r--src/micro/port_wasm_stream.c10
-rw-r--r--src/runtime/rt.zc512
-rw-r--r--src/runtime/run.zc41
-rwxr-xr-xsrc/tools/automap.sh10
-rw-r--r--src/tools/build.zc13
-rwxr-xr-xsrc/tools/embed.sh89
-rw-r--r--src/tools/genmap.c13
-rw-r--r--src/tools/mkgenome.c11
-rwxr-xr-xsrc/tools/mklsp.sh12
-rwxr-xr-xsrc/tools/mklspwasm.sh6
-rwxr-xr-xsrc/tools/mkvsix.sh4
-rw-r--r--src/tools/zc0.c103
-rwxr-xr-xsrc/tools/zcc4
-rwxr-xr-xsrc/web/build.sh4
31 files changed, 7559 insertions, 1955 deletions
diff --git a/src/baremetal/run.sh b/src/baremetal/run.sh
index d6ac333..aec9d20 100755
--- a/src/baremetal/run.sh
+++ b/src/baremetal/run.sh
@@ -12,6 +12,10 @@
# Prints the program's UART output and nothing else. Exits 0 on a clean
# power-off. A missing toolchain exits 0 from `ready` and 1 otherwise.
set -euo pipefail
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/baremetal/run.sh ready riscv|aarch64' ' src/baremetal/run.sh riscv|aarch64 run|io|compile FILE.zc' 'builds a freestanding microcore and boots it in qemu-system.' 'run compiles the program into the image; io also forwards program input.' 'compile sends source to the resident compiler over the uart.' 'ready checks for the cross compiler and emulator.'; exit 0 ;;
+ -*) echo "baremetal run: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
ready() {
diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc
index db7355b..d274eff 100644
--- a/src/compiler/compiler.zc
+++ b/src/compiler/compiler.zc
@@ -1,177 +1,202 @@
-// ============================================================================
-// compiler.zc: the resident zc compiler. it lives in the genome next to the
-// runtime, compiles source straight to vm words in memory, and is visible to
-// the programs it compiles: every function and global here can be called or
-// patched by name, and parse words (immediate fns) run inside it.
-// ============================================================================
+namespace run;
+include rt;
+
+//: ============================================================================
+//: compiler.zc: the resident zc compiler. it lives in the genome next to the
+//: runtime, compiles source straight to vm words in memory, and is visible to
+//: the programs it compiles: every function and global here can be called or
+//: patched by name, and parse words (immediate fns) run inside it.
+//: ============================================================================
+
+//: how this file is organized, in the order a source file travels through it:
+//:
+//: 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a
+//: kind (tk), a value (tv: number or interned name) and a
+//: place (line, offset, length). sources nest: stdin, a
+//: token list for macros, a string for eval.
+//: 2 symbols every name is interned once in a string pool; symbols
+//: (functions, globals, constants, keywords, types) hang
+//: off the interned name and shadow one another.
+//: 3 types records in the type area, hash-consed, so comparing two
+//: types is comparing two words. typing is optional: an
+//: untyped program is all "word".
+//: 4 analyzer the ownership/lifetime pass. it runs while the parser
+//: emits code, tracking for every local whether it owns an
+//: object in this block (m), moves one up and out (u), or
+//: moves one down into a lower scope (d). borrowing is
+//: tracked separately. the pass inserts frees, and joins
+//: merge that knowledge at
+//: if/else, loops and switches.
+//: 5 expressions precedence climbing over the operator tables, which a
+//: program can extend at compile time.
+//: 6 statements blocks, control flow, switch, asm blocks, quote/grab.
+//: 7 functions signatures, prologues, closures, methods, generics,
+//: fattening (typed new) and the top level.
+//: 8 setup the keyword table, built-in names, entry point.
+//: 9 check mode the same compiler run over an in-memory document for the
+//: language server: errors and hints become records.
+//:
+//: the compiler emits vm words directly into memory at "here" (there is no
+//: intermediate tree). a forward reference emits a placeholder literal and
+//: remembers the site on a patch chain; defining the name patches the chain.
+//: the compiler is itself written in zc and lives in the genome next to rt.zc,
+//: so a program being compiled can call any function in this file, and
+//: immediate fns (parse words) run in the middle of compilation and can read
+//: tokens, emit code, or add operators. a few conventions that recur:
+//: - "plain word": most values are untyped 64-bit words. a pointer is just a
+//: word that happens to be a heap address.
+//: - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op.
+//: - names ending in an s_ prefix are interned spellings the parser compares
+//: against by pointer, which is why intern exists.
+//: - serr reports and carries on; die reports and skips input to the next
+//: ; or }. nothing in here raises or unwinds.
+//: - a backtick outside a comment or a literal ends the source (srcend). zc0,
+//: the c bootstrap, reads the same way, so a backtick in a comment is fine.
+//:
+//: ---- compiler data region (@map-cc) ----
-// how this file is organized, in the order a source file travels through it:
-//
-// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a
-// kind (tk), a value (tv: number or interned name) and a
-// place (line, offset, length). sources nest: stdin, a
-// token list for macros, a string for eval.
-// 2 symbols every name is interned once in a string pool; symbols
-// (functions, globals, constants, keywords, types) hang
-// off the interned name and shadow one another.
-// 3 types records in the type area, hash-consed, so comparing two
-// types is comparing two words. typing is optional: an
-// untyped program is all "word".
-// 4 analyzer the ownership/lifetime pass. it runs while the parser
-// emits code, tracking for every local whether it owns an
-// object in this block (m), moves one up and out (u), or
-// moves one down into a lower scope (d). borrowing is
-// tracked separately. the pass inserts frees, and joins
-// merge that knowledge at
-// if/else, loops and switches.
-// 5 expressions precedence climbing over the operator tables, which a
-// program can extend at compile time.
-// 6 statements blocks, control flow, switch, asm blocks, quote/grab.
-// 7 functions signatures, prologues, closures, methods, generics,
-// fattening (typed new) and the top level.
-// 8 setup the keyword table, built-in names, entry point.
-// 9 check mode the same compiler run over an in-memory document for the
-// language server: errors and hints become records.
-//
-// the compiler emits vm words directly into memory at "here" (there is no
-// intermediate tree). a forward reference emits a placeholder literal and
-// remembers the site on a patch chain; defining the name patches the chain.
-// the compiler is itself written in zc and lives in the genome next to rt.zc,
-// so a program being compiled can call any function in this file, and
-// immediate fns (parse words) run in the middle of compilation and can read
-// tokens, emit code, or add operators. a few conventions that recur:
-// - "plain word": most values are untyped 64-bit words. a pointer is just a
-// word that happens to be a heap address.
-// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op.
-// - names ending in an s_ prefix are interned spellings the parser compares
-// against by pointer, which is why intern exists.
-// - serr reports and carries on; die reports and skips input to the next
-// ; or }. nothing in here raises or unwinds.
-// - a backtick outside a comment or a literal ends the source (srcend). zc0,
-// the c bootstrap, reads the same way, so a backtick in a comment is fine.
-//
-// ---- compiler data region (@map-cc) ----
// src/tools/genmap computes these addresses from the compiler table sizes for each
// build. later regions (the type area, local types, exit records) are computed
// from these names.
// @map-cc
-
-const int IHN = 0x8000;
-const word R_IH = 0x90100;
-const word R_POOL = 0x98100;
-const word R_POOLEND = 0xD8100;
-const int NS = 0x4000;
-const word R_SYM = 0xD8200;
-const word sname_BASE = 0xD8200;
-const word skind_BASE = 0xDC200;
-const word sval_BASE = 0xE0200;
-const word ssig_BASE = 0xE4200;
-const word sflag_BASE = 0xE8200;
-const word sfix_BASE = 0xEC200;
-const word snext_BASE = 0xF0200;
-const int NL = 0x200;
-const word lname_BASE = 0xF4200;
-const word lslot_BASE = 0xF4400;
-const word lhome_BASE = 0xF4600;
-const word lstate_BASE = 0xF4800;
-const word lowner_BASE = 0xF4A00;
-const word lused_BASE = 0xF4C00;
-const word larr_BASE = 0xF4E00;
-const word lown_BASE = 0xF5000;
-const int NF = 0x8000;
-const word fsite_BASE = 0xF5200;
-const word fnext_BASE = 0xFD200;
-const word R_SNAP_BASE = 0x105200;
-const word R_QBUF_BASE = 0x109200;
-const word R_SRC_BASE = 0x10D200;
-const word R_SEQ_BASE = 0x10D600;
-const word bname_BASE = 0x10DE00;
-const word bprec_BASE = 0x10DE40;
-const word bseq_BASE = 0x10DE80;
-const word blen_BASE = 0x10DEC0;
-const word uname_BASE = 0x10DF00;
-const word useq_BASE = 0x10DF20;
-const word ulen_BASE = 0x10DF40;
-const word aname_BASE = 0x10DF60;
-const word abin_BASE = 0x10DFA0;
-const word kops_BASE = 0x10E000;
-const word R_CTX_BASE = 0x10E200;
-const word R_LOOP_BASE = 0x10EA00;
-const word R_ASM = 0x10EE00;
-const int ASM_LABELS = 0x40;
-const int ASM_REFS = 0x100;
-const int ASM_WORDS = 0x400;
-const int EXIT_WORDS = 0xA00;
-const int ENUM_MEMBERS = 0x80;
-const int TAGGED_ENUMS = 0x10;
-const int STRUCT_METHODS = 0x20;
-const int GENERATED_WORDS = 0x2000;
-const int RANGE_FACTS = 0x20;
-const int JOIN_PATHS = 0x40;
-const int O3_REGIONS = 0xA;
+const IHN = 0x8000;
+const R_IH = 0x90100;
+const R_POOL = 0x98100;
+const R_POOLEND = 0xD8100;
+const NS = 0x4000;
+const R_SYM = 0xD8200;
+const sname_BASE = 0xD8200;
+const skind_BASE = 0xDC200;
+const sval_BASE = 0xE0200;
+const ssig_BASE = 0xE4200;
+const sflag_BASE = 0xE8200;
+const sfix_BASE = 0xEC200;
+const snext_BASE = 0xF0200;
+const NL = 0x200;
+const lname_BASE = 0xF4200;
+const lslot_BASE = 0xF4400;
+const lhome_BASE = 0xF4600;
+const lstate_BASE = 0xF4800;
+const lowner_BASE = 0xF4A00;
+const lused_BASE = 0xF4C00;
+const larr_BASE = 0xF4E00;
+const lown_BASE = 0xF5000;
+const NF = 0x8000;
+const fsite_BASE = 0xF5200;
+const fnext_BASE = 0xFD200;
+const R_SNAP_BASE = 0x105200;
+const R_QBUF_BASE = 0x109200;
+const R_SRC_BASE = 0x10D200;
+const R_SEQ_BASE = 0x10D600;
+const bname_BASE = 0x10DE00;
+const bprec_BASE = 0x10DE40;
+const bseq_BASE = 0x10DE80;
+const blen_BASE = 0x10DEC0;
+const uname_BASE = 0x10DF00;
+const useq_BASE = 0x10DF20;
+const ulen_BASE = 0x10DF40;
+const aname_BASE = 0x10DF60;
+const abin_BASE = 0x10DFA0;
+const kops_BASE = 0x10E000;
+const R_CTX_BASE = 0x10E200;
+const R_LOOP_BASE = 0x10EA00;
+const R_ASM = 0x10EE00;
+const ASM_LABELS = 0x40;
+const ASM_REFS = 0x100;
+const ASM_WORDS = 0x400;
+const EXIT_WORDS = 0xA00;
+const ENUM_MEMBERS = 0x80;
+const TAGGED_ENUMS = 0x10;
+const STRUCT_METHODS = 0x20;
+const GENERATED_WORDS = 0x2000;
+const RANGE_FACTS = 0x20;
+const JOIN_PATHS = 0x40;
+const O3_REGIONS = 0xA;
// @end-map
-// table bases start in the generated map; growth copies columns without changing indices.
-word sname = sname_BASE;
+//: table bases start in the generated map; growth copies columns without changing indices.
+
+/* interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id. */
+char** sname = sname_BASE;
+/* kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC. */
word skind = skind_BASE;
+/* value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind. */
word sval = sval_BASE;
+/* packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags. */
word ssig = ssig_BASE;
+/* definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC. */
word sflag = sflag_BASE;
+/* head of unresolved code references for each symbol; references are patched when the symbol is defined. */
word sfix = sfix_BASE;
+/* previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain. */
word snext = snext_BASE;
-word lname = lname_BASE;
+// retained source metadata for functions that can be transformed at runtime.
+word fnmeta;
+int syntax_host_floor, syntax_host_from; word syntax_host_symbols;
+/* interned name of each currently active local variable or parameter. */
+char** lname = lname_BASE;
+/* frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame. */
word lslot = lslot_BASE;
+/* scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks. */
word lhome = lhome_BASE;
+/* ownership state of each local: plain, m, u, d, dead, child or borrowed. */
word lstate = lstate_BASE;
+/* owner local of a borrowed or container-owned local; lroot(i) follows this chain. */
word lowner = lowner_BASE;
+/* source line of each local's last tracked use; zero means it has not been used. */
word lused = lused_BASE;
+/* nonzero for locals stored in place as fixed arrays or structs; their expression value is their address. */
word larr = larr_BASE;
+/* nonzero when a local is responsible for releasing its owned object. */
word lown = lown_BASE;
+/* code location to patch for each unresolved reference record. */
word fsite = fsite_BASE;
+/* next unresolved reference record, stored as index plus one; zero ends a patch chain. */
word fnext = fnext_BASE;
int symcap = NS, loccap = NL, fixcap = NF;
-const word R_TY = ZFP - 16384, R_TYEND = ZFP;
-// interned text and type records never move: only their index vectors grow.
-word namehash = R_IH, poolend = R_POOLEND;
+const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP;
+//: interned text and type records never move: only their index vectors grow.
+char** namehash = R_IH; word poolend = R_POOLEND;
word poolblocks, typeblocks;
int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL;
word typelist = R_TY, tyend = R_TYEND;
int typecap = 1024;
-// growing scratch regions retain offsets used by nested parser frames.
+//: growing scratch regions retain offsets used by nested parser frames.
word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE;
word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE;
word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE;
word R_SEQ = R_SEQ_BASE; int R_SEQ_cap = bname_BASE - R_SEQ_BASE;
word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE;
word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE;
-[60]word lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
+word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
-// operator columns grow independently of their initial map reservations.
-word bname = bname_BASE;
+//: operator columns grow independently of their initial map reservations.
+char** bname = bname_BASE;
word bprec = bprec_BASE;
word bseq = bseq_BASE;
word blen = blen_BASE;
-word uname = uname_BASE;
+char** uname = uname_BASE;
word useq = useq_BASE;
word ulen = ulen_BASE;
-word aname = aname_BASE;
+char** aname = aname_BASE;
word abin = abin_BASE;
word kops = kops_BASE;
int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE;
-// ---- token kinds, symbol kinds and flags ----
+//: ---- token kinds, symbol kinds and flags ----
+
const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6;
const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8;
const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768;
-// variable states for the lifetime analyzer
+//: variable states for the lifetime analyzer
const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6;
-// expression classes: plain word, a new object, or a bare variable (C_VAR + index)
+//: expression classes: plain word, a new object, or a bare variable (C_VAR + index)
const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2;
-// lvalue kinds
+//: lvalue kinds
const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4;
-// opcodes
+//: opcodes
const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7;
const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20;
const int XOP0 = 22; // first enhancement opcode
@@ -180,37 +205,48 @@ const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609;
const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal
const int SEQREF = -2; // in an operator sequence: the next word names a symbol
-// ---- state ----
+//: ---- state ----
+
int chk_heap;
int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree;
+char* module_name; word module_imports;
int nbin, nun, nasg, nkop, seqtop;
char* mnem = "q@!$_~cjzr,?.+-*&|^><%";
int check_on = 1; // set to 0 to switch the analyzer off for what follows
int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot;
-// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended
+//: where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended
int toff = -1, toklen, tpend = -1, tpline;
int ntok; // tokens taken so far, so a parse word that reads nothing can be caught
int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal
-[1100]word tbuf_initial; word tbuf = tbuf_initial; int tbufcap = 1100;
-// doc comments: the comment lines directly above a declaration (no blank line
-// between them or before the declaration) are its documentation. the lexer
-// collects them in dbuf, dlast is the line of the last one, and dline the
-// line of the token that follows them. only when docs are on.
+char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100;
+//: doc comments: the comment lines directly above a declaration (no blank line
+//: between them or before the declaration) are its documentation. the lexer
+//: collects them in dbuf, dlast is the line of the last one, and dline the
+//: line of the token that follows them. only when docs are on.
int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string
-[2100]word dbuf_initial; word dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending;
-[2100]word dsnap_initial; word dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; [300]word dnmb_initial; word dnmb = dnmb_initial; int docnamecap = 300;
-int pk_tk, pk_tv, pq_n; [4]word pq_tk; [4]word pq_tv; [4]word pq_gl; [4]word pq_ln; // up to 4 tokens of lookahead
+char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect;
+char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300;
+int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead
int here, hdelta, hlimit, ptop;
-int code_chunk = CODE_END;
+int code_chunk = rt.CODE_END;
word image_buffer, image_words; int image_active, image_org, image_capacity, image_size;
int nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn;
int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u)
+int fn_return_type; // a consistent return type inferred from the function body
+//: effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative.
+const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64;
+word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base;
+word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy;
+int borrow_pending, borrow_depth, borrow_jump = -1, borrow_entry; word borrow_slots;
+int argorigin = -1, argtemporary, argowner = -1; word argowner_fact;
int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints
+word xprojection; int af_mutation_tick;
int xbor = -1; // local a pointer value is borrowed from, or -1
int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on
int lsp, loopbase, ctxsp, snaptop;
int lv, lvk, lvi, kw_expr, xcls, fty;
-// interned spellings the parser compares against
+//: interned spellings the parser compares against
+
int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec;
int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror;
int s_dot, s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_immediate, s_m, s_d, s_u;
@@ -218,116 +254,143 @@ int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s
int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave;
int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static;
-// ---- types: kinds, tables and parser state (see the types section) ----
+//: ---- types: kinds, tables and parser state (see the types section) ----
+
const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6;
-// dyn: []T, a = element, n = 1 if fixed
-const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12;
+//: dyn: T[?], a = element, n = 1 if fixed
+const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13;
const int NTL = 1024; // the first NTL words list every record
-// type per local, declared tag per local, type per symbol
+//: type per local, declared tag per local, type per symbol
+
const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL;
word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE;
-int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32;
-int ckst, ckn; // extern: the zc struct a by-value parameter names (ckind), and its name
+int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr;
+int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name
int tc_on; // the optional typechecker, off unless a program says typecheck on;
-// type, literal 0, float, byte string, dry runs (typeof), unique giveaway
+//: type, literal 0, float, byte string, dry runs (typeof), unique giveaway
+
word xty; int xzero, xfloat, xstr, dry, xgive;
-// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts
+//: the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts
+
const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768;
int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict;
+word if_edges, if_value, if_local;
int ifrcap = NIF, ifxcap = IFN, iftcap = IFT;
int fldst, fldi; // the one struct a bare address's field name resolved to
-[8]word tpn; int ntp; // type parameters in scope
-// reading a return type: unknown names are type parameters declared after the function's name
+char*[8] tpn; int ntp; // type parameters in scope
+//: reading a return type: unknown names are type parameters declared after the function's name
int tvauto;
+word readonly_types;
int dty, dtag; // result of dtype()
-int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_on, s_off, s_new, s_new_t;
+int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t;
int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob;
int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32;
-// ---- o3 region state ----
+//: ---- o3 region state ----
-// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer.
-int o3on, o3nxt, o3one; [32]word o3sl; [32]word o3rg; int no3sl; [O3_REGIONS * 67]word o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; [80]word o3dsc; int o3fs, o3na, o3fn0;
-// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
-[8]word o3free; int no3free, o3nreg; [NL]word o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten;
-// operator names used by numeric type checks.
-[12]word numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval;
+//: each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer.
+int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0;
+//: o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
+word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten;
+//: operator names used by numeric type checks.
+word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval;
int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_nops, fs_ok, xstamp;
-// local lower bound / len(array); one proof record per live loop
-int nfact, factcap = RANGE_FACTS; [RANGE_FACTS]word floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial;
+//: local lower bound / len(array); one proof record per live loop
+int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial;
int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s;
-int mdg; [8]word mdn; // the type parameters of the generic struct whose methods are being compiled
-[8]word gbind; int ngbind; // explicit type arguments for the next call: f<int>(...)
-// generic functions. the template (type parameters stay variables) is compiled once, as before,
-// and its tokens are kept; a call whose type arguments are all known gets an instance, the same
-// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
-// inside it mean the real type. instances are compiled after the item that asked for them.
+int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled
+word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...)
+//: generic functions. the template (type parameters stay variables) is compiled once, as before,
+//: and its tokens are kept; a call whose type arguments are all known gets an instance, the same
+//: tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
+//: inside it mean the real type. instances are compiled after the item that asked for them.
int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read
-int gnew, gnew_np; [8]word gnew_nm; // a generic function was just defined
-word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np; int gt_nm; // the columns are allocated at the first generic function
+int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined
+int fn_method_compile, fnpatchseq;
+word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function
int gi_n; word gi_t, gi_sym, gi_ar, gi_done;
-int ginst; [8]word gnm; [8]word gib; int gng, ginsn; // compiling an instance: the names and what they stand for
+int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for
int s_this;
-// closures being compiled, innermost last: the enclosing function's locals
-// (lo .. hi), how many variables the body has captured so far (names and the
-// enclosing local each came from), and the body's own hidden env parameter
-const int NCLO = 8, NCAP = 32;
+const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */
+const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */
+
+//: ---- closure compilation state ----
+//: closures being compiled, innermost last: the enclosing function's locals
+//: (lo .. hi), how many variables the body has captured so far (names and the
+//: enclosing local each came from), and the body's own hidden env parameter
+
int nclo, closure_cap = NCLO, capture_width = NCAP;
-[NCLO]word clo_lo_initial; word clo_lo = clo_lo_initial;
-[NCLO]word clo_hi_initial; word clo_hi = clo_hi_initial;
-[NCLO]word clo_n_initial; word clo_n = clo_n_initial;
-[NCLO]word clo_env_initial; word clo_env = clo_env_initial;
-[NCLO]word clo_ret_initial; word clo_ret = clo_ret_initial;
-[256]word clo_name_initial; word clo_name = clo_name_initial;
-[256]word clo_loc_initial; word clo_loc = clo_loc_initial;
-[256]word clo_via_initial; word clo_via = clo_via_initial;
+word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial;
+word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial;
+word[NCLO] clo_n_initial; word clo_n = clo_n_initial;
+word[NCLO] clo_env_initial; word clo_env = clo_env_initial;
+word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial;
+char*[256] clo_name_initial; char** clo_name = clo_name_initial;
+word[256] clo_loc_initial; word clo_loc = clo_loc_initial;
+word[256] clo_via_initial; word clo_via = clo_via_initial;
-// ---- errors ----
-// the compiler reports up to 20 errors before it stops. a syntax error
-// (die) skips the token it happened on and sets panic, which skips ahead to
-// the next ; or } (or the next top-level item) and keeps quiet until then.
-// other errors (serr) just report: the parse is still in step.
-//
-// check mode (cmode) is for the language server, which runs the real compiler
-// over a document it holds in memory. nothing is printed and nothing halts:
-// each error is written into dgbuf as a record [severity, line, offset,
-// length, chars...] and giveup raises chkabort, which makes the lexer return
-// end of file so every parsing loop winds down by itself.
+//: ---- errors ----
+
+//: the compiler reports up to 20 errors before it stops. a syntax error
+//: (die) skips the token it happened on and sets panic, which skips ahead to
+//: the next ; or } (or the next top-level item) and keeps quiet until then.
+//: other errors (serr) just report: the parse is still in step.
+//:
+//: check mode (cmode) is for the language server, which runs the real compiler
+//: over a document it holds in memory. nothing is printed and nothing halts:
+//: each error is written into dgbuf as a record [severity, line, offset,
+//: length, chars...] and giveup raises chkabort, which makes the lexer return
+//: end of file so every parsing loop winds down by itself.
int nerr, panic;
-// bits kept in a local's ltag for the hints: assigned after its declaration,
-// read, declared with let, initialized with a literal; the size of the object
-// a new gave it (bits 16 to 39); and how many times it was used (from bit 56)
+//: bits kept in a local's ltag for the hints: assigned after its declaration,
+//: read, declared with let, initialized with a literal; the size of the object
+//: a new gave it (bits 16 to 39); and how many times it was used (from bit 56)
const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
const int NEWTYPED = 0x800000;
-[NL]word lpos_initial; word lpos = lpos_initial; [NL]word lnsz_initial; word lnsz = lnsz_initial; [NL]word lleaf_initial; word lleaf = lleaf_initial; [NL]word lrsoff_initial; word lrsoff = lrsoff_initial; [NL]word lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
-[NL]word lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address
-// storage facts are shared by aliases. they describe one allocation, not a
-// variable slot: [length, lifetime, kind, allocation place, end place, owner].
-// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle.
-[NL]word lafact_initial; word lafact = lafact_initial;
-[NL]word laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact;
-// local name lookup keeps a head for each spelling and a shadow link per slot.
-[NL]word lprev_initial; word lprev = lprev_initial;
-[256]word lkeys_initial; word lkeys = lkeys_initial;
-[256]word lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn;
+word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
+word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address
+//: storage facts are shared by aliases. they describe one allocation, not a
+//: variable slot: [length, lifetime, kind, allocation place, end place, owner].
+//: lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle.
+word[NL] lafact_initial; word lafact = lafact_initial;
+word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact;
+
+//: value previews are separate from optimization facts. each reference keeps
+//: the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes.
+word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv;
+//: local name lookup keeps a head for each spelling and a shadow link per slot.
+word[NL] lprev_initial; word lprev = lprev_initial;
+word[256] lkeys_initial; word lkeys = lkeys_initial;
+word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn;
int s_new_k, s_recycle_leaf;
-// dgprev: the next record sits at the end of the previous token
+//: dgprev: the next record sits at the end of the previous token
+//: chk_hook, when set, is called after each top-level item of the document in check mode: the language
+//: server streams diagnostics and drops a check whose source has been replaced
+word chk_hook;
int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode;
// expand a diagnostic in progress, keeping its output offset within the buffer.
int dg_expand() {
- dgbuf = chk_resize(dgbuf, dgpos + 5 + outn, dgcap * 2);
- dgcap = dgcap * 2; outbuf = dgbuf + dgpos + 5; outcap = dgcap - dgpos - 5;
- return 0;
-}
-// diagnostic records for check mode. dg_open starts a record [severity, line,
-// offset, length, text length, text...] at the current token (or just past the
-// previous one when dgprev is set) and redirects the error text into it with
-// outmode 2; dg_close seals it with the length of the text and restores output.
-// severity 1 is an error, 2 a warning, and 4 a hint.
+ dgbuf = chk_resize(dgbuf, dgpos + 5 + rt.outn, dgcap * 2);
+ dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5;
+ return 0;
+}
+//: diagnostic records for check mode. dg_open starts a record [severity, line,
+//: offset, length, text length, text...] at the current token (or just past the
+//: previous one when dgprev is set) and redirects the error text into it with
+//: outmode 2; dg_close seals it with the length of the text and restores output.
+//: severity 1 is an error, 2 a warning, and 4 a hint.
+//: completion: the language server asks what is in scope at a place in the document. probe_off is that place
+//: (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or
+//: before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after
+//: a dot, what is before the dot. the check stops as soon as it has what was asked for
+int probe_off = -1, probe_has, probe_n, probe_cap, probe_static;
+char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem;
+char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include
int dg_close() {
if (!dgopen) return 0;
- dgbuf[dgpos + 4] = outn; dgpos = dgpos + 5 + outn; dgn++;
- dgopen = 0; outmode = dgmode; outgrow = dggrow;
+ if (!dgdrop) { dgbuf[dgpos + 4] = rt.outn; dgpos = dgpos + 5 + rt.outn; dgn++; }
+ dgdrop = 0;
+ dgopen = 0; rt.outmode = dgmode; rt.outgrow = dggrow;
return 0;
}
// open a check-mode diagnostic record of the given severity.
@@ -344,24 +407,31 @@ int dg_open(int sev) {
}
else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; }
dgbuf[dgpos] = sev;
- outbuf = dgbuf + dgpos + 5; outn = 0; outcap = dgcap - dgpos - 5;
- dggrow = outgrow; outgrow = dg_expand;
- outmode = 2; dgopen = 1;
+ // inside an included file the document has no place for the finding: hints and warnings about a library are
+ // left out, and an error is shown on the include that brought the file in, with where it really is
+ if (incdepth > 0) {
+ dgbuf[dgpos + 1] = incplace & 0xFFFFFF; dgbuf[dgpos + 2] = ((incplace >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (incplace >> 24) & 0xFFFF;
+ if (sev != 1) dgdrop = 1;
+ }
+ rt.outbuf = dgbuf + dgpos + 5; rt.outn = 0; rt.outcap = dgcap - dgpos - 5;
+ dggrow = rt.outgrow; rt.outgrow = dg_expand;
+ rt.outmode = 2; dgopen = 1;
+ if (incdepth > 0 && sev == 1) { rt.put_s("in "); rt.put_s(curinc); rt.put_s(", line "); rt.put_n(tline); rt.put_s(": "); }
return 0;
}
-[256]word dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n;
+word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n;
// start and stop a check run. chk_begin points the diagnostics at a buffer
// the caller owns; chk_end flushes any open record and restores output.
int chk_begin(int buf, int cap) {
cmode = 1; chkabort = 0; chkfuel = 0; dgbuf = buf; dgcap = cap; dgpos = 0; dgn = 0; dgopen = 0;
- nerr = 0; panic = 0; dgmode = outmode; fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0;
+ nerr = 0; panic = 0; dgmode = rt.outmode; rt.fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0;
return 0;
}
// end check mode and put the output back the way it was.
int chk_end() {
dg_close();
cmode = 0;
- outmode = dgmode;
+ rt.outmode = dgmode;
return 0;
}
// a hint is a finding that isn't an error: the program still compiles, and
@@ -385,13 +455,36 @@ int dg_dup() {
while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); }
dgseen[i] = key; dgseen_n++; return 0;
}
-// start a check-mode hint at token position p, or say no when hints are off.
-int hint_begin(int p) {
+//: hints on; hints small; hints off; set how many hints the code after it gets:
+//: on uses informational ranges, small uses compact hint markers, and off
+//: suppresses hints. each directive is kept with its byte
+//: offset, so a hint is judged by where its code is, not where it was reported.
+const int HINT_SMALL = 1, HINT_ALL = 2;
+word hl_off, hl_lev; int hl_n, hl_cap = 16;
+int hint_set(int level, int at) {
+ if (!cmode || dry || isp != 0 || chkind != 3) return 0;
+ if (hl_n && hl_off[hl_n - 1] >= at) return 0;
+ if (!hl_off) { hl_cap = 16; hl_off = chk_resize(0, 0, hl_cap); hl_lev = chk_resize(0, 0, hl_cap); }
+ if (hl_n >= hl_cap) { hl_off = chk_resize(hl_off, hl_n, hl_cap * 2); hl_lev = chk_resize(hl_lev, hl_n, hl_cap * 2); hl_cap = hl_cap * 2; }
+ hl_off[hl_n] = at; hl_lev[hl_n] = level; hl_n++;
+ return 0;
+}
+// the hint level at byte offset o: 2 all, 1 small, 0 off
+int hint_level(int o) {
+ int k = hl_n;
+ while (k) { k--; if (hl_off[k] <= o) return hl_lev[k]; }
+ return 2;
+}
+// start a check-mode hint at token position p, or say no when hints are off or
+// this kind (HINT_SMALL or HINT_ALL) is not wanted there.
+int hint_begin(int p, int kind) {
int sv, ok;
if (!cmode || dry || panic || chkabort || stubgen) return 0;
+ int level = hint_level(((p >> 40) & 0x1FFFFF) - 1);
+ if (!level) return 0;
sv = tpack(); tunpack(p);
ok = !dg_dup();
- if (ok) { dg_open(4); ok = dgopen; }
+ if (ok) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; }
tunpack(sv);
return ok;
}
@@ -405,9 +498,9 @@ int errat() {
if (cmode) {
dg_open(1);
} else {
- put_s("zc: line ");
- put_n(tline);
- put_s(": ");
+ rt.put_s("zc: line ");
+ rt.put_n(tline);
+ rt.put_s(": ");
}
nerr++;
return 0;
@@ -415,45 +508,45 @@ int errat() {
// finish an error line; the compiler stops after 20 errors.
int errend() {
if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; }
- put_c(10);
- if (nerr >= 20) { put_s("zc: too many errors, stopping\n"); halt(); }
+ rt.put_c(10);
+ if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); rt.halt(); }
return 0;
}
// stop compiling after errors and report how many there were.
int giveup() {
if (cmode) { dg_close(); chkabort = 1; return 0; }
- put_s("zc: "); put_n(nerr); if (nerr == 1) put_s(" error\n"); else put_s(" errors\n"); halt();
+ rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); rt.halt();
return 0;
}
// report a fatal error and stop, or record it in check mode.
-int fatal(int msg) {
- errat(); put_s(msg);
+int fatal(char* msg) {
+ errat(); rt.put_s(msg);
if (cmode) { dg_close(); chkabort = 1; return 0; }
- put_c(10); halt();
+ rt.put_c(10); rt.halt();
return 0;
}
// report a syntax error at the current token, once per recovery.
-int serr(int msg) {
+int serr(char* msg) {
if (!panic && !dry) {
errat();
- put_s(msg);
+ rt.put_s(msg);
errend();
}
return 0;
}
// report an error that names something, then the message.
-int serrn(int name, int msg) {
+int serrn(char* name, char* msg) {
if (!panic && !dry) {
errat();
- put_s(name);
- put_s(": ");
- put_s(msg);
+ rt.put_s(name);
+ rt.put_s(": ");
+ rt.put_s(msg);
errend();
}
return 0;
}
// report an error and skip to the next top-level item.
-int die(int msg) {
+int die(char* msg) {
serr(msg);
panic = 1;
if (tk == T_EOF) giveup();
@@ -462,19 +555,19 @@ int die(int msg) {
}
// what the parser is looking at, for the "found" part of a message
int tokshow() {
- if (tk == T_EOF) { put_s("the end of the file"); return 0; }
- if (tk == T_NUM) { put_s("the number "); put_n(tv); return 0; }
- if (tk == T_STR) { put_s("a string"); return 0; }
- if (tk == T_BSTR) { put_s("a byte string"); return 0; }
- if (tk == T_FLT) { put_s("a float"); return 0; }
- put_s("'"); put_s(tv); put_s("'");
+ if (tk == T_EOF) { rt.put_s("the end of the file"); return 0; }
+ if (tk == T_NUM) { rt.put_s("the number "); rt.put_n(tv); return 0; }
+ if (tk == T_STR) { rt.put_s("a string"); return 0; }
+ if (tk == T_BSTR) { rt.put_s("a byte string"); return 0; }
+ if (tk == T_FLT) { rt.put_s("a float"); return 0; }
+ rt.put_s("'"); rt.put_s(tv); rt.put_s("'");
return 0;
}
// like die, and says what was found instead
-int diefound(int msg) {
+int diefound(char* msg) {
if (!panic && !dry) {
errat();
- put_s(msg); put_s(", found "); tokshow();
+ rt.put_s(msg); rt.put_s(", found "); tokshow();
errend();
}
panic = 1;
@@ -483,15 +576,15 @@ int diefound(int msg) {
return 0;
}
// die with a name shown before the message.
-int dien(int name, int msg) {
+int dien(char* name, char* msg) {
serrn(name, msg);
panic = 1;
if (tk == T_EOF) giveup();
next();
return 0;
}
-// four classification bits live next to each interned spelling. this works
-// for names in later pool chunks too, without indexing by a fixed pool offset.
+//: four classification bits live next to each interned spelling. this works
+//: for names in later pool chunks too, without indexing by a fixed pool offset.
const int LSW = 1;
word lsets;
int lsmark(int set, int n) {
@@ -502,19 +595,19 @@ word lsget(int set, int n) {
if (!lsets) return 0;
return (n[-2] >> set) & 1;
}
-// name resolution for the language server. while a check runs over the document
-// at rsbase, every name a use or a declaration resolves to is written into rsrec
-// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD),
-// declaration or symbol, and storage facts at that position. a local names its declaration by offset
-// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the
-// compiler's own scopes decide, so the highlighter and hover see locals and
-// parameters first, then globals and built-ins, the way the compiler does.
-const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 9;
+//: name resolution for the language server. while a check runs over the document
+//: at rsbase, every name a use or a declaration resolves to is written into rsrec
+//: as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD),
+//: declaration or symbol, and storage facts at that position. a local names its declaration by offset
+//: (-1 when it has none), a symbol names its kind and flags (see rs_sym). the
+//: compiler's own scopes decide, so the highlighter and hover see locals and
+//: parameters first, then globals and built-ins, the way the compiler does.
+const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13;
int lt_on, lt_n, ltcap = 256, ltxtcap = 2048;
word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn;
int ht_n, htcap = 256; word ht_start, ht_end, ht_type;
-int fieldn, fieldcap = 128; word fieldtypes, fieldnames, fieldoffs;
-int field_note(word t, word n, int off) {
+int fieldn, fieldcap = 128; word fieldtypes, fieldoffs; char** fieldnames;
+int field_note(word t, char* n, int off) {
if (!lt_on || isp != 0 || dry) return 0;
if (!fieldtypes || fieldn == fieldcap) {
if (fieldtypes) fieldcap = fieldcap * 2;
@@ -525,18 +618,32 @@ int field_note(word t, word n, int off) {
fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++;
return 0;
}
-int field_decl(word t, word n) {
+int field_decl(word t, char* n) {
int k = 0;
while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; }
return 0 - 1;
}
-word rsrec, rstmp; [32]word pnoffs; int rsn, rscap, rsbase, nameoff;
-// resize check-mode tables; their global pointers keep them visible to gc.
-unchecked word chk_resize(word old, int count, int capacity) {
- word p = new(capacity);
- p[-4] = p[-4] | F_INTERNAL;
- fill_w(p, 0, capacity);
- if (old && count) copy_w(p, old, count);
+//: where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1
+//: (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document)
+word sdoff, sdsrc;
+//: includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the
+//: workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor
+//: sent (a host with no files of its own), incres the path an include was found at
+char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done;
+word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff;
+//: ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when
+//: it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from.
+//: transfers are logged as events, EV_W words each: the local's declaration offset, what
+//: happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one.
+const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4;
+word evrec; int evn, evcap = 64;
+// resize compiler tables. these buffers are collector-managed: m* relinquishes
+// scope cleanup, and their local or global pointers keep them visible to gc.
+word chk_resize(word old, int count, int capacity) {
+ word p = m*rt.new(capacity);
+ p[-4] = p[-4] | rt.F_INTERNAL;
+ rt.fill_w(p, 0, capacity);
+ if (old && count) rt.copy_w(p, old, count);
return p;
}
// lexer and documentation buffers grow before storing the next character.
@@ -566,7 +673,11 @@ int reserve_symbols(int need) {
sfix = chk_resize(sfix, nsym, cap);
snext = chk_resize(snext, nsym, cap);
stype = chk_resize(stype, nsym, cap);
+ if (effect_symbols) effect_symbols = chk_resize(effect_symbols, nsym, cap);
+ if (fnmeta) fnmeta = chk_resize(fnmeta, nsym, cap);
if (ifs) ifs = chk_resize(ifs, symcap, cap);
+ if (svkind) { svkind = chk_resize(svkind, nsym, cap); svvalue = chk_resize(svvalue, nsym, cap); }
+ if (sdoff) { sdoff = chk_resize(sdoff, nsym, cap); sdsrc = chk_resize(sdsrc, nsym, cap); }
symcap = cap; return cap;
}
int reserve_locals(int need) {
@@ -584,12 +695,21 @@ int reserve_locals(int need) {
ltype = chk_resize(ltype, nloc, cap);
ltag = chk_resize(ltag, nloc, cap);
lpos = chk_resize(lpos, nloc, cap);
+ lreadpos = chk_resize(lreadpos, nloc, cap);
lnsz = chk_resize(lnsz, nloc, cap);
lleaf = chk_resize(lleaf, nloc, cap);
lrsoff = chk_resize(lrsoff, nloc, cap);
lrskind = chk_resize(lrskind, nloc, cap);
lptr = chk_resize(lptr, nloc, cap);
+ if (if_local) if_local = chk_resize(if_local, nloc, cap);
+ if (local_origin) local_origin = chk_resize(local_origin, nloc, cap);
+ if (local_origin_mask) local_origin_mask = chk_resize(local_origin_mask, nloc, cap);
+ if (local_cell) local_cell = chk_resize(local_cell, nloc, cap);
+ if (local_captured) local_captured = chk_resize(local_captured, nloc, cap);
+ if (local_ended) local_ended = chk_resize(local_ended, nloc, cap);
+ if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap);
lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap);
+ if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); }
o3cache = chk_resize(o3cache, nloc, cap);
loccap = cap; return cap;
}
@@ -699,10 +819,36 @@ int rs_put(int off, int kind, int ref, int sym) {
}
at = rsn * RS_W;
rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym;
- fill_w(rsrec + at + 4, 0, RS_W - 4);
+ rt.fill_w(rsrec + at + 4, 0, RS_W - 4);
rsn++;
return 0;
}
+// the ownership of local i at the record for the name at off
+int os_at(int off, int i) {
+ int at, r, k = rsn;
+ if (!rsn || !cmode || dry || i < 0 || chkind != 3 || chbase != rsbase) return 0;
+ while (k && rsrec[(k - 1) * RS_W] != off) k--;
+ if (!k) return 0;
+ at = (k - 1) * RS_W;
+ rsrec[at + 11] = lstate[i] + 1;
+ if (lown[i]) rsrec[at + 11] = rsrec[at + 11] + 16;
+ if (lstate[i] == S_CHILD) { r = lroot(i); if (lrsoff[r] >= 0) rsrec[at + 12] = lrsoff[r] + 1; }
+ return 0;
+}
+// log a transfer of local i; the last few events are searched so a loop body is not logged twice
+int ev_put(int i, int kind, int target) {
+ int k = evn, n = 0, t = 0;
+ if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || i < 0 || lrsoff[i] < 0) return 0;
+ if (target >= 0 && lrsoff[target] >= 0) t = lrsoff[target] + 1;
+ while (k && n < 8) {
+ k--; n++;
+ if (evrec[k * EV_W] == lrsoff[i] && evrec[k * EV_W + 1] == kind && evrec[k * EV_W + 2] == tline) return 0;
+ }
+ if (!evrec) { evcap = 64; evrec = chk_resize(0, 0, evcap * EV_W); }
+ if (evn >= evcap) { evrec = chk_resize(evrec, evn * EV_W, evcap * 2 * EV_W); evcap = evcap * 2; }
+ evrec[evn * EV_W] = lrsoff[i]; evrec[evn * EV_W + 1] = kind; evrec[evn * EV_W + 2] = tline; evrec[evn * EV_W + 3] = t;
+ evn++; return 0;
+}
// write a record for the name token that is current.
int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); }
// the flags of every symbol record, read again once the check has defined the
@@ -710,7 +856,10 @@ int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); }
int rs_fix() {
int i = 0;
while (i < rsn) {
- if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) rsrec[i * RS_W + 2] = rs_sym(rsrec[i * RS_W + 3]);
+ if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) {
+ int s = rsrec[i * RS_W + 3]; rsrec[i * RS_W + 2] = rs_sym(s);
+ if (skind[s] == K_CONST) vf_at(i, 1, sval[s]);
+ }
i++;
}
return 0;
@@ -725,7 +874,7 @@ int rs_sym(int s) {
}
// copies record a of from to record b of to (the merge sort moves records this way).
int rs_move(word from, int a, word to, int b) {
- copy_w(to + b * RS_W, from + a * RS_W, RS_W);
+ rt.copy_w(to + b * RS_W, from + a * RS_W, RS_W);
return 0;
}
// sort the records by offset: a bottom-up merge sort through rstmp, so a long
@@ -755,29 +904,44 @@ int rs_sort() {
return 0;
}
// analyzer errors only count in checked, reachable code
-int cerr(int msg) {
+int cerr(char* msg) {
if (fchk && live) serr(msg);
return 0;
}
// report an analyzer error about local i, named in the message.
-int cerrn(int i, int msg) {
- if (fchk && live) serrn(lname[i], msg);
- return 0;
+int cerrn(int i, char* msg) {
+ int root = i, cause = 0; word info = 0;
+ if (fchk && live) {
+ if (rt.eq_w(msg, "used after it was given away")) {
+ root = lroot(i); if (local_end_info) info = local_end_info[i];
+ if (!info && local_end_info) info = local_end_info[root];
+ if (info) cause = info[2]; else if (local_ended) cause = local_ended[root];
+ }
+ if (cause && !panic && !dry) {
+ errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(msg);
+ rt.put_s("; owner "); if (info) rt.put_s(info[0]); else rt.put_s(lname[root]);
+ rt.put_s(" was transferred on line "); rt.put_n(cause & 0xFFFFFF);
+ if (info && info[1]) { rt.put_s(" (declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); }
+ errend();
+ } else serrn(lname[i], msg);
+ }
+ return 0;
}
-// ---- interning ----
+//: ---- interning ----
+
// the pool: an open addressing hash table (R_IH) of pointers into a string
// pool (R_POOL). each spelling has two spare words: its symbol-chain head at
// p[-1] and editor classification bits at p[-2]. equal text returns the same
// stable handle, which lookup can use directly.
-int hashs(word s) {
+int hashs(char* s) {
int h = 0;
while (*s) { h = h * 31 + *s; s++; }
return h & (hashcap - 1);
}
// rehash the index without moving strings or their symbol-chain heads.
int reserve_names(int need) {
- int cap = hashcap, previous = hashcap, i = 0, h; word p, old;
+ int cap = hashcap, previous = hashcap, i = 0, h; char* p; char** old;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
old = namehash;
@@ -791,17 +955,17 @@ int reserve_names(int need) {
return cap;
}
// return the interned copy of a spelling. new chunks preserve every old handle.
-word intern(word s) {
+char* intern(char* s) {
int h; word p; int n;
if ((namecount + 1) * 2 >= hashcap) reserve_names(hashcap * 2);
h = hashs(s);
while (1) {
p = namehash[h];
if (!p) break;
- if (p[0] == s[0] && eq_w(p, s)) return p;
+ if (p[0] == s[0] && rt.eq_w(p, s)) return p;
h = (h + 1) & (hashcap - 1);
}
- n = len_w(s);
+ n = rt.len_w(s);
if (pooltop + n + 3 > poolend) {
if (poolchunk < n + 3) poolchunk = n + 3;
else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2;
@@ -810,18 +974,26 @@ word intern(word s) {
}
pooltop[0] = 0; pooltop[1] = 0;
p = pooltop + 2;
- copy_w(p, s, n + 1);
+ rt.copy_w(p, s, n + 1);
pooltop = pooltop + n + 3;
namehash[h] = p; namecount++;
return p;
}
-// ---- symbols ----
+//: ---- symbols ----
+
// each interned name keeps the index (+1) of its newest symbol in p[-1], and
// each symbol links to the one it shadows, so lookup is one step.
-int addsym(word name, int kind, int val) {
+// remember where symbol s is defined, when the prescan made it before its definition was read
+int def_note(int s, int off) {
+ if (cmode && !dry && !ginst && sdoff && off >= 0 && !sdoff[s]) { sdoff[s] = off + 1; sdsrc[s] = curinc; }
+ return 0;
+}
+int addsym(char* name, int kind, int val) {
int i = nsym;
+ if (module_name && kind != K_KW) name = module_symbol(name);
if (nsym >= symcap) reserve_symbols(nsym + 1);
+ if (!fnmeta) fnmeta = chk_resize(0, 0, symcap);
nsym++;
sname[i] = name;
skind[i] = kind;
@@ -829,27 +1001,88 @@ int addsym(word name, int kind, int val) {
ssig[i] = 0;
sflag[i] = F_DEF;
sfix[i] = 0;
+ fnmeta[i] = 0;
stype[i] = 0;
snext[i] = name[-1];
name[-1] = i + 1;
+ if (cmode && !dry && !sdoff) { sdoff = chk_resize(0, 0, symcap); sdsrc = chk_resize(0, 0, symcap); }
+ if (sdoff) {
+ sdoff[i] = 0; sdsrc[i] = 0;
+ if (cmode && !dry && !ginst && nameoff >= 0) { sdoff[i] = nameoff + 1; sdsrc[i] = curinc; }
+ }
return i;
}
// while an image builds, names below vislo are hidden, except keywords
-int lookup(word name) {
- int i = name[-1] - 1;
+int lookup_raw(char* name) {
+ int i = name[-1] - 1, resident;
+ if (syntax_host_floor && i >= syntax_host_floor && i < syntax_host_from) {
+ resident = i;
+ while (resident >= syntax_host_floor) resident = snext[resident] - 1;
+ if (resident >= 0) return resident;
+ if (skind[i] == K_VAR) serr("a syntax handler cannot access emitted program globals");
+ }
while (i >= 0) {
if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i;
i = snext[i] - 1;
}
return -1;
}
+// qualify a declaration within its file namespace; generated method names retain their prefix.
+char* module_symbol(char* name) {
+ int i = 0, j; char* b; char* q;
+ while (name[i]) { if (name[i] == '.') return name; i++; }
+ if (!module_name) return name;
+ if (((name[0] == 'd' && name[1] == 'o' && name[2] == 'c') || (name[0] == 's' && name[1] == 'i' && name[2] == 'g')) && name[3] == 32) {
+ q = methname(module_name, name + 4); b = chk_resize(0, 0, rt.len_w(q) + 5);
+ rt.copy_w(b, name, 4);
+ j = 0; while (q[j]) { b[j + 4] = q[j]; j++; } b[j + 4] = 0;
+ return intern(b);
+ }
+ return methname(module_name, name);
+}
+int lookup(char* name) {
+ int s, i = 0;
+ if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; }
+ s = lookup_raw(name); if (s >= 0) return s;
+ while (name[i]) { if (name[i] == '.') return -1; i++; }
+ return lookup_raw(methname("rt", name));
+}
+// modules loaded from source are tracked independently of each file's imports.
+word included_modules;
+int module_loaded(char* name) {
+ word p = included_modules;
+ while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; }
+ return 0;
+}
+int module_known(char* name) {
+ return module_loaded(name) || rt.eq_w(name, "rt") || rt.eq_w(name, "run") || rt.eq_w(name, "core") || rt.eq_w(name, "lsp") || rt.eq_w(name, "std") || rt.eq_w(name, "icon");
+}
+int module_imported(char* name) {
+ word p = module_imports;
+ if (module_name && rt.eq_w(name, module_name)) return 1;
+ while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; }
+ return 0;
+}
+// source references require their module's explicit import; compiler-generated calls use lookup directly.
+int source_lookup(char* name) {
+ int i = 0, s; char* owner;
+ while (name[i] && name[i] != '.') i++;
+ if (name[i]) {
+ owner = chk_resize(0, 0, i + 1); rt.copy_w(owner, name, i); owner[i] = 0;
+ if ((module_known(owner) || module_loaded(owner)) && !module_imported(owner)) {
+ errat(); rt.put_s("module access requires `include "); rt.put_s(owner); rt.put_s(";`"); errend(); return -1;
+ }
+ }
+ if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; }
+ return lookup_raw(name);
+}
// drop the symbols above index to, which ends a scope.
int popsyms(int to) {
while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; }
return 0;
}
// look a name up from a plain string.
-word lookups(int s) { return lookup(intern(s)); }
+int lookups(char* s) { return lookup(intern(s)); }
// patch nodes
int fixnew(int site, int link) {
@@ -872,7 +1105,8 @@ int fixall(int head, int v) {
return 0;
}
-// ---- code emission ----
+//: ---- code emission ----
+
// code goes straight into memory at "here". hdelta shifts where it is written
// (an image build emits at one address and stores at another, see nested
// compiles), and dry turns emission off entirely, which typeof and similar
@@ -898,9 +1132,9 @@ int reserve_code(int need) {
if (here + 1 >= hlimit) fatal("no room for a code link");
cap = code_chunk;
if (cap < need + 5) cap = need + 5;
- p = chk_resize(0, 0, cap); top = p + p[-5] - HDR;
- if (syscall(9, 9, top, 0) < top) fatal("code address reservation failed");
- p[0] = code_blocks; p[1] = p + 3; p[2] = top; code_blocks = p;
+ p = chk_resize(0, 0, cap); top = p + p[-5] - rt.HDR;
+ if (rt.syscall(9, 9, top, 0) < top) fatal("code address reservation failed");
+ p[0] = rt.code_blocks; p[1] = p + 3; p[2] = top; rt.code_blocks = p;
*here = (p + 3) * 2 + 1; here[1] = O_JMP * 2;
here = p + 3; hlimit = top;
if (cap < 0x20000000) code_chunk = cap * 2;
@@ -908,8 +1142,8 @@ int reserve_code(int need) {
}
// a region handed to an optimizer must fit in one contiguous segment.
int code_contiguous(int lo, int hi) {
- word p = code_blocks;
- if (image_active || (lo >= 0 && hi <= CODE_END)) return 1;
+ word p = rt.code_blocks;
+ if (image_active || (lo >= 0 && hi <= rt.CODE_END)) return 1;
while (p) { if (lo >= p[1] && hi <= p[2] - 2) return 1; p = p[0]; }
return 0;
}
@@ -949,7 +1183,7 @@ int jmp(int t) {
int hole() {
int a;
reserve_code(1); a = here;
- lit(MW - 1);
+ lit(rt.MW - 1);
return a;
}
// forward references. calling a function that is not defined yet creates a
@@ -958,26 +1192,31 @@ int hole() {
// itself to the symbol's chain (sfix); defining the function runs fixall to
// patch every site. MW-1 is a jump target that halts, so an unpatched call
// stops the machine instead of running garbage.
-int fwdfn_at(int name, int p) {
+int fwdfn_at(char* name, int p) {
int s = addsym(name, K_FN, p);
sflag[s] = 0;
return s;
}
// the place of the first use (line, and offset above bit 24), until it's defined
-int fwdfn(int name) {
+int fwdfn(char* name) {
return fwdfn_at(name, tpack());
}
// emit a reference to function s, patched later if it is not defined yet.
int fnref(int s) {
+ if (syntax_host_floor && s >= syntax_host_floor && s < syntax_host_from) {
+ s = syntax_host_function(s);
+ if (s < 0) { lit(rt.MW - 1); return 0; }
+ }
if (sflag[s] & F_DEF) { lit(sval[s]); return 0; }
if (dry) return 0;
reserve_code(1); sfix[s] = fixnew(here, sfix[s]);
- lit(MW - 1); // a jump to MW-1 halts if it ever runs unpatched
+ lit(rt.MW - 1); // a jump to MW-1 halts if it ever runs unpatched
return 0;
}
// emit a call to the function with this name, making a forward slot if it is not defined.
-int callname(int name) {
+int callname(char* name) {
int s = lookup(name);
+ if (s < 0) { name = methname("rt", name); s = lookup(name); }
if (s < 0) s = fwdfn(name);
fnref(s);
op(O_CALL);
@@ -986,7 +1225,7 @@ int callname(int name) {
// the address of local slot k: the frame pointer lives in the word ZFP, and a
// local is fp + k. (o3 locals live in cpu registers instead; see vaddr.)
int laddr(int k) {
- lit(ZFP);
+ lit(rt.ZFP);
op(O_LD);
if (k) { lit(k); op(O_ADD); }
return 0;
@@ -1003,11 +1242,12 @@ word vaddr(int i) {
return 0;
}
}
- return laddr(lslot[i]);
+ laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0;
}
-// registers for an o3 region's variables: the cpu has 7. a variable takes the
-// one its region freed last (when a scope closed), or the next unused one
-// ---- o3: the optimizing region compiler's bookkeeping ----
+//: registers for an o3 region's variables: the cpu has 7. a variable takes the
+//: one its region freed last (when a scope closed), or the next unused one
+//: ---- o3: the optimizing region compiler's bookkeeping ----
+
// code inside an o3 region (or a function marked o3) keeps hot locals in the
// cpu's registers. the compiler does not change what it emits for them; it
// records which frame slot maps to which register and hands the finished
@@ -1063,7 +1303,7 @@ int o3promote(int i) {
o3rg[no3sl] = r; no3sl++;
return 0;
}
-// a fixed []T can't change its items or its length, so o3 code keeps both in
+// a fixed T[?] can't change its items or its length, so o3 code keeps both in
// two hidden variables, loaded when the array variable is set (or when the
// region starts), and indexes through them
int o3mkcache(int i) {
@@ -1090,7 +1330,7 @@ int o3region(int lo, int hi, word sl, word rg, int n) {
o3dsc[0] = 0; o3dsc[1] = 2; o3dsc[2] = lo; o3dsc[3] = hi; o3dsc[4] = n;
while (i < n) { o3dsc[5 + i + i] = sl[i]; o3dsc[6 + i + i] = rg[i]; i++; }
o3dsc[5 + n + n] = o3fs; o3dsc[6 + n + n] = o3na; o3dsc[7 + n + n] = o3fn0;
- install(o3dsc);
+ rt.install(o3dsc);
return 0;
}
// o3 blocks go to the cpu once their function is done, so every jump in them is final
@@ -1103,10 +1343,10 @@ int o3flush() {
}
// a string literal: the characters (one per word, then a 0) are emitted into
// the code stream behind a jump, and the literal's value is their address
-int strlit(word s) {
+int strlit(char* s) {
int j = hole(), a;
op(O_JMP);
- reserve_code(len_w(s) + 1); a = here;
+ reserve_code(rt.len_w(s) + 1); a = here;
while (*s) { emit(*s); s++; }
emit(0);
setlit(j, here);
@@ -1114,7 +1354,8 @@ int strlit(word s) {
return 0;
}
-// ---- input sources ----
+//: ---- input sources ----
+
// frame: 0 kind (0 stdin, 1 token list, 2 string) 1 object 2 index 3 end
// 4 transparent 5..7 saved token 8..10 saved char state 11 owned 12 saved line
// 13 saved token line 14 saved lookahead count 15..30 saved lookahead 31 saved char buffer
@@ -1152,14 +1393,16 @@ int srcend(int c) {
}
// the character level of the lexer: ch is the current char (-1 at the end),
// and chkind says where it comes from (0 stdin, 3 a byte string, anything
-// else a word string). a stdin source ends at a backtick (see srcend).
+// else a zero-terminated char* string). a stdin source ends at a backtick (see srcend).
int nextch() {
if (chkind == 3) { // the usual case: the unit readsrc packed
ch = (chbase[chptr >> 2] >> ((chptr & 3) * 8)) & 255;
if (ch) chptr++; else ch = -1;
} else if (chkind == 0) {
- if (qdone || eof()) { ch = -1; return 0; }
- ch = get_c() & 255;
+ if (qdone || rt.eof()) { ch = -1; return 0; }
+ ch = rt.get_c() & 255;
+ // a read that waited for a slow pipe and found its end returns -1: that is the end, not a 0xFF byte
+ if (ch == 255 && rt.eof()) { qdone = 1; ch = -1; return 0; }
if (srcend(ch)) { qdone = 1; ch = -1; return 0; } // a backtick ends the source
} else {
ch = *chptr & 255;
@@ -1191,7 +1434,7 @@ int cpos() {
// saved into a 32 word frame so src_pop can put them back exactly. kind 0 is
// stdin, 1 a token list (obj[0] is the count, then triples of kind, value and
// packed place: this is how macros, quote/grab and feed replay code), 2 a
-// word string and 3 a byte string. trans means "transparent": when a token
+// char* string and 3 a byte string. trans means "transparent": when a token
// list runs out the reader pops it and carries on in the source below, which
// is what lets a fed list splice into the text around it. owned means the
// frame frees obj when it pops.
@@ -1231,7 +1474,7 @@ int src_pop() {
i++;
}
isp--;
- if (f[11]) free(f[1]);
+ if (f[11]) rt.free(f[1]);
f[1] = 0; // a dead source pointer would pin a quarantined block
return 0;
}
@@ -1317,9 +1560,8 @@ int setstr(int i, int f) {
}
// b"...": length word, then bytes packed 8 per word, little-endian, and a 0
// after the last byte. a jump skips the data. the address pushed is the length.
-int bstrlit(word s) {
- int j = hole(), a, n = 0, i = 0, w, k;
- op(O_JMP);
+int bstrdata(char* s) {
+ int a, n = 0, i = 0, w, k;
while (s[n]) n++;
reserve_code((n >> 3) + 2); a = here;
emit(n);
@@ -1329,11 +1571,14 @@ int bstrlit(word s) {
emit(w);
}
if (!(n & 7)) emit(0);
- setlit(j, here);
- lit(a);
- return 0;
+ return a;
}
-// ---- float literals and float code ----
+int bstrlit(char* s) {
+ int j = hole(), a;
+ op(O_JMP); a = bstrdata(s); setlit(j, here); lit(a); return 0;
+}
+//: ---- float literals and float code ----
+
// a float is one word holding the ieee bits. the lexer collects the literal's
// text in tbuf and fbits asks the core (opcode 611) to convert it, so the
// compiler needs no float parser of its own. float operations are single vm
@@ -1399,6 +1644,8 @@ int ftest() {
// a float stored into an int becomes an int, and an int stored into a float
// becomes a float. word keeps the bits and drops the float mark.
int fcoerce(word dst) {
+ if (dst && treadonly(xty) && isref(xty) && !treadonly(dst) && dst[0] != TY_VAR) serr("const storage cannot be converted to a mutable reference");
+ vf_coerce(dst, 1);
if (!dst || dst[0] == TY_VAR) return 0;
if (isfl(dst)) {
if (!xfloat) { op(OP_I2F); xfloat = 1; xty = tfloat; }
@@ -1476,13 +1723,18 @@ int fieldscan(int n) {
// doc comment being collected, if the line above was part of it, or starts a
// new one; a comment after code on the same line is not documentation.
int doccomment(int nl) {
- int cl = line;
+ int cl = line, start;
nextch();
+ if (ch == ':') { while (ch >= 0 && ch != 10) nextch(); dn = 0; dsect = 1; dlast = cl; docpending = 1; return 0; }
if (!nl) { while (ch >= 0 && ch != 10) nextch(); return 0; }
- if (dn && dlast != cl - 1) dn = 0;
+ if (dlast != cl - 1) { dn = 0; dsect = 0; }
if (ch == 32) nextch();
+ start = dn;
while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); }
- while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 13)) dn--;
+ while (dn > start && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 13)) dn--;
+ // a comment block with a section heading is section text: it documents nothing
+ if (dn - start >= 4 && dbuf[start] == '-' && dbuf[start + 1] == '-' && dbuf[dn - 1] == '-' && dbuf[dn - 2] == '-') dsect = 1;
+ if (dsect) { dn = 0; dlast = cl; docpending = 1; return 0; }
doc_char(10);
dlast = cl; docpending = 1;
return 0;
@@ -1490,8 +1742,9 @@ int doccomment(int nl) {
// block documentation can end on the declaration's line. remove the optional
// leading star on each line, while leaving markdown inside the comment intact.
int docblock(int nl) {
- int cl = line, bol = 1, c;
+ int cl = line, bol = 1, c, adj = dsect && dlast == cl - 1;
if (!nl || (dn && dlast != cl - 1)) dn = 0;
+ if (!adj) dsect = 0;
nextch();
if (ch == '*') { nextch(); if (ch == '/') { nextch(); return 0; } }
while (ch >= 0) {
@@ -1507,6 +1760,7 @@ int docblock(int nl) {
}
while (dn && (dbuf[dn - 1] == 32 || dbuf[dn - 1] == 9 || dbuf[dn - 1] == 10 || dbuf[dn - 1] == 13)) dn--;
doc_char(10); dlast = cl; docpending = 1;
+ if (adj) dn = 0;
return 0;
}
// the lexer proper: skip white space and comments, then read one token into
@@ -1516,8 +1770,31 @@ int docblock(int nl) {
// white space, 0 when it does not, and 3 for a char literal (so the magic
// number hint can leave 'a' alone). asm blocks use it to read -5 as one
// literal.
+// commented numeric literals are recorded by source offset, including lookahead.
+word literal_docs; int literal_doc_n, literal_doc_cap = 32, token_block_doc;
+int literal_documented(int off) {
+ int key = off + 1, i;
+ if (!literal_doc_n || key <= 0) return 0;
+ i = dg_hash(key, literal_doc_cap);
+ while (literal_docs[i]) { if (literal_docs[i] == key) return 1; i = (i + 1) & (literal_doc_cap - 1); }
+ return 0;
+}
+int literal_doc_note(int off) {
+ int key = off + 1, i, cap, k; word old;
+ if (literal_documented(off)) return 0;
+ if (!literal_docs) literal_docs = chk_resize(0, 0, literal_doc_cap);
+ if ((literal_doc_n + 1) * 2 >= literal_doc_cap) {
+ old = literal_docs; cap = literal_doc_cap * 2; literal_docs = chk_resize(0, 0, cap);
+ i = 0;
+ while (i < literal_doc_cap) { if (old[i]) { k = dg_hash(old[i], cap); while (literal_docs[k]) k = (k + 1) & (cap - 1); literal_docs[k] = old[i]; } i++; }
+ literal_doc_cap = cap;
+ }
+ i = dg_hash(key, literal_doc_cap); while (literal_docs[i]) i = (i + 1) & (literal_doc_cap - 1);
+ literal_docs[i] = key; literal_doc_n++; return 0;
+}
int lex1() {
int n = 0, v, dg, nl;
+ token_block_doc = 0;
if (pendot) {
pendot = 0; tglue = 1; tline = line; toff = cpos();
tbuf[0] = '.'; tbuf[1] = 0; tk = T_OP; tv = intern(tbuf);
@@ -1534,6 +1811,7 @@ int lex1() {
tglue = 0; continue;
}
if (ch == '*') {
+ token_block_doc = 1;
if (docs_on) { docblock(nl); tglue = 0; continue; }
nextch();
while (ch >= 0) { if (ch == '*') { nextch(); if (ch == '/') break; } else nextch(); }
@@ -1584,6 +1862,11 @@ int lex1() {
tbuf[n] = 0;
tk = T_BSTR; tv = intern(tbuf); return 0;
}
+ if (ch == '.' && module_known(tbuf) && findloc(intern(tbuf)) < 0) {
+ n = token_char(n, '.'); nextch();
+ while (isidc(ch)) { n = token_char(n, ch); nextch(); }
+ tbuf[n] = 0;
+ }
tk = T_ID; tv = intern(tbuf); return 0;
}
if (ch == 39) {
@@ -1625,6 +1908,9 @@ int lex1() {
// lex1 plus the token's length, so the language server knows its extent
int lex() {
lex1();
+ if (cmode && tk == T_NUM && token_block_doc && chkind == 3 && chbase == rsbase && toff >= 0) {
+ literal_doc_note(toff);
+ }
if (toff < 0) toklen = 0; else toklen = cpos() - toff;
return 0;
}
@@ -1670,7 +1956,7 @@ int peek() { return peekat(0); }
// case in them.
int next0() {
word f, q; int i;
- if (chkabort) { tk = T_EOF; tv = 0; chkfuel++; if (chkfuel > 3000000) halt(); return 0; }
+ if (chkabort) { tk = T_EOF; tv = 0; chkfuel++; if (chkfuel > 3000000) rt.halt(); return 0; }
f = R_SRC + isp * 32;
if (f[0] != 1) { lex(); return 0; }
i = f[2];
@@ -1703,7 +1989,7 @@ int expect(int s) {
dgprev = 1;
errat();
dgprev = 0;
- put_s("expected '"); put_s(s); put_s("', found "); tokshow();
+ rt.put_s("expected '"); rt.put_s(s); rt.put_s("', found "); tokshow();
errend();
}
panic = 1;
@@ -1720,10 +2006,11 @@ int name() {
return n;
}
-// ---- operator tables ----
+//: ---- operator tables ----
+
// a sequence is written like asm: one-char mnemonics, numbers, and names
// (resolved when the operator is used, so image builds stay self-contained)
-int seqasm(word s) {
+int seqasm(char* s) {
int start = seqtop, n, neg, v, k, c;
while (1) {
while (*s == 32) s++;
@@ -1814,7 +2101,7 @@ int asgfind(int p) {
}
// add or replace a binary operator. code is a sequence string, or 0 for the
// built-in && (prec 2) and || (prec 1) behaviour, chosen by -1 / -2 in prec.
-int add_binop(int nm, int prec, int code) {
+int add_binop(char* nm, int prec, char* code) {
int p = intern(nm), i = binfind(p), a, n;
if (i < 0) { reserve_binary(nbin + 1); i = nbin; nbin++; }
bname[i] = p; bprec[i] = prec;
@@ -1822,9 +2109,9 @@ int add_binop(int nm, int prec, int code) {
bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i];
addkop(p);
// and its compound assignment, x op= y
- n = len_w(nm);
+ n = rt.len_w(nm);
while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; }
- copy_w(tbuf + 1000, nm, n);
+ rt.copy_w(tbuf + 1000, nm, n);
tbuf[1000 + n] = '='; tbuf[1001 + n] = 0;
a = intern(tbuf + 1000);
if (prec >= 8 || prec == 5 || prec == 4 || prec == 3) {
@@ -1835,7 +2122,7 @@ int add_binop(int nm, int prec, int code) {
return i;
}
// register a unary operator and the sequence it expands to.
-int add_unop(int nm, int code) {
+int add_unop(char* nm, char* code) {
int p = intern(nm), i = unfind(p);
if (i < 0) { reserve_unary(nun + 1); i = nun; nun++; }
uname[i] = p; useq[i] = seqasm(code); ulen[i] = seqtop - useq[i];
@@ -1843,48 +2130,376 @@ int add_unop(int nm, int code) {
return i;
}
// register a keyword with its handler and flags.
-int add_kw(int nm, int handler, int flags) {
+int add_kw(char* nm, int handler, int flags) {
int s = addsym(intern(nm), K_KW, handler);
sflag[s] = F_DEF | flags;
return s;
}
+// register a library function as a receiver method. its first parameter
+// determines the receiver type; remaining parameters follow the member name.
+word receiver_methods, closure_expected;
+int add_method(char* member, char* function_name) {
+ int s = lookup_raw(intern(function_name)); word link;
+ if (s < 0 || skind[s] != K_FN || !stype[s] || !stype[s][3]) {
+ serr("a receiver method requires a function with at least one typed parameter"); return -1;
+ }
+ link = chk_resize(0, 0, 3);
+ link[0] = receiver_methods; link[1] = intern(member); link[2] = s; receiver_methods = link;
+ return s;
+}
+int receiver_method(word receiver_type, int member) {
+ word link = receiver_methods, ft, actual; word[8] bs; int found = -1, s;
+ while (link) {
+ s = link[2];
+ if (s < nsym && link[1] == member) {
+ ft = stype[s]; actual = receiver_type;
+ if (actual[0] == TY_ARR && ft[7][0] == TY_DYN && ft[7][3]) actual = tdyn(actual[4], 1);
+ rt.fill_w(bs, 0, 8);
+ if (unify(ft[7], actual, bs)) { if (found >= 0 && found != s) return -2; found = s; }
+ }
+ link = link[0];
+ }
+ return found;
+}
+// run.method("member", function) registers a receiver extension while parsing
+// the importing source, including when its code is emitted into an image.
+int kw_method() {
+ char* member; int s;
+ stmtonly(); next(); expect(s_lp);
+ if (tk != T_STR) { die("a receiver method name must be a character string"); return 0; }
+ member = tv; next(); expect(s_comma);
+ s = -1; if (tk == T_ID) s = source_lookup(tv);
+ if (s < 0 || skind[s] != K_FN) serr("a receiver extension requires a function name");
+ next(); expect(s_rp); expect(s_semi);
+ if (s >= 0) add_method(member, sname[s]);
+ return 0;
+}
+// publish a reference for shallow copies while preserving its declared type.
+// scalar values are unchanged; in-place aggregates require explicit copying.
+int kw_share_value() {
+ int c; word t;
+ next(); expect(s_lp); c = expr(); rval(); plainuse(c); t = xty; expect(s_rp);
+ if (isagg(t)) serr("sharing an aggregate requires sharing its pointer fields explicitly");
+ else if (isptr(t) || t[0] == TY_DYN || t[0] == TY_FN || t == tword) callname(intern("rt.share"));
+ xbor = -1; xaf = 0; effect_value = 0; xty = t; xconst = 0; xvk = 0;
+ return C_PLAIN;
+}
+
+word statement_hooks;
+
+// source libraries can register syntax without emitting a runtime registration call.
+int kw_syntax() {
+ char* keyword; int handler, entry = -1;
+ stmtonly(); next(); expect(s_lp);
+ if (tk != T_STR) { die("a syntax keyword must be a character string"); return 0; }
+ keyword = tv; next(); expect(s_comma); handler = -1;
+ if (tk == T_ID) handler = source_lookup(tv);
+ if (handler < 0 || skind[handler] != K_FN) serr("a syntax extension requires a function name");
+ next(); expect(s_rp); expect(s_semi);
+ if (handler >= 0) {
+ if (hdelta && handler >= ubase) entry = syntax_resident(handler); else entry = sval[handler];
+ if (entry >= 0) add_kw(keyword, entry, F_TOP | F_STMT);
+ }
+ return 0;
+}
+int kw_statement_hook() {
+ int handler = -1; word link;
+ stmtonly(); next(); expect(s_lp); if (tk == T_ID) handler = source_lookup(tv);
+ if (handler < 0 || skind[handler] != K_FN) serr("a statement hook requires a function name");
+ next(); expect(s_rp); expect(s_semi);
+ if (handler >= 0) { link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = handler; link[2] = 1; statement_hooks = link; }
+ return 0;
+}
+// optional statement callbacks installed by source extensions at include time.
+// callbacks execute at ordinary vm call boundaries and must guard reentrancy.
+int add_statement_hook(int callback) {
+ word link = chk_resize(0, 0, 3); link[0] = statement_hooks; link[1] = callback; link[2] = 0; statement_hooks = link;
+ return 0;
+}
+int emit_statement_hooks() {
+ word link = statement_hooks;
+ if (dry || o3on || cmode || incdepth || !infn) return 0;
+ while (link) { if (link[2]) fnref(link[1]); else lit(link[1]); op(O_CALL); op(O_DROP); link = link[0]; }
+ return 0;
+}
+
+//: ---- function effects and temporary borrows ----
+word effect_record(int symbol) {
+ if (symbol < 0) return 0;
+ if (!effect_symbols) effect_symbols = chk_resize(0, 0, symcap);
+ if (!effect_symbols[symbol]) effect_symbols[symbol] = chk_resize(0, 0, 50);
+ return effect_symbols[symbol];
+}
+// trusted runtime contracts apply only to built-ins, never a user function with the same name.
+int effect_contract(int symbol, int parameter) {
+ word n;
+ if (symbol < 0 || symbol >= ubase) return EF_UNKNOWN;
+ n = sname[symbol];
+ if (n == s_put_s || rt.eq_w(n, "rt.put_bs") || rt.eq_w(n, "rt.len_w") || rt.eq_w(n, "rt.len_s") || rt.eq_w(n, "rt.eq_w") || rt.eq_w(n, "rt.eq_s") || rt.eq_w(n, "rt.at_s") || rt.eq_w(n, "rt.put_n") || rt.eq_w(n, "rt.put_c")) return EF_READ;
+ if (n == s_free || n == s_recycle) return EF_CONSUME;
+ if (rt.eq_w(n, "rt.copy_w")) { if (!parameter) return EF_WRITE; return EF_READ; }
+ if (rt.eq_w(n, "rt.fill_w")) { if (!parameter) return EF_WRITE; return EF_READ; }
+ return EF_UNKNOWN;
+}
+int effect_get(int symbol, int parameter) {
+ word r;
+ if (parameter < 0 || parameter >= 24) return EF_UNKNOWN;
+ if (symbol < 0 || symbol >= nsym) return EF_UNKNOWN;
+ if (symbol < ubase) return effect_contract(symbol, parameter);
+ r = effect_record(symbol);
+ if (r[0] != 1 || (sflag[symbol] & F_EXT)) {
+ if (parameter < 12 && (((ssig[symbol] >> 6) >> (parameter + parameter)) & 3) == 1) return EF_READ | EF_WRITE | EF_ALIAS;
+ return EF_UNKNOWN;
+ }
+ return r[2 + parameter];
+}
+int effect_origin(int c) {
+ int i = xbor;
+ if (c >= C_VAR) i = c - C_VAR;
+ if (i < lbase || i >= nloc) return -1;
+ if (local_origin && local_origin[i] >= 0) return local_origin[i];
+ if (lrskind[i] == RS_PARAM && i - lbase < 24) return i - lbase;
+ return -1;
+}
+word effect_mask(int c) {
+ int i;
+ if (c >= C_VAR) {
+ i = c - C_VAR;
+ if (local_origin_mask && local_origin_mask[i]) return local_origin_mask[i];
+ if (i >= lbase && lrskind[i] == RS_PARAM && i - lbase < 24) return 1 << (i - lbase);
+ }
+ return effect_value;
+}
+int effect_note_mask(word mask, int flags) {
+ int i = 0;
+ while (mask && i < 24) { if (mask & 1) effect_note(i, flags); mask = mask >> 1; i++; }
+ return 0;
+}
+int effect_args_mask(int callee, int parameter, word mask) {
+ int i = 0;
+ while (mask && i < 24) { if (mask & 1) effect_arg(callee, parameter, i); mask = mask >> 1; i++; }
+ return 0;
+}
+int effect_note(int origin, int flags) {
+ if (effect_current && origin >= 0 && origin < 24) { effect_current[2 + origin] = effect_current[2 + origin] | flags; effect_current[26 + origin] = effect_current[26 + origin] | flags; }
+ return 0;
+}
+int effect_arg(int callee, int parameter, int origin) {
+ word e;
+ if (!effect_current || origin < 0) return 0;
+ e = talloc(6); e[0] = effect_edges; e[1] = effect_current; e[2] = origin; e[3] = callee; e[4] = parameter; e[5] = 0;
+ effect_edges = e; return 0;
+}
+int effect_require_mode(int callee, int parameter, int place, int mode) {
+ word e;
+ if (!fchk || !live || dry) return 0;
+ e = talloc(7); e[6] = mode;
+ e[5] = effect_floor;
+ e[0] = effect_requirements; e[1] = callee; e[2] = parameter; e[3] = place; e[4] = curinc;
+ effect_requirements = e; return 0;
+}
+int effect_require(int callee, int parameter, int place) { return effect_require_mode(callee, parameter, place, 0); }
+// argument checks for a generic call use the instantiated body's effects.
+int effect_specialize(int original, int instance, word requirements, word edges) {
+ word e = effect_requirements;
+ while (e && e != requirements) { if (e[1] == original) e[1] = instance; e = e[0]; }
+ e = effect_edges;
+ while (e && e != edges) { if (e[3] == original) e[3] = instance; e = e[0]; }
+ return 0;
+}
+int effect_finish(int from) {
+ word e, r, next; int changed = 1, flags, prior, save = tpack(), inc = curinc, i = 0;
+ if (chkabort) return 0;
+ // direct body effects are immutable inputs; recompute callee effects so an earlier nested unit cannot leave stale unknowns.
+ while (effect_symbols && i < nsym) { r = effect_symbols[i]; if (r) rt.copy_w(r + 2, r + 26, 24); i++; }
+ while (changed) {
+ changed = 0; e = effect_edges;
+ while (e) {
+ r = e[1]; flags = effect_get(e[3], e[4]) & ~(EF_ALIAS | EF_IDENTITY); prior = r[2 + e[2]];
+ r[2 + e[2]] = prior | flags; if (prior != r[2 + e[2]]) changed = 1;
+ e = e[0];
+ }
+ }
+ e = effect_requirements; effect_requirements = 0;
+ while (e) {
+ next = e[0];
+ if (e[5] < from) { e[0] = effect_requirements; effect_requirements = e; e = next; continue; }
+ flags = effect_get(e[1], e[2]);
+ if ((!e[6] && (flags & (EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) || (e[6] && !(flags & EF_UNKNOWN) && (flags & (EF_RETAIN | EF_CONSUME)))) {
+ tunpack(e[3]); curinc = e[4];
+ if (e[6]) serr("this callee retains or consumes a borrowed owner; declare its parameter .d to transfer ownership");
+ else if (flags & EF_UNKNOWN) serr("a temporary argument requires a proved non-escaping borrow; bind it to a name first or provide an explicit parameter contract");
+ else serr("a temporary argument requires a proved non-escaping borrow; this callee retains or consumes it, so transfer ownership through a .d parameter");
+ panic = 0;
+ }
+ e = next;
+ }
+ tunpack(save); curinc = inc; return 0;
+}
+int borrow_begin() { return 0; }
+int borrow_prologue() { borrow_jump = hole(); op(O_JMP); borrow_entry = here; return 0; }
+int borrow_initializer() {
+ word p = borrow_slots;
+ if (borrow_jump < 0) return 0;
+ if (!p) { setlit(borrow_jump, borrow_entry); return 0; }
+ setlit(borrow_jump, here);
+ while (p) { lit(0); laddr(p[1]); op(O_ST); p = p[0]; }
+ jmp(borrow_entry); return 0;
+}
+int emit_borrow_hold() {
+ word p;
+ if (!infn) { cerr("temporary borrow outside a function"); return 0; }
+ p = talloc(2); p[0] = borrow_slots; p[1] = fslots; fslots++; borrow_slots = p;
+ lit(0); op(O_PICK); laddr(p[1]); op(O_ST); borrow_pending++; return 0;
+}
+// release only temporaries introduced by this full expression. zero slots cover untaken branches.
+int borrow_cleanup_since(word stop) {
+ word p = borrow_slots; int skip;
+ while (p && p != stop) {
+ laddr(p[1]); op(O_LD); skip = hole(); op(O_JZ);
+ laddr(p[1]); op(O_LD); callname(s_recycle); op(O_DROP);
+ lit(0); laddr(p[1]); op(O_ST); setlit(skip, here); p = p[0];
+ }
+ return 0;
+}
+int borrow_cleanup() { return borrow_cleanup_since(0); }
+int borrow_condition() {
+ word before = borrow_slots; int c = expr(); rval(); plainuse(c); ftest(); borrow_cleanup_since(before); return c;
+}
+
+//: ---- locals and the analyzer's view of them ----
+
+// value tracking is for check mode only; it never changes emitted code.
+int vf_symbols() {
+ if (!svkind) { svkind = chk_resize(0, 0, symcap); svvalue = chk_resize(0, 0, symcap); }
+ return 0;
+}
+int vf_at(int r, int kind, word value) {
+ if (!cmode || dry || chkind != 3 || chbase != rsbase || r < 0 || r >= rsn) return 0;
+ rsrec[r * RS_W + 9] = kind; rsrec[r * RS_W + 10] = value; return 0;
+}
+char* vf_string(char* text) {
+ word p; int n;
+ if (!cmode || dry) return 0;
+ n = rt.len_w(text) + 1; p = talloc(n); rt.copy_w(p, text, n); return p;
+}
+int vf_forget() {
+ if (!cmode || dry) return 0;
+ if (lvkind) rt.fill_w(lvkind + lbase, 0, nloc - lbase);
+ if (svkind) rt.fill_w(svkind, 0, nsym);
+ xvk = 0; return 0;
+}
+// a call or indirect write can change globals, escaped locals, and string data.
+int vf_effect() {
+ int i = lbase;
+ if (!cmode || dry) return 0;
+ while (i < nloc) { if (lvescaped[i] || lvkind[i] >= 3) lvkind[i] = 0; i++; }
+ if (svkind) rt.fill_w(svkind, 0, nsym);
+ xvk = 0; return 0;
+}
+word vf_i2f(word v) { return asm { v @ #1216 }; }
+word vf_f2i(word v) { return asm { v @ #1218 }; }
+word vf_negf(word v) { return asm { 0.0 v @ #1204 }; }
+int vf_coerce(word t, int convert) {
+ if (!xvk || !t) return 0;
+ if (xvk >= 3) { if (t != tword && t != tbstr && t[0] != TY_PTR) xvk = 0; return 0; }
+ if (isfl(t)) {
+ if (xvk == 1) { if (convert) xvv = vf_i2f(xvv); xvk = 2; }
+ if (t == tf32 && convert) xvv = rt.zs2f(rt.zf2s(xvv));
+ } else {
+ if (xvk == 2 && convert && isnum(t)) xvv = vf_f2i(xvv);
+ xvk = 1; xvv = cnorm(t, xvv);
+ }
+ return 0;
+}
+int vf_store(int i) {
+ if (!cmode || dry || !live) return 0;
+ lvkind[i] = xvk; lvvalue[i] = xvv; return 0;
+}
-// ---- locals and the analyzer's view of them ----
// these warnings add no ownership rules. facts are discarded at control-flow
// joins and after unknown effects, so a possible failure is never called definite.
word af_new(int length, int kind, int place) {
word f;
if (dry || stubgen) return 0;
- f = talloc(6); f[0] = length; f[1] = 1; f[2] = kind;
+ f = talloc(15); rt.fill_w(f, 0, 15); f[0] = length; f[1] = 1; f[2] = kind;
f[3] = place; f[4] = 0; f[5] = -1;
return f;
}
+// owner-relative projections retain provenance and the array generation at the time of borrowing.
+word af_base(word f) { while (f && f[6]) f = f[6]; return f; }
+word af_project(word f, int offset, int extent, int place, int elements) {
+ word p, base = af_base(f); int start = offset;
+ if (!f || !base) return 0;
+ if (f[6] && f[7] >= 0 && offset >= 0) start = f[7] + offset;
+ p = af_new(extent, base[2], base[3]);
+ if (!p) return 0;
+ p[1] = base[1]; p[4] = base[4]; p[5] = base[5]; p[6] = base; p[7] = start;
+ p[8] = elements; p[9] = base[10]; p[12] = place;
+ if (base[2] == 3) p[2] = 2;
+ if (elements) {
+ p[0] = -1;
+ if (offset >= 0 && f[0] >= 0) { p[0] = f[0] - offset; p[13] = f[13] - offset; p[14] = 1; }
+ }
+ return p;
+}
+int af_mutate(word f, int place) {
+ word base = af_base(f);
+ if (!base || dry || !live) return 0;
+ af_mutation_tick++; base[10] = af_mutation_tick; base[11] = place; base[0] = -1; return 0;
+}
+int af_projection_access(word f, int place) {
+ word base; int saved;
+ if (!f || !f[6] || !f[8]) return 0;
+ base = af_base(f);
+ if (base[10] < 0 || base[10] == f[9] || dry || !live) return 0;
+ saved = tpack(); tunpack(place);
+ if (fchk) { if (panic) { tunpack(saved); return 0; } errat(); }
+ else if (!af_warning(place)) { tunpack(saved); return 0; }
+ rt.put_s("element borrow was invalidated by array mutation on line "); rt.put_n(base[11] & 0xFFFFFF);
+ rt.put_s("; borrow created on line "); rt.put_n(f[12] & 0xFFFFFF);
+ if (fchk) errend(); else af_warning_end(); tunpack(saved); return 0;
+}
+
int af_forget() {
+ vf_forget();
int i = lbase;
while (i < nloc) { lafact[i] = 0; i++; }
xaf = 0; return 0;
}
// an unknown callee may change a passed allocation's length or lifetime.
int af_escape(word f) {
- if (f) { f[0] = -1; f[1] = 0; }
+ if (f) { word base = af_base(f); base[0] = -1; base[1] = 0; base[10] = -1; f[0] = -1; f[1] = 0; }
return 0;
}
int af_store(int i) {
if (!dry) lafact[i] = xaf;
return 0;
}
+int af_expired(word f) {
+ word base;
+ if (!f) return 0;
+ if (f[1] == 2) return 1;
+ if (!f[6] || !f[8]) return 0;
+ base = af_base(f); return base[10] >= 0 && base[10] != f[9];
+}
+int af_snapshot(word f) {
+ if (f && f[6] && af_expired(f)) { f[1] = 2; f[4] = af_base(f)[11]; }
+ return 0;
+}
int af_record(int off, word f) {
int at;
if (!f || !cmode || dry || chkind != 3 || chbase != rsbase || !rsn) return 0;
+ af_snapshot(f);
at = (rsn - 1) * RS_W;
if (rsrec[at] != off) return 0;
- copy_w(rsrec + at + 4, f, 5); return 0;
+ rt.copy_w(rsrec + at + 4, f, 5); return 0;
}
// keep declaration previews as well as the facts at each use site.
int af_declaration(int off, word f) {
int i = rsn;
if (!f || !cmode || dry || chkind != 3 || chbase != rsbase) return 0;
- while (i) { i--; if (rsrec[i * RS_W] == off) { copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } }
+ while (i) { i--; if (rsrec[i * RS_W] == off) { rt.copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } }
return 0;
}
int af_warning(int place) {
@@ -1892,20 +2507,27 @@ int af_warning(int place) {
if (dry || panic || chkabort || stubgen || !live) return 0;
sv = tpack(); tunpack(place);
if (cmode) dg_open(2);
- else { put_s("zc: line "); put_n(tline); put_s(": warning: "); }
+ else { rt.put_s("zc: line "); rt.put_n(tline); rt.put_s(": warning: "); }
tunpack(sv); return 1;
}
-int af_warning_end() { if (cmode) dg_close(); else put_c(10); return 0; }
+int af_warning_end() { if (cmode) dg_close(); else rt.put_c(10); return 0; }
int af_access(word f, int place) {
+ af_projection_access(f, place); if (f && f[6]) { word base = af_base(f); f[1] = base[1]; f[4] = base[4]; }
if (!f || f[1] != 2 || !af_warning(place)) return 0;
- put_s("use after free: this storage's lifetime ended on line ");
- put_n(f[4] & 0xFFFFFF); af_warning_end(); return 0;
+ rt.put_s("use after free: this storage's lifetime ended on line ");
+ rt.put_n(f[4] & 0xFFFFFF); af_warning_end(); return 0;
+}
+int af_use(word f, int place, int c) {
+ if (fchk && c >= C_VAR && lstate[lroot(c - C_VAR)] == S_DEAD) return 0;
+ return af_access(f, place);
}
int af_index(word f, int place) {
- if (!f || f[1] == 2 || f[0] < 0 || !xconst || (xcval >= 0 && xcval < f[0])) return 0;
+ int lower = 0;
+ if (f && f[6]) { word base = af_base(f); if (base[1] == 0 || base[10] < 0) return 0; if (f[14]) lower = f[13]; }
+ if (!f || f[1] == 2 || f[0] < 0 || !xconst || (xcval >= lower && xcval < f[0])) return 0;
if (!af_warning(place)) return 0;
- put_s("out-of-bounds access: index "); put_n(xcval);
- put_s(" is outside length "); put_n(f[0]); af_warning_end(); return 0;
+ rt.put_s("out-of-bounds access: index "); rt.put_n(xcval);
+ rt.put_s(" is outside length "); rt.put_n(f[0]); rt.put_s(" (storage declared or allocated on line "); rt.put_n(f[3] & 0xFFFFFF); rt.put_c(')'); af_warning_end(); return 0;
}
// freeing an alias ends the same allocation as freeing its original name.
int af_end(word f, int place) {
@@ -1916,9 +2538,9 @@ int af_close(int save) {
int i = save; word f;
if (dry) return 0;
while (i < nloc) {
- f = laaddr[i]; if (f) af_end(f, tpack());
+ f = laaddr[i]; if (f && f[2] == 1) af_end(f, tpack());
f = lafact[i];
- if (f && fchk && lstate[i] == S_M && lown[i]) af_end(f, tpack());
+ if (f && fchk && lstate[i] == S_M && lown[i] && !local_captured[i]) af_end(f, tpack());
i++;
}
return 0;
@@ -1987,6 +2609,15 @@ int newloc(int n) {
if (nloc >= loccap) reserve_locals(nloc + 1);
nloc++;
lname[i] = n; local_bind(n, i); lslot[i] = fslots; lhome[i] = fdepth; lstate[i] = S_PLAIN; lsmark(0, n);
+ if (!local_origin) local_origin = chk_resize(0, 0, loccap); local_origin[i] = -1;
+ if (if_local) if_local[i] = 0;
+ if (!local_origin_mask) local_origin_mask = chk_resize(0, 0, loccap); local_origin_mask[i] = 0;
+ if (!local_cell) local_cell = chk_resize(0, 0, loccap); local_cell[i] = 0;
+ if (!local_captured) local_captured = chk_resize(0, 0, loccap); local_captured[i] = 0;
+ if (!local_ended) local_ended = chk_resize(0, 0, loccap); local_ended[i] = 0;
+ if (!local_end_info) local_end_info = chk_resize(0, 0, loccap); local_end_info[i] = 0;
+ lpos[i] = 0;
+ lreadpos[i] = 0;
lowner[i] = 0;
lrsoff[i] = 0 - 1; lrskind[i] = RS_LOCAL;
lused[i] = 0;
@@ -1996,6 +2627,10 @@ int newloc(int n) {
ltag[i] = 0;
o3cache[i] = 0;
lafact[i] = 0; laaddr[i] = 0;
+ if (cmode) {
+ if (!lvkind) { lvkind = chk_resize(0, 0, loccap); lvvalue = chk_resize(0, 0, loccap); lvescaped = chk_resize(0, 0, loccap); }
+ lvkind[i] = 0; lvvalue[i] = 0; lvescaped[i] = 0;
+ }
return i;
}
// a local's known lower bound, and the array whose length it holds. -1 is unknown.
@@ -2065,6 +2700,12 @@ int bpoison(int i) {
}
// note a store to a local: a constant or a length it now holds.
int note_store(int i) {
+ if (if_local && !dry) {
+ if (!if_local[i]) if_local[i] = chk_resize(0, 0, 1);
+ if (if_value) if_link(if_local[i], if_value);
+ else if (xty == tword) *if_local[i] = -1;
+ if_join(if_local[i], xty);
+ }
int lo = -1, ln = -1;
if (xconst && xcval >= 0) lo = xcval;
if (xlenloc >= 0) ln = xlenloc;
@@ -2088,12 +2729,22 @@ int lroot(int i) {
while (lstate[i] == S_CHILD) i = lowner[i];
return i;
}
+// retire existing aliases together with their owner. dead aliases do not keep later loop snapshots live.
+int end_owner(int i) {
+ int j = lbase; word info = talloc(3);
+ info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack();
+ local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD;
+ while (j < nloc) {
+ if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; }
+ j++;
+ }
+ return 0;
+}
// true when a local has an ownership state the analyzer follows.
word tracked(int i) { return lstate[i] != S_PLAIN; }
// a child link with nothing owned: a pointer computed from some other variable
word isalias(int i) { return lstate[i] == S_CHILD && !lown[i]; }
-// record the last-use line and saturating count read by the hints ("only
-// handed off once").
+// record the last-use line and saturating reference count for diagnostics.
int luse(int i) {
lused[i] = tline;
if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1;
@@ -2123,13 +2774,14 @@ int plainuse(int c) {
// the size class of an n word object, for a constant-size new
int szclass(int n) {
int k = 3;
- while (pow2[k] < n + HDR) k++;
+ while (rt.pow2[k] < n + rt.HDR) k++;
return k;
}
// freeing an owned local. one that holds a typed object with no pointer
// fields goes back through recycle_leaf, which knows its class
int freevar(int i) {
- laddr(lslot[i]); op(O_LD);
+ if (local_captured && local_captured[i]) { lit(0); laddr(lslot[i]); op(O_ST); return 0; }
+ vaddr(i); op(O_LD);
if (lleaf[i] && !o3on && !dry && lookup(s_recycle_leaf) >= 0) {
lit(lleaf[i]);
lit(szclass(lnsz[i]));
@@ -2148,7 +2800,7 @@ int exit_vars(int dep, int final) {
if (lhome[i] < dep) continue;
st = lstate[i];
if (st == S_M) {
- freevar(i);
+ if (!local_captured || !local_captured[i]) ev_put(i, EV_FREE, -1); freevar(i);
} else if (st == S_U) {
cerrn(i, "u object must leave this block (block value, assignment to the enclosing block, or return)");
}
@@ -2160,15 +2812,15 @@ int exit_vars(int dep, int final) {
// block ends.
int unused_check(int from) {
while (from < nloc) {
- if (fchk && lown[from] && !lused[from]) serrn(lname[from], "m object is never used");
+ if (fchk && lown[from] && !lused[from] && !(lrskind[from] == RS_PARAM && rt.eq_w(lname[from], "_"))) serrn(lname[from], "owned object is never used");
from++;
}
return 0;
}
// what a block's own variables show once it ends (check mode only)
-int hint_name(int i, int msg) {
- if (!hint_begin(lpos[i])) return 0;
- put_s(lname[i]); put_s(msg);
+int hint_name(int i, char* msg, int kind) {
+ if (!hint_begin(lpos[i], kind)) return 0;
+ rt.put_s(lname[i]); rt.put_s(msg);
dg_close();
return 0;
}
@@ -2178,11 +2830,15 @@ int local_hints(int from) {
if (!cmode || dry || panic || chkabort || stubgen) return 0;
while (i < nloc) {
f = ltag[i];
- if ((f & LDECL) && !larr[i]) {
- if (!(f & LREAD)) hint_name(i, " is never read");
- else if ((f & LCONST) && !(f & LASG)) hint_name(i, " is never reassigned; make it a const");
- else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap");
- else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner");
+ if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & LREAD)) {
+ if (hint_begin(lpos[i], HINT_SMALL)) {
+ rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close();
+ }
+ } else if ((f & LDECL) && !larr[i]) {
+ if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL);
+ else if ((f & LCONST) && !(f & LASG)) hint_name(i, " is never reassigned; make it a const", HINT_ALL);
+ else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL);
+ else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL);
}
i++;
}
@@ -2234,7 +2890,7 @@ int alias_end(int dep, int save) {
return 0;
}
// snapshots: [lbase] [nloc] then state, owner, home per local
-// save the states of all current locals; returns the snapshot's offset
+// save the states of all current locals; returns the snapshot offset
int snap_push() {
int b = snaptop, i = lbase; word p;
af_forget();
@@ -2286,30 +2942,32 @@ int statename(int s) {
// report that local i ends two paths in different states
int merge_err(int i, int sa, int what) {
if (!fchk || panic || dry) return 0;
- errat(); put_s(lname[i]); put_s(": "); put_s(what);
- put_s(" (one path leaves it ");
- put_s(statename(sa));
- put_s(", another ");
- put_s(statename(lstate[i]));
- put_s(")");
+ errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what);
+ rt.put_s(" (one path leaves it ");
+ rt.put_s(statename(sa));
+ rt.put_s(", another ");
+ rt.put_s(statename(lstate[i]));
+ rt.put_s(")");
errend();
return 0;
}
-// ---- snapshots and comparing paths ----
-// to analyze an if, the compiler snapshots the locals' states before the
-// branch, compiles the then path, restores the snapshot for the else path, and
-// then compares or merges the two. a snapshot is [lbase, nloc] followed by
-// state, owner and home for each local; the stack of them is at R_SNAP.
-// ---- joins ----
-// where control flow comes back together (after an if, a loop, a switch or
-// ?:) every variable has to agree across the paths that reach the join. one
-// exception: a variable that became d, or was given away, on some path is dead
-// after the join, and the paths where it's still alive free it on the way in
-// (fixups). u objects and borrows don't get this treatment: a u object still
-// has to leave, and a borrow never changes.
+//: ---- snapshots and comparing paths ----
-int extop; [JOIN_PATHS]word jps_initial; word jps = jps_initial; int jpscap = JOIN_PATHS, njps; // the paths being joined: [lbase, nloc, triples]
+//: to analyze an if, the compiler snapshots the locals' states before the
+//: branch, compiles the then path, restores the snapshot for the else path, and
+//: then compares or merges the two. a snapshot is [lbase, nloc] followed by
+//: state, owner and home for each local; the stack of them is at R_SNAP.
+//: ---- joins ----
+
+//: where control flow comes back together (after an if, a loop, a switch or
+//: ?:) every variable has to agree across the paths that reach the join. one
+//: exception: a variable that became d, or was given away, on some path is dead
+//: after the join, and the paths where it's still alive free it on the way in
+//: (fixups). u objects and borrows don't get this treatment: a u object still
+//: has to leave, and a borrow never changes.
+
+int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JOIN_PATHS, njps; // the paths being joined: [lbase, nloc, triples]
// accessors on a path record p (a snapshot or an exit record): the state,
// owner and home of local i on that path. locals declared after the snapshot
// read as plain.
@@ -2373,12 +3031,12 @@ int join(int n0, int what) {
}
} else if (dead && okdead) lstate[i] = S_DEAD;
else if (fchk && !panic && !dry) {
- errat(); put_s(lname[i]); put_s(": "); put_s(what);
- put_s(" (one path leaves it ");
- put_s(statename(s0));
- put_s(", another ");
- put_s(statename(jst(jps[bad], i)));
- put_s(")");
+ errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(what);
+ rt.put_s(" (one path leaves it ");
+ rt.put_s(statename(s0));
+ rt.put_s(", another ");
+ rt.put_s(statename(jst(jps[bad], i)));
+ rt.put_s(")");
errend();
}
i++;
@@ -2410,9 +3068,9 @@ int backedge(int s0) {
}
else if (!panic && !dry) {
errat();
- put_s(lname[i]);
- put_s(": changes state inside a loop; each pass has to leave it as it found it (it went from ");
- put_s(statename(jst(p, i))); put_s(" to "); put_s(statename(s)); put_s(")"); errend();
+ rt.put_s(lname[i]);
+ rt.put_s(": changes state inside a loop; each pass has to leave it as it found it (it went from ");
+ rt.put_s(statename(jst(p, i))); rt.put_s(" to "); rt.put_s(statename(s)); rt.put_s(")"); errend();
}
}
i++;
@@ -2436,13 +3094,14 @@ int join2(int sa, int livea, int ja, int livebv, int n0, int what) {
return 0;
}
-// the rules below are the heart of the analyzer. each is a function the parser
-// calls at one kind of place where a value moves: recast (u*x), argument
-// passing, assignment, return, block values, and discarding. the argument
-// c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a
-// new object nobody owns yet) or C_VAR + n (a bare local, number n), which is
-// how the analyzer knows the thing being moved is a variable it tracks.
-// ---- analyzer rules for the places values flow ----
+//: the rules below are the heart of the analyzer. each is a function the parser
+//: calls at one kind of place where a value moves: recast (u*x), argument
+//: passing, assignment, return, block values, and discarding. the argument
+//: c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a
+//: new object nobody owns yet) or C_VAR + n (a bare local, number n), which is
+//: how the analyzer knows the thing being moved is a variable it tracks.
+//: ---- analyzer rules for the places values flow ----
+
// recast: x as u / x as d / x as m
int recast(int c, int t) {
int i, st;
@@ -2463,7 +3122,10 @@ int recast(int c, int t) {
// an argument for a parameter tagged t (0 plain, 1 m, 2 d)
int argcheck(int c, int t) {
int i, st;
+ if (fchk && t == 2 && xaf && xaf[2] == 5) { cerr("a borrowed temporary cannot transfer ownership"); return 0; }
+ if (fchk && t == 2 && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot transfer ownership to a d parameter"); return 0; }
if (!fchk) return 0;
+ if (t == 2 && c < C_VAR && c != C_FRESH && xbor >= 0 && tracked(lroot(xbor))) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; }
if (c == C_FRESH) {
if (t != 2) {
cerr("a new object can only go straight to a d parameter; bind it to a name first");
@@ -2474,6 +3136,7 @@ int argcheck(int c, int t) {
}
if (c < C_VAR || !tracked(c - C_VAR)) return 0;
i = c - C_VAR;
+ if (t == 2 && isalias(i)) { cerr("a borrowed projection cannot transfer ownership; transfer its owner"); return 0; }
st = lstate[i];
if (lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; }
if (t <= 1) { // untagged parameters are m: a lend
@@ -2484,7 +3147,7 @@ int argcheck(int c, int t) {
touch(i);
return 0;
}
- if (st == S_D) { lstate[i] = S_DEAD; luse(i); xgive = 1; return 0; }
+ if (st == S_D || ((st == S_M || st == S_U) && lown[i])) { end_owner(i); luse(i); xgive = 1; ev_put(i, EV_DOWN, -1); return 0; }
if (st == S_M) cerrn(i, "is m; write d*x to give it away");
else if (st == S_U) cerrn(i, "is u; write d*x to give it away instead of returning it");
else if (st == S_CHILD) cerrn(i, "belongs to a container and can't be given away alone");
@@ -2512,8 +3175,8 @@ int o3own() {
// owner in the child's header. a growable array's elements live in its items
// object, so that is the owner there.
int emitown(int p) {
- laddr(lslot[p]); op(O_LD);
- // a []T's elements live in its items object
+ vaddr(p); op(O_LD);
+ // a T[?]'s elements live in its items object
if (ltype[p][0] == TY_DYN) {
lit(2);
op(O_ADD);
@@ -2526,9 +3189,15 @@ int emitown(int p) {
}
// a store of class rc into an lvalue of kind k (local or container index i)
int assigncheck(int k, int i, int rc) {
- int st, j, r, js, bor;
+ int st, j, r, js, bor, origin = effect_origin(rc);
+ if (k == LV_LOCAL && local_origin) { local_origin[i] = origin; local_origin_mask[i] = local_origin_mask[i] | effect_mask(rc); }
+ if (k == LV_ELEM) { effect_note_mask(effect_mask(C_VAR + i), EF_WRITE); }
+ if (k != LV_LOCAL && isptr(xty)) effect_note_mask(effect_mask(rc), EF_RETAIN);
+ else if (k != LV_LOCAL && xty == tword) effect_note_mask(effect_mask(rc), EF_UNKNOWN);
+ if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression into a variable or container");
bor = xbor; xbor = -1;
if (!fchk) return C_PLAIN;
+ if (k != LV_LOCAL && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty))) { cerr("an array literal cannot escape into a global or container"); return C_PLAIN; }
j = -1;
if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR;
if (j >= 0 && lstate[lroot(j)] == S_DEAD) {
@@ -2540,7 +3209,7 @@ int assigncheck(int k, int i, int rc) {
if (isalias(i)) st = S_PLAIN; // a borrow isn't an owner; overwriting it drops the alias
if (j < 0 && rc != C_FRESH) {
if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it");
- else if (bor >= 0) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; }
+ else if (bor >= 0 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; }
else lstate[i] = S_PLAIN;
return C_PLAIN;
}
@@ -2552,16 +3221,29 @@ int assigncheck(int k, int i, int rc) {
lstate[i] = S_M;
lused[i] = 0;
lown[i] = 1;
+ local_ended[i] = 0; local_end_info[i] = 0;
if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
return C_PLAIN;
}
js = lstate[j];
+ if (js == S_BORROW || isalias(j)) { lstate[i] = S_CHILD; lowner[i] = lroot(j); lown[i] = 0; touch(j); return C_PLAIN; }
+ // replace a released owner by moving a same-scope owner into its empty slot.
+ // fresh declarations still cannot copy owners; the source and its aliases expire.
+ if (st == S_DEAD && lown[i] && js == S_M && lown[j] && lhome[i] == lhome[j]
+ && !local_captured[i] && !local_captured[j]) {
+ lstate[i] = S_M; lused[i] = 0; lown[i] = 1;
+ lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j];
+ local_ended[i] = 0; local_end_info[i] = 0;
+ end_owner(j); luse(j); ev_put(j, EV_MOVE, i);
+ if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
+ return C_PLAIN;
+ }
if (js == S_U && lhome[i] == lhome[j] - 1) { // handing a u object up one block
lstate[i] = S_M;
lused[i] = 0;
lown[i] = 1;
- lstate[j] = S_DEAD;
- luse(j);
+ end_owner(j);
+ luse(j); ev_put(j, EV_UP, i);
if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
return C_PLAIN;
}
@@ -2584,16 +3266,17 @@ int assigncheck(int k, int i, int rc) {
if (js == S_U) {
cerrn(j, "is u; it has to leave through the block above, not into a borrowed object");
}
- else { lstate[j] = S_DEAD; luse(j); emitown(i); } // storing it gives it away
+ else { end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away
} else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)");
else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into");
- else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); emitown(i); }
+ else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); }
return C_PLAIN;
}
// a global, or memory the analyzer doesn't track: the collector takes it
+ // an owner (d, u, or an m local that owns its object) is transferred as if by u*x
if (j >= 0) {
- if (lstate[j] == S_D) { lstate[j] = S_DEAD; luse(j); }
- else cerrn(j, "must be d to be stored in a global or in untracked memory (the collector takes it over)");
+ if (lstate[j] == S_D || lstate[j] == S_U || (lstate[j] == S_M && lown[j])) { ev_put(j, EV_GLOBAL, -1); end_owner(j); luse(j); }
+ else cerrn(j, "is not an owner, so it can't be stored in a global or in untracked memory (the collector takes it over)");
}
return C_PLAIN;
}
@@ -2603,8 +3286,29 @@ int assigncheck(int k, int i, int rc) {
// object is lent, not given, so returning it is an error, and so is a plain word
// from an owned function that was called before its body.
int retcheck(int c) {
- int i;
+ int i;
+ if (isptr(xty) || xty == tword) {
+ effect_note_mask(effect_mask(c), EF_ALIAS);
+ if (c >= C_VAR && lrskind[c - C_VAR] == RS_PARAM) effect_note(effect_origin(c), EF_IDENTITY);
+ }
+ if (fchk && xaf && xaf[2] == 5 && (isptr(xty) || xty == tword)) cerr("a borrowed temporary cannot escape its expression through return");
if (!fchk) return 0;
+ if (c < C_VAR && c != C_FRESH && xbor >= 0 && (isptr(xty) || xty == tword)) {
+ int root = lroot(xbor);
+ if (fret) { cerr("a borrowed projection cannot be returned as an owned result"); return 0; }
+ if (lown[root] && (lstate[root] == S_M || lstate[root] == S_U || lstate[root] == S_D)) { cerr("a borrow of a local owner cannot escape through return; return the owner"); return 0; }
+ }
+ if (c >= C_VAR && isalias(c - C_VAR)) {
+ int root = lroot(c - C_VAR);
+ if (fret) cerr("a borrowed projection cannot be returned as an owned result");
+ else if (lown[root]) cerr("a borrow of a local owner cannot escape through return; return the owner");
+ return 0;
+ }
+ if (c >= C_VAR && lstate[c - C_VAR] == S_BORROW) {
+ if (fret) cerr("a borrowed parameter cannot be returned as an owned result");
+ return 0;
+ }
+ if ((isptr(xty) || isagg(xty)) && ((xaf && xaf[2] == 4) || (xbor >= 0 && lafact[xbor] && lafact[xbor][2] == 4))) { cerr("an array literal cannot escape its function through return"); return 0; }
i = -1;
if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR;
if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; }
@@ -2613,11 +3317,12 @@ int retcheck(int c) {
if (sflag[finsym] & F_EARLY) serrn(sname[finsym], "is called before its body, so its owned return can't be known there; declare the return type with .u");
ssig[finsym] = ssig[finsym] | 1; // the return is owned: callers read it from the signature
sflag[finsym] = sflag[finsym] | F_RETOWN;
+ if (stype[finsym]) { word ft = stype[finsym]; stype[finsym] = tfn(ft[4], ft[3], ft + 7, ssig[finsym]); }
fret = 1; finfer = 0;
}
if (fret) {
if (i >= 0) {
- if (lstate[i] == S_U) { lstate[i] = S_DEAD; luse(i); }
+ if (lstate[i] == S_U) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); }
else cerrn(i, "has to be u to be returned");
}
return 0;
@@ -2633,7 +3338,7 @@ int handup(int c) {
if (c == C_FRESH) return C_FRESH;
if (c < C_VAR || !tracked(c - C_VAR)) return C_PLAIN;
i = c - C_VAR;
- if (lstate[i] == S_U && lhome[i] == fdepth) { lstate[i] = S_DEAD; luse(i); return C_FRESH; }
+ if (lstate[i] == S_U && lhome[i] == fdepth) { lstate[i] = S_DEAD; luse(i); ev_put(i, EV_VALUE, -1); return C_FRESH; }
if (lstate[i] == S_U) cerrn(i, "only a u object of this block can be its value");
else cerrn(i, "has to be u to be handed up as the block's value");
return C_PLAIN;
@@ -2646,7 +3351,55 @@ int discard(int c) {
return 0;
}
-// ---- types ----
+//: ---- completion ----
+//: copy local i (kind: 1 local, 2 parameter, 3 captured from the function around a closure) into the probe,
+//: unless a nearer local has its name
+int probe_add(int i, int kind) {
+ int k = 0;
+ while (k < probe_n) { if (probe_name[k] == lname[i]) return 0; k++; }
+ if (!probe_name || probe_n == probe_cap) {
+ if (!probe_cap) probe_cap = 32; else probe_cap = probe_cap * 2;
+ probe_name = chk_resize(probe_name, probe_n, probe_cap); probe_kind = chk_resize(probe_kind, probe_n, probe_cap);
+ probe_state = chk_resize(probe_state, probe_n, probe_cap); probe_type = chk_resize(probe_type, probe_n, probe_cap); probe_decl = chk_resize(probe_decl, probe_n, probe_cap);
+ }
+ probe_name[probe_n] = lname[i]; probe_kind[probe_n] = kind;
+ probe_state[probe_n] = lstate[lroot(i)] == S_DEAD || af_expired(lafact[i]);
+ if (lafact[i] && lafact[i][1] == 2) probe_state[probe_n] = 1;
+ probe_type[probe_n] = ltype[i]; probe_decl[probe_n] = lrsoff[i];
+ probe_n++;
+ return 0;
+}
+// what is in scope right now: this function's locals, then those of the functions around a closure
+int probe_snap() {
+ int i = nloc, k = nclo;
+ probe_n = 0; probe_has = 1;
+ while (i > lbase) {
+ i--;
+ if (lname[i][0] != '<') { if (lrskind[i] == RS_PARAM) probe_add(i, 2); else probe_add(i, 1); }
+ }
+ while (k) {
+ k--;
+ i = clo_hi[k];
+ while (i > clo_lo[k]) { i--; if (lname[i][0] != '<') probe_add(i, 3); }
+ }
+ return 0;
+}
+// a statement of the document starts here: remember what is in scope, or stop when the place has been passed.
+// statements of a method or generic compiled for a call belong to another function and are left alone
+int probe_stmt() {
+ if (!cmode || dry || ginst || chkabort || incdepth != 0 || ctxsp > nclo) return 0;
+ probe_snap();
+ if (toff > probe_off) { chkabort = 1; return 0; }
+ return 0;
+}
+// the place asked about is a member name, and t is the type before the dot
+int probe_member(word t) {
+ probe_mem = t; probe_has = 2; chkabort = 1;
+ return 0;
+}
+
+//: ---- types ----
+
// types are a compiler primitive: records in the type area, hash-consed so
// the same type is always the same pointer and comparing types is comparing
// words. untyped code is typed word, which goes with anything. the records
@@ -2666,28 +3419,49 @@ int discard(int c) {
// the hash-consing: asking for the same pointer type twice returns the same
// record. the list is searched linearly, which is fine for the few hundred
// types a program has.
-unchecked int talloc(int n) {
+int talloc(int n) {
int p = tytop;
if (tytop + n > tyend) {
- p = new(n + 16385); p[-4] = p[-4] | F_INTERNAL; p[0] = typeblocks; typeblocks = p;
+ p = chk_resize(0, 0, n + 16385); p[0] = typeblocks; typeblocks = p;
p++; tytop = p; tyend = p + n + 16384;
}
tytop = tytop + n;
- fill_w(p, 0, n);
+ rt.fill_w(p, 0, n);
return p;
}
// make a type record of the given kind, size and name in the type area.
-word tnew(int kind, int size, int nm, int extra) {
+word tnew(int kind, int size, char* nm, int extra) {
word t = talloc(7 + extra);
t[0] = kind; t[1] = size; t[2] = nm;
if (ntl >= typecap) { typelist = chk_resize(typelist, ntl, typecap * 2); typecap = typecap * 2; }
typelist[ntl] = t; ntl++;
return t;
}
+// qualified types retain their layout and reference their unqualified record.
+word tunconst(word t) {
+ word entry = readonly_types;
+ while (entry) { if (entry[1] == t) return entry[2]; entry = entry[0]; }
+ return t;
+}
+int treadonly(word t) { return t && tunconst(t) != t; }
+word tconst(word t) {
+ word entry = readonly_types, qualified; int words = 7;
+ if (treadonly(t)) return t;
+ while (entry) { if (entry[2] == t) return entry[1]; entry = entry[0]; }
+ if (t[0] == TY_FN) words = words + t[3];
+ qualified = tnew(t[0], t[1], t[2], words - 7); rt.copy_w(qualified, t, words);
+ entry = talloc(3); entry[0] = readonly_types; entry[1] = qualified; entry[2] = t; readonly_types = entry;
+ return qualified;
+}
+// writes require mutable storage, independently of optional type checking.
+int require_mutable(word t) {
+ if (treadonly(t)) serr("const storage cannot be modified");
+ return 0;
+}
// the pointer type to type e, reused when it already exists.
word tptr(int e) {
int i = 0; word t;
- while (i < ntl) { t = typelist[i]; if (t[0] == TY_PTR && t[4] == e) return t; i++; }
+ while (i < ntl) { t = typelist[i]; if (t[0] == TY_PTR && t[4] == e && !treadonly(t)) return t; i++; }
t = tnew(TY_PTR, 1, 0, 0); t[4] = e;
return t;
}
@@ -2701,9 +3475,9 @@ word tbytes(word t) { if (t[0] == TY_INT || t[0] == TY_FLOAT) return t[5]; retur
// true for a packed integer or float type.
word ispk(word t) { return (t[0] == TY_INT || t[0] == TY_FLOAT) && (t[6] & 2); }
// the type a packed type stores its elements as, or the type itself.
-word tpk(word t) { if ((t[0] == TY_INT || t[0] == TY_FLOAT) && t[4]) return t[4]; return t; }
+word tpk(word t) { if (treadonly(t)) return tconst(tpk(tunconst(t))); if ((t[0] == TY_INT || t[0] == TY_FLOAT) && t[4]) return t[4]; return t; }
// an enhancement used by the compiler itself, inline when the runtime has it
-int xenh(int name) {
+int xenh(char* name) {
int s = lookup(intern(name));
if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); return 0; }
callname(intern(name));
@@ -2711,18 +3485,19 @@ int xenh(int name) {
}
// words an array of n elements of e takes: packed for a sized integer
word tasz(word e, int n) {
+ if (n < 0) return 0;
if (tbytes(e)) return (n * e[5] + 7) >> 3;
return n * tsz(e);
}
// the fixed array type of n elements of type e, reused when it already exists.
word tarr(int e, int n) {
int i = 0; word t;
- while (i < ntl) { t = typelist[i]; if (t[0] == TY_ARR && t[4] == e && t[3] == n) return t; i++; }
+ while (i < ntl) { t = typelist[i]; if (t[0] == TY_ARR && t[4] == e && t[3] == n && !treadonly(t)) return t; i++; }
t = tnew(TY_ARR, tasz(e, n), 0, 0); t[4] = e; t[3] = n;
return t;
}
// a sized integer type and its packed twin
-word tsized(int name, int bytes, int sgn) {
+word tsized(char* name, int bytes, int sgn) {
word t = tnew(TY_INT, 1, intern(name), 0), twin = tnew(TY_INT, 1, t[2], 0);
t[5] = bytes; t[6] = sgn; t[4] = twin;
twin[5] = bytes; twin[6] = sgn | 2;
@@ -2741,18 +3516,18 @@ word tfn(int ret, int np, word ps, int sig) {
i++;
}
t = tnew(TY_FN, 1, 0, np); t[4] = ret; t[3] = np; t[5] = sig;
- copy_w(t + 7, ps, np);
+ rt.copy_w(t + 7, ps, np);
return t;
}
-// []T, or fixed []T
+// T[?], or fixed T[?]
word tdyn(int e, int f) {
int i = 0; word t;
- while (i < ntl) { t = typelist[i]; if (t[0] == TY_DYN && t[4] == e && t[3] == f) return t; i++; }
+ while (i < ntl) { t = typelist[i]; if (t[0] == TY_DYN && t[4] == e && t[3] == f && !treadonly(t)) return t; i++; }
t = tnew(TY_DYN, 1, 0, 0); t[4] = e; t[3] = f;
return t;
}
// the type variable with index i, reused when it already exists.
-word tvar(int i, int nm) {
+word tvar(int i, char* nm) {
int k = 0; word t;
while (k < ntl) { t = typelist[k]; if (t[0] == TY_VAR && t[3] == i) return t; k++; }
t = tnew(TY_VAR, 1, nm, 0); t[3] = i;
@@ -2771,18 +3546,18 @@ word tinst(word g, word args, int n) {
}
i++;
}
- v = talloc(n + 1); v[0] = n; copy_w(v + 1, args, n);
+ v = talloc(n + 1); v[0] = n; rt.copy_w(v + 1, args, n);
t = tnew(TY_STRUCT, 0, g[2], 0); t[3] = -1; t[5] = g; t[6] = v;
return t;
}
// lay out a generic instance on first use
word tresolve(word t) {
- word g, f; int n, i; word fs; int off = 0, ft; [8]word bs;
+ word g, f; int n, i; word fs; int off = 0, ft; word[8] bs;
if (t[0] != TY_STRUCT || t[3] >= 0) return t;
if (t[3] == -2) { serr("a struct can't contain itself by value"); t[3] = 0; return t; }
t[3] = -2;
g = t[5]; n = g[5]; f = g[4];
- fill_w(bs, 0, 8); copy_w(bs, t[6] + 1, t[6][0]);
+ rt.fill_w(bs, 0, 8); rt.copy_w(bs, t[6] + 1, t[6][0]);
fs = talloc(n * 3 + 1);
i = 0;
while (i < n) {
@@ -2801,10 +3576,12 @@ word tsz(word t) {
}
// replace type parameters with what they were bound to (unbound ones become word)
word subst(word t, word bs) {
- int k = t[0], i, n; [24]word v; word vec;
+ if (treadonly(t)) return tconst(subst(tunconst(t), bs));
+ int k = t[0], i, n; word[24] v; word vec;
if (k == TY_VAR) { if (bs[t[3]]) return bs[t[3]]; return tword; }
if (k == TY_PTR) return tptr(subst(t[4], bs));
if (k == TY_ARR) return tarr(subst(t[4], bs), t[3]);
+ if (k == TY_DYN) return tdyn(subst(t[4], bs), t[3]);
if (k == TY_FN) {
n = t[3];
i = 0;
@@ -2826,14 +3603,18 @@ word isnum(word t) { return t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL
// true for an aggregate type: an array or a struct.
word isagg(word t) { return t[0] == TY_ARR || t[0] == TY_STRUCT; }
// true for a pointer or an array type.
-word isptr(word t) { return t[0] == TY_PTR || t[0] == TY_ARR; }
+word isptr(word t) { return t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_BSTR; }
+// true for a reference value, including growable-array handles and functions.
+word isref(word t) { return isptr(t) || t[0] == TY_DYN || t[0] == TY_FN; }
// the pointer an array type turns into when used as a value.
word decay(word t) {
+ if (treadonly(t)) return tconst(decay(tunconst(t)));
if (t[0] == TY_ARR) return tptr(t[4]);
return t;
}
// the element type of a pointer or array, or word for anything else.
word telem(word t) {
+ if (treadonly(t)) return tconst(telem(tunconst(t)));
if (t[0] == TY_PTR || t[0] == TY_ARR) return t[4];
return tword;
}
@@ -2843,7 +3624,12 @@ word telem(word t) {
// the same element unless one side is untyped or void, and a literal 0 is a
// null pointer.
word compat(word dst, word src) {
- if (dst == src || dst == tword || src == tword) return 1;
+ if (treadonly(src) && isref(src) && !treadonly(dst)) return 0;
+ dst = tunconst(dst); src = tunconst(src);
+ if (dst == src) return 1;
+ if (dst == tword && src && (src == tbstr || (src[0] == TY_PTR && src[4] == tchar) || (src[0] == TY_ARR && src[4] == tchar))) return 0;
+ if (dst == tword || src == tword) return 1;
+ if (dst == tbstr || src == tbstr) return dst == tbstr && xzero;
if (dst[0] == TY_PTR && src[0] == TY_STRUCT && dst[4] == src) return 1; // an in-place struct supplies its address
if (isen(dst) || isen(src)) { // an enum takes only its own enum; an enum still becomes an int
if (isen(dst) && isen(src)) return dst == src;
@@ -2865,9 +3651,25 @@ word compat(word dst, word src) {
// T from its arguments.
word unify(word p, word a, word bs) {
int i, n; word pv, av;
+ if (p[0] != TY_VAR) {
+ if (treadonly(a) && isref(a) && !treadonly(p)) return 0;
+ p = tunconst(p); a = tunconst(a);
+ }
if (p[0] == TY_VAR) { i = p[3]; if (!bs[i]) { bs[i] = a; return 1; } return compat(bs[i], a); }
a = decay(a);
if (p[0] == TY_PTR && a[0] == TY_PTR) { if (a[4] == tword) return 1; return unify(p[4], a[4], bs); }
+ if (p[0] == TY_DYN && a[0] == TY_DYN) return (p[3] || !a[3]) && unify(p[4], a[4], bs);
+ if (p[0] == TY_FN && a[0] == TY_FN) {
+ if (p[3] != a[3] || ((p[5] >> 6) != (a[5] >> 6))) return 0;
+ i = 0;
+ while (i < p[3]) {
+ pv = subst(p[7 + i], bs); av = a[7 + i];
+ if (pv != tword && av != tword && pv != av) return 0;
+ if (!unify(p[7 + i], av, bs)) return 0;
+ i++;
+ }
+ return unify(p[4], a[4], bs);
+ }
if (p[0] == TY_STRUCT && p[5] && a[0] == TY_STRUCT && a[5] == p[5]) {
pv = p[6]; av = a[6]; n = pv[0]; i = 0;
while (i < n) { if (!unify(pv[1 + i], av[1 + i], bs)) return 0; i++; }
@@ -2875,39 +3677,42 @@ word unify(word p, word a, word bs) {
}
return compat(p, a);
}
-// print a type the way it is written in source. a pointer to an array or a
-// function has no source spelling (T* would read as an array of pointers; give
-// it a type alias), so it prints with a leading star for the reader's benefit.
+// print a type the way it is written in source. function pointers use an alias
+// for source spelling; array pointers use T[N]*.
+int tprint_dimensions(word t) {
+ while (t[0] == TY_ARR || t[0] == TY_DYN) { rt.put_c('['); if (t[0] == TY_DYN) rt.put_c('?'); else if (t[3] >= 0) rt.put_n(t[3]); rt.put_c(']'); t = t[4]; }
+ return 0;
+}
word tprint(word t) {
+ if (treadonly(t)) { rt.put_s("const "); return tprint(tunconst(t)); }
int k = t[0], i; word v;
- if (k == TY_PTR) { // T*; a pointer to an array or a function keeps the prefix form, since T* would read as an array of pointers
+ if (k == TY_PTR) { // postfix pointers; only function pointers keep the display-only prefix form
v = t[4][0];
- if (v == TY_ARR || v == TY_DYN || v == TY_FN) { put_c('*'); return tprint(t[4]); }
- tprint(t[4]); put_c('*');
+ if (v == TY_FN) { rt.put_c('*'); return tprint(t[4]); }
+ tprint(t[4]); rt.put_c('*');
return 0;
}
- if (k == TY_DYN) { if (t[3]) put_s("fixed "); put_s("[]"); return tprint(t[4]); }
- if (k == TY_ARR) { put_c('['); put_n(t[3]); put_c(']'); return tprint(t[4]); }
+ if (k == TY_ARR || k == TY_DYN) { if (k == TY_DYN && t[3]) rt.put_s("fixed "); v = t; while (v[0] == TY_ARR || v[0] == TY_DYN) v = v[4]; tprint(v); return tprint_dimensions(t); }
if (k == TY_FN) {
- if (t[4] == tword) put_s("fn"); else tprint(t[4]);
- if (t[5] & 1) put_s(".u");
- put_c('(');
+ if (t[4] == tword) rt.put_s("fn"); else tprint(t[4]);
+ if (t[5] & 1) rt.put_s(".u");
+ rt.put_c('(');
i = 0;
while (i < t[3]) {
- if (i) put_s(", ");
+ if (i) rt.put_s(", ");
tprint(t[7 + i]);
- if (i < 12 && tparamtag(t, i) == 1) put_s(".m");
- if (i < 12 && tparamtag(t, i) == 2) put_s(".d");
+ if (i < 12 && tparamtag(t, i) == 1) rt.put_s(".m");
+ if (i < 12 && tparamtag(t, i) == 2) rt.put_s(".d");
i++;
}
- put_c(')');
+ rt.put_c(')');
return 0;
}
- if (t[2]) put_s(t[2]); else put_s("?");
+ if (t[2]) rt.put_s(t[2]); else rt.put_s("?");
if (k == TY_STRUCT && t[5]) {
- v = t[6]; put_c('<');
- i = 0; while (i < v[0]) { if (i) put_s(", "); tprint(v[1 + i]); i++; }
- put_c('>');
+ v = t[6]; rt.put_c('<');
+ i = 0; while (i < v[0]) { if (i) rt.put_s(", "); tprint(v[1 + i]); i++; }
+ rt.put_c('>');
}
return 0;
}
@@ -2915,15 +3720,15 @@ word tprint(word t) {
// code, and never during a dry run.
int tcerr(int what, int want, int got) {
if (!tc_on || !live || dry || panic) return 0;
- errat(); put_s("type error: "); put_s(what);
- put_s(": expected "); tprint(want); put_s(", got "); tprint(got);
+ errat(); rt.put_s("type error: "); rt.put_s(what);
+ rt.put_s(": expected "); tprint(want); rt.put_s(", got "); tprint(got);
errend();
return 0;
}
// report a type error when ok is false, showing the type it got.
int tcneed(int ok, int what, int got) {
if (ok || !tc_on || !live || dry || panic) return 0;
- errat(); put_s("type error: "); put_s(what); put_s(", got "); tprint(got);
+ errat(); rt.put_s("type error: "); rt.put_s(what); rt.put_s(", got "); tprint(got);
errend();
return 0;
}
@@ -2939,9 +3744,9 @@ int tpfind(int n) {
return -1;
}
// true when the name n is a type, a type parameter or a generic type.
-word istypename(int n) {
+word istypename(char* n) {
int s;
- if (n == s_fixed) return 1;
+ if (n == s_fixed || n == s_const) return 1;
if (ginst && gfind(n) >= 0) return 1;
if (tpfind(n) >= 0) return 1;
if (findloc(n) >= 0) return 0;
@@ -2954,56 +3759,68 @@ int closeangle() {
expect(s_gt);
return 0;
}
-// a type, then any number of (params) after it: int(int, int) is a function
-// returning int, fn(int) one returning a plain word
-// ---- parsing types ----
+//: a type, then any number of (params) after it: int(int, int) is a function
+//: returning int, fn(int) one returning a plain word
+//: ---- parsing types ----
+
// grammar, outermost first:
// ptype a type, then any number of (params): int(int, int) is a function type
-// ptype0 a type with its postfix stars: int*, vec<T>*, point**
-// ptype1 a base: [n]T, []T, fixed []T, a type name, or a generic instance
+// ptype0 postfix pointers and array groups: T*, T[N], T[], T[?]
+// ptype1 a type name, fixed qualifier, function base, or generic instance
// the first thing in ptype's comment applies here too: fn(int) is a function
// returning a plain word.
int ptype() {
- int start = toff, t = ptype0(), np; [24]word ps;
+ int start = toff, t = ptype0(), np, tags, tag; word[24] ps;
while (is_op(s_lp)) {
- next(); np = 0;
+ next(); np = 0; tags = 0;
if (!is_op(s_rp)) {
while (1) {
if (np >= 24) fatal("too many parameters");
- ps[np] = ptype();
+ ps[np] = ptype(); if (ps[np][0] == TY_ARR) ps[np] = decay(ps[np]);
+ tag = 0;
+ if (accept(s_dot)) {
+ if (tv == s_m) tag = 1; else if (tv == s_d) tag = 2; else die("callback parameter lifetime is .m or .d");
+ next();
+ }
+ tags = tags | (tag << (np + np));
np++;
if (!accept(s_comma)) break;
}
}
expect(s_rp);
- t = tfn(t, np, ps, np + np);
+ t = tfn(t, np, ps, np + np + tags * 64);
}
ht_note(start, toff, t);
return t;
}
// a type with any stars after it: int*, vec<T>*, point**
int ptype0() {
- int t = ptype1();
- while (is_op(s_star)) { next(); t = tptr(t); }
+ int t = ptype1(), cap = 8, n, count, j; word dims = 0;
+ while (is_op(s_star) || is_op(s_lk)) {
+ if (accept(s_star)) t = tptr(t);
+ else {
+ count = 0;
+ while (accept(s_lk)) {
+ if (accept(s_q)) { expect(s_rk); n = -2; }
+ else if (accept(s_rk)) n = -1;
+ else { n = cexpr(); expect(s_rk); if (n < 0) serr("a negative array length"); }
+ if (!dims || count == cap) { if (dims) cap = cap * 2; dims = chk_resize(dims, count, cap); }
+ dims[count] = n; count++;
+ }
+ j = count; while (j) { j--; if (dims[j] == -2) t = tdyn(t, 0); else t = tarr(t, dims[j]); }
+ }
+ }
+ if (dims) rt.free(dims);
return t;
}
// parse one type with no trailing function parts.
word ptype1() {
- int n, s; word t; [8]word args; int na = 0, i;
- if (accept(s_lk)) {
- if (accept(s_rk)) { // []T: grows
- t = ptype0();
- return tdyn(t, 0);
- }
- n = cexpr();
- expect(s_rk);
- t = ptype0();
- if (n < 0) serr("a negative array length");
- return tarr(t, n);
- }
- if (is_id(s_fixed)) { // fixed []T can't grow; fixed [n]T says what [n]T already means
+ int n, s; word t; word[8] args; int na = 0, i;
+ if (is_op(s_lk)) { die("array types are postfix: T[N], T[] or T[?]"); return tword; }
+ if (is_id(s_const)) { next(); return tconst(ptype0()); }
+ if (is_id(s_fixed)) { // fixed T[?] can't grow; fixed T[n] says what T[n] already means
next(); t = ptype0();
- if (t[0] == TY_DYN) return tdyn(t[4], 1);
+ if (t[0] == TY_DYN) { if (treadonly(t)) return tconst(tdyn(t[4], 1)); return tdyn(t[4], 1); }
if (t[0] != TY_ARR) serr("only arrays can be fixed");
return t;
}
@@ -3020,7 +3837,7 @@ word ptype1() {
}
i = tpfind(n);
if (i >= 0) { next(); return tvar(i, n); }
- s = lookup(n);
+ s = source_lookup(n);
// T in T first<T>(...)
if (tvauto && (s < 0 || skind[s] != K_TYPE) && ntp < 8) {
tpn[ntp] = n;
@@ -3030,6 +3847,7 @@ word ptype1() {
return tvar(ntp - 1, n);
}
if (s < 0 || skind[s] != K_TYPE) { dien(n, "isn't a type"); return tword; }
+ rs_put(toff, RS_SYM, rs_sym(s), s);
next();
t = sval[s];
if (t[0] != TY_GEN) return t;
@@ -3087,9 +3905,9 @@ int dtype() {
// and call its siblings. a generic struct keeps its fields unresolved and
// can't have methods; each instance is laid out later by tresolve.
int kw_struct() {
- int n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; word mn = chk_resize(0, 0, mcap), mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0, fname = 0;
+ char* n; word t; int s, nf = 0; word buf = chk_resize(0, 0, 64); int bufcap = 64, i; word fs; int off = 0, gen = 0, ft, nm = 0, mcap = STRUCT_METHODS; char** mn = chk_resize(0, 0, mcap); word mq = chk_resize(0, 0, mcap), mst = chk_resize(0, 0, mcap), mty = chk_resize(0, 0, mcap), mrt = chk_resize(0, 0, mcap), mrg = chk_resize(0, 0, mcap); int bo, k, stat = 0; char* fname = 0;
next();
- n = name();
+ n = module_symbol(name());
if (is_op(s_lt)) { // generic
next(); ntp = 0;
while (1) {
@@ -3102,16 +3920,17 @@ int kw_struct() {
closeangle();
gen = 1;
t = tnew(TY_GEN, 0, n, 0); t[3] = ntp;
- fs = talloc(ntp); copy_w(fs, tpn, ntp); t[6] = fs;
+ fs = talloc(ntp); rt.copy_w(fs, tpn, ntp); t[6] = fs;
} else { t = tnew(TY_STRUCT, 0, n, 0); t[3] = -2; } // -2 while its fields are read
s = addsym(n, K_TYPE, t);
expect(s_lb);
while (!is_op(s_rb)) {
if (tk == T_EOF) { die("missing }"); break; }
stat = 0;
+ if (is_id(s_unchecked)) { next(); stat = 2; }
if (is_id(s_static)) { // static fn name(params) { body }, or static T name(params) { body }: a function of the struct, called as Struct.name(args), with no this
next();
- stat = 1;
+ stat = stat | 1;
}
if (is_id(s_fn)) { // fn name(params) { body }: a method, compiled once the struct is complete
next();
@@ -3125,14 +3944,14 @@ int kw_struct() {
mq[nm] = grabfn();
mty[nm] = 0;
k = methdecl(n, mn[nm]);
- if (stat) sflag[k] = sflag[k] | F_STATIC;
+ if (stat & 1) sflag[k] = sflag[k] | F_STATIC;
nm++;
continue;
}
// let x, y; fields are plain words
fname = 0;
if (is_id(s_var)) {
- if (stat) die("static must be followed by fn or a typed method");
+ if (stat & 1) die("static must be followed by fn or a typed method");
next();
ft = tword;
} else {
@@ -3151,11 +3970,11 @@ int kw_struct() {
mty[nm] = 1; mrt[nm] = dty; mrg[nm] = dtag == S_U;
if (dtag && dtag != S_U) serr("the only return tag is .u");
k = methdecl(n, fname);
- if (stat) sflag[k] = sflag[k] | F_STATIC;
+ if (stat & 1) sflag[k] = sflag[k] | F_STATIC;
nm++;
continue;
}
- if (stat) die("static must be followed by fn or a typed method");
+ if (stat & 1) die("static must be followed by fn or a typed method");
if (dtag) serr("a field has no tag");
ft = dty;
}
@@ -3170,9 +3989,9 @@ int kw_struct() {
}
next();
if (gen) {
- fs = talloc(nf + nf); copy_w(fs, buf, nf + nf);
+ fs = talloc(nf + nf); rt.copy_w(fs, buf, nf + nf);
t[4] = fs; t[5] = nf;
- mdg = ntp; copy_w(mdn, tpn, ntp);
+ mdg = ntp; rt.copy_w(mdn, tpn, ntp);
ntp = 0;
} else {
fs = talloc(nf * 3 + 1);
@@ -3194,18 +4013,19 @@ int kw_struct() {
}
accept(s_semi);
i = 0;
- while (i < nm) { methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); i++; }
+ while (i < nm) { fn_method_compile = 1; methdef(n, mn[i], mq[i], mst[i], mty[i], mrt[i], mrg[i]); fn_method_compile = 0; i++; }
mdg = 0;
return s;
}
-// ---- methods ----
+//: ---- methods ----
+
// a method is an ordinary function named Struct.name whose first parameter is
// this, a pointer to the struct. e.method(a) calls Struct.name(e, a). the
// receiver's struct comes from its type, or, for an untyped word, from the
// one struct that has a method of that name.
// the symbol for a method is the interned string "Struct.name"
-word methname(word sn, word mn) {
- word b = chk_resize(0, 0, len_w(sn) + len_w(mn) + 2); int i = 0, j = 0;
+char* methname(char* sn, char* mn) {
+ char* b = chk_resize(0, 0, rt.len_w(sn) + rt.len_w(mn) + 2); int i = 0, j = 0;
while (sn[j]) { b[i] = sn[j]; i++; j++; }
b[i] = '.'; i++;
j = 0;
@@ -3216,13 +4036,13 @@ word methname(word sn, word mn) {
// the method called n on struct type t, as a symbol index, or -1. methwhich
// (below) does the untyped version: search every struct for one with that
// method, which is unique or ambiguous (-2).
-int methof(word t, int n) {
+int methof(word t, char* n) {
int s = lookup(methname(t[2], n));
if (s >= 0 && skind[s] == K_FN) return s;
return 0 - 1;
}
// the one method called n, -1 for none, -2 for several structs having one
-int methscan(int n) {
+int methscan(char* n) {
int i = 0; word t; int s, hit = 0 - 1;
while (i < ntl) {
t = typelist[i];
@@ -3237,7 +4057,7 @@ int methscan(int n) {
// declare Struct.name now, so methods can call each other in any order
// (the declaration is what makes mutual recursion between methods work: the
// symbol exists with no body yet, and calls to it go on the patch chain)
-int methdecl(int sn, int mn) {
+int methdecl(char* sn, char* mn) {
int nm = methname(sn, mn), s = lookup(nm);
if (s >= ubase && s >= 0 && skind[s] == K_FN) {
serrn(mn, "is defined twice in this struct");
@@ -3252,15 +4072,15 @@ int methdecl(int sn, int mn) {
// into a token list (kind, value, place triples) up to the matching brace, and
// methdef replays them later. the place travels with each token, so errors
// inside a method still point at the right line.
-unchecked word grabfn() {
- int cap = 768; word q = new(cap); int n = 0, depth = 0; word nq;
+word .u grabfn() {
+ int cap = 768; word q = rt.new(cap); int n = 0, depth = 0; word nq;
q[0] = 0;
if (!is_op(s_lp)) { die("expected ( after the method name"); return q; }
while (tk != T_EOF) {
if (n * 3 + 4 > cap) {
- nq = new(cap + cap);
- copy_w(nq, q, cap);
- free(q);
+ nq = rt.new(cap + cap);
+ rt.copy_w(nq, q, cap);
+ rt.free(q);
q = nq;
cap = cap + cap;
}
@@ -3278,17 +4098,26 @@ unchecked word grabfn() {
// method's saved tokens, push it as an input source and parse it as an
// ordinary top-level function. a method is therefore just a function whose
// first parameter is this; nothing else about it is special.
-unchecked int tkput(word r, int k, int kind, int val, int pl) {
+int tkput(word r, int k, int kind, int val, int pl) {
r[1 + k * 3] = kind; r[2 + k * 3] = val; r[3 + k * 3] = pl;
return k + 1;
}
// compile a struct method once the struct is complete.
-unchecked int methdef(int sn, int mn, word q, int stat, int typed, int rty, int rtag) {
- int n = q[0]; word r; int i, k = 0, lnp;
- if (!n) { free(q); return 0; }
- r = new((n + 8 + mdg * 4) * 3 + 1);
+int methdef(char* sn, char* mn, word q, int stat, int typed, int rty, int rtag) {
+ int n = q[0]; word r; int i, k = 0, lnp, unchecked_method = stat & 2;
+ stat = stat & 1;
+ if (!n) { rt.free(q); return 0; }
+ if (!stat) {
+ i = 1;
+ while (i < n && q[2 + i * 3] != s_comma && q[2 + i * 3] != s_rp) {
+ if (q[1 + i * 3] == T_ID && q[2 + i * 3] == s_this) serr("this is implicit and cannot be declared as a method parameter");
+ i++;
+ }
+ }
+ r = rt.new((n + 8 + mdg * 4) * 3 + 1);
lnp = q[3];
- r[1] = T_ID; r[2] = s_fn; r[3] = lnp; k = 1;
+ if (unchecked_method) k = tkput(r, k, T_ID, s_unchecked, lnp);
+ k = tkput(r, k, T_ID, s_fn, lnp);
r[1 + k * 3] = T_ID; r[2 + k * 3] = methname(sn, mn); r[3 + k * 3] = lnp; k++;
if (mdg) { // a method of a generic struct is a generic function over the struct's parameters: fn S.m<K, V>(S<K, V>* this, ...)
k = tkput(r, k, T_OP, s_lt, lnp);
@@ -3332,12 +4161,36 @@ unchecked int methdef(int sn, int mn, word q, int stat, int typed, int rty, int
i++;
}
r[0] = k;
- free(q);
+ rt.free(q);
+ if (typed && mdg) {
+ // retain a typed generic method as ordinary typed function tokens.
+ int mode = rt.outmode, length = rt.outn, capacity = rt.outcap, prefix = unchecked_method != 0;
+ word output = rt.outbuf, grow = rt.outgrow, buffer = rt.new(4096), return_tokens, joined;
+ rt.outmode = 2; rt.outbuf = m*buffer; rt.outn = 0; rt.outcap = 4095; rt.outgrow = 0;
+ tprint(rty); if (rtag) rt.put_s(".u"); buffer[rt.outn] = 0;
+ rt.outmode = mode; rt.outbuf = output; rt.outn = length; rt.outcap = capacity; rt.outgrow = grow;
+ return_tokens = tokens(buffer);
+ int token = 0, variable;
+ while (token < return_tokens[0]) {
+ variable = 0;
+ while (variable < ntl) {
+ word descriptor = typelist[variable];
+ if (descriptor[0] == TY_VAR && descriptor[3] < mdg && return_tokens[1 + token * 3] == T_ID && return_tokens[2 + token * 3] == descriptor[2]) return_tokens[2 + token * 3] = mdn[descriptor[3]];
+ variable++;
+ }
+ token++;
+ }
+ joined = rt.new((r[0] + return_tokens[0]) * 3 + 1);
+ joined[0] = r[0] - 1 + return_tokens[0];
+ if (prefix) rt.copy_w(joined + 1, r + 1, 3);
+ rt.copy_w(joined + 1 + prefix * 3, return_tokens + 1, return_tokens[0] * 3);
+ rt.copy_w(joined + 1 + (prefix + return_tokens[0]) * 3, r + 1 + (prefix + 1) * 3, (r[0] - prefix - 1) * 3);
+ word previous = m*r; r = d*joined; rt.free(return_tokens); rt.free(buffer); rt.free(previous);
+ }
src_push(1, r, 0, 1);
next();
- if (typed) {
+ if (typed && !mdg) {
// a typed method: skip the fn and the name, then compile from the ( with the declared return type
- if (mdg) die("a typed method of a generic struct is not supported yet");
next();
next();
ifkp = tpack(); ifkk = 0 - 1; if (isp != 0) ifkk = 0;
@@ -3352,17 +4205,31 @@ unchecked int methdef(int sn, int mn, word q, int stat, int typed, int rty, int
// e.m(args): by now the receiver is on the stack, so it simply becomes the
// first argument. the call is checked against the method's signature if it
// has been compiled, and its result class is fresh if the method returns .u.
-int methcall(int s, word rst) {
+int methcall(int s, word rst, word receiver_fact, int receiver, word receiver_mask) {
af_forget();
- int np, tags = 0, ret = 0; word ft = stype[s]; [8]word bs;
+ int np, tags = 0, ret = 0, temporary, owner, aliases, i; word ft = stype[s]; word[8] bs; word owner_fact, origins;
+ word requirements = effect_requirements, edges = effect_edges; int original = s;
+ effect_args_mask(s, 0, receiver_mask);
+ if (receiver_fact && (receiver_fact[2] == 4 || receiver_fact[2] == 5)) effect_require(s, 0, callpos);
+ if (receiver >= 0 && tracked(lroot(receiver))) effect_require_mode(s, 0, callpos, 1);
if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; }
else sflag[s] = sflag[s] | F_UNTYPED;
if (sflag[s] & F_RETOWN) ret = 1;
- fill_w(bs, 0, 8);
- if ((sflag[s] & F_GEN) && ft && rst && rst[0] == TY_STRUCT) unify(ft[7], tptr(rst), bs); // a method of a generic struct: the receiver says what its parameters are
- np = args(tags >> 2, 0, bs, 0) + 1;
+ rt.fill_w(bs, 0, 8);
+ if (ft && rst) {
+ word actual = rst; if (rst[0] == TY_STRUCT) actual = tptr(rst);
+ if (!unify(ft[7], actual, bs)) tcerr("receiver", subst(ft[7], bs), actual);
+ }
+ ifcall = s; effect_argument_base = 1;
+ np = args(tags >> 2, ft, bs, 0) + 1;
+ temporary = argtemporary || (receiver_fact && receiver_fact[2] == 5); owner = argowner; owner_fact = argowner_fact; origins = argorigin_mask;
+ if (effect_get(s, 0) & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) {
+ origins = origins | receiver_mask;
+ if (owner < 0) { owner = receiver; owner_fact = receiver_fact; }
+ else if (owner != receiver) { owner = -1; owner_fact = 0; }
+ }
if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments");
- if (sflag[s] & F_GEN) s = ginstance(s, bs);
+ if (sflag[s] & F_GEN) { s = ginstance(s, bs); effect_specialize(original, s, requirements, edges); }
fnref(s); op(O_CALL);
lv = 0;
lvk = LV_NONE;
@@ -3376,6 +4243,12 @@ int methcall(int s, word rst) {
xty = tword;
if (ft) xty = subst(ft[4], bs);
if (isfl(xty)) xfloat = 1;
+ effect_value = 0;
+ if (!ret && (isptr(xty) || xty == tword)) {
+ effect_value = origins;
+ if (isptr(xty)) xbor = owner;
+ if (temporary) { aliases = 0; i = 0; while (i < np) { aliases = aliases | effect_get(s, i); i++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, callpos); }
+ }
if (ret) return C_FRESH;
return C_PLAIN;
}
@@ -3399,14 +4272,16 @@ int caststart() {
peek();
if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) {
k = 1; peekat(k);
+ if (pk_tk == T_OP && (pk_tv == s_lk || pk_tv == s_lt || (pk_tv == s_lp && pq_tv[0] == s_fn))) return 1;
while (pk_tk == T_OP && pk_tv == s_star && k < 3) { k++; peekat(k); } // (int*)x, (node**)x
return pk_tk == T_OP && pk_tv == s_rp;
}
return 0;
}
-// memdebug on; checks and interior pinning. memdebug scrub; also poisons frees.
-// memdebug off; stops both. at the top level, start() applies it before main.
-// ---- statements that only configure the compiler ----
+//: memdebug on; checks and interior pinning. memdebug scrub; also poisons frees.
+//: memdebug off; stops both. at the top level, start() applies it before main.
+//: ---- statements that only configure the compiler ----
+
int kw_memdebug() {
int f = 0;
stmtonly();
@@ -3424,20 +4299,20 @@ int kw_memdebug() {
// enum name { A, B = 5, C }; the names are global constants, the name is an int type
// the type of an enum: an int record that the typechecker keeps apart from the
// plain ints. t[6] bit 4 marks it; the members carry it in stype
-word tenum(int n) { word t = tnew(TY_INT, 1, n, 0); t[6] = 16; return t; }
+word tenum(char* n) { word t = tnew(TY_INT, 1, n, 0); t[6] = 16; return t; }
// true for a type made by an enum declaration.
word isen(word t) { return t[0] == TY_INT && (t[6] & 16); }
-// enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A)
-// member records for tagged enums and for plain enums (pm_en is -1 for a plain
-// member). a member's payload fields are at most 8.
-// the text a tagged enum or an extern stub is generated into
-[GENERATED_WORDS]word ebuf_initial; word ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn;
-[ENUM_MEMBERS]word pm_name_initial, pm_en_initial, pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial;
-word pm_name = pm_name_initial, pm_en = pm_en_initial, pm_tag = pm_tag_initial, pm_nf = pm_nf_initial, pm_val = pm_val_initial, pm_exp = pm_exp_initial; int pmcap = ENUM_MEMBERS, npm;
-[ENUM_MEMBERS * 8]word pm_fnm_initial, pm_ft_initial, pm_off_initial;
-word pm_fnm = pm_fnm_initial, pm_ft = pm_ft_initial, pm_off = pm_off_initial;
-[TAGGED_ENUMS]word pe_st_initial, pe_first_initial, pe_n_initial, pe_name_initial;
-word pe_st = pe_st_initial, pe_first = pe_first_initial, pe_n = pe_n_initial, pe_name = pe_name_initial; int pecap = TAGGED_ENUMS, npe;
+//: enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A)
+//: member records for tagged enums and for plain enums (pm_en is -1 for a plain
+//: member). a member's payload fields are at most 8.
+//: the text a tagged enum or an extern stub is generated into
+char[GENERATED_WORDS] ebuf_initial; char* ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn;
+char*[ENUM_MEMBERS] pm_name_initial, pm_en_initial; word[ENUM_MEMBERS] pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial;
+char** pm_name = pm_name_initial, pm_en = pm_en_initial; word pm_tag = pm_tag_initial, pm_nf = pm_nf_initial, pm_val = pm_val_initial, pm_exp = pm_exp_initial; int pmcap = ENUM_MEMBERS, npm;
+char*[ENUM_MEMBERS * 8] pm_fnm_initial; word[ENUM_MEMBERS * 8] pm_ft_initial, pm_off_initial;
+char** pm_fnm = pm_fnm_initial; word pm_ft = pm_ft_initial, pm_off = pm_off_initial;
+word[TAGGED_ENUMS] pe_st_initial, pe_first_initial, pe_n_initial; char*[TAGGED_ENUMS] pe_name_initial;
+word pe_st = pe_st_initial, pe_first = pe_first_initial, pe_n = pe_n_initial; char** pe_name = pe_name_initial; int pecap = TAGGED_ENUMS, npe;
// grow whole member rows, keeping their payload-field indices stable.
int reserve_enum_members(int need) {
int cap = pmcap;
@@ -3459,9 +4334,9 @@ int reserve_tagged_enums(int need) {
pecap = cap; return cap;
}
// a field's c-like spelling in a generated struct
-int ftext(int t) { if (t == tint) return "int"; if (t == tfloat) return "float"; return "let"; }
+char* ftext(int t) { if (t == tint) return "int"; if (t == tfloat) return "float"; return "let"; }
// the member named n of a tagged enum, or -1
-int pm_find(int n) {
+int pm_find(char* n) {
int gi = npm - 1;
while (gi >= 0) { if (pm_name[gi] == n && pm_en[gi] >= 0) return gi; gi--; }
return -1;
@@ -3477,7 +4352,7 @@ int ptagged(word t) {
// and scoped (name.A). a member with a block of fields makes it a tagged enum
// (see enum_tagged): enum name { circle { int r; }, dot }.
int kw_enum() {
- int n, en = 0, first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft, fname;
+ char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft; char* fname;
stmtonly();
next();
if (tk == T_ID) { en = tv; next(); }
@@ -3525,8 +4400,8 @@ int kw_enum() {
// function on the enum, en.member(fields...), that allocates the struct, sets
// the tag and the member's fields, and returns it owned. the struct is generated
// as source and compiled here, so its methods are ordinary zc.
-int enum_tagged(int en, int first, int cnt) {
- int gi, j, nf, w = 1; word buf; int sk;
+int enum_tagged(char* en, int first, int cnt) {
+ int gi, j, nf, w = 1; word buf, oldimports = module_imports; int sk;
if (!en) { serr("a tagged enum needs a name"); return 0; }
reserve_tagged_enums(npe + 1);
if (cnt > 63) { fatal("a tagged enum has at most 63 members"); return 0; }
@@ -3548,7 +4423,7 @@ int enum_tagged(int en, int first, int cnt) {
if (pm_ft[nf] == tint) ebs("int "); else if (pm_ft[nf] == tfloat) ebs("float ");
ebs(pm_fnm[nf]);
}
- ebs(") { "); ebs(en); ebs("* o = new("); ebs(en); ebs("); o.tag = "); ebn10(pm_tag[gi]); ebs("; ");
+ ebs(") { "); ebs(en); ebs("* o = rt.new("); ebs(en); ebs("); o.tag = "); ebn10(pm_tag[gi]); ebs("; ");
for (j = 0; j < pm_nf[gi]; j++) {
nf = gi * 8 + j;
ebs("o."); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs(" = "); ebs(pm_fnm[nf]); ebs("; ");
@@ -3556,15 +4431,17 @@ int enum_tagged(int en, int first, int cnt) {
ebs("return u*o; } ");
}
ebs("}");
- buf = new(ebn + 2);
- copy_w(buf, ebuf, ebn + 1);
+ buf = rt.new(ebn + 14);
+ rt.copy_w(buf, "include rt; ", 12);
+ rt.copy_w(buf + 12, ebuf, ebn + 1);
src_push(2, buf, 0, 0);
next();
stubgen = 1;
while (tk != T_EOF) topitem();
stubgen = 0;
src_pop();
- free(d*buf);
+ module_imports = oldimports;
+ rt.free(d*buf);
sk = lookup(en);
if (sk < 0 || skind[sk] != K_TYPE) { serr("the tagged enum didn't make its struct"); return 0; }
pe_st[npe] = sval[sk]; pe_first[npe] = first; pe_n[npe] = cnt; pe_name[npe] = en;
@@ -3584,32 +4461,207 @@ int cover_warn(int e, int cov, int sl) {
if (dry || panic || cmode) return 0;
for (k = 0; k < pe_n[e]; k++) {
if (k < 63 && ((cov >> k) & 1)) continue;
- if (first) { put_s("zc: line "); put_n(sl); put_s(": warning: this switch doesn't cover "); first = 0; }
- else put_s(", ");
+ if (first) { rt.put_s("zc: line "); rt.put_n(sl); rt.put_s(": warning: this switch doesn't cover "); first = 0; }
+ else rt.put_s(", ");
gi = pe_first[e] + k;
- put_s(pm_name[gi]);
+ rt.put_s(pm_name[gi]);
}
- if (!first) put_c(10);
+ if (!first) rt.put_c(10);
+ return 0;
+}
+//: ---- include ----
+//: the text of the embedded std library, one character to a word and ending in 0, as readfile gives a file.
+//: it is built from the pieces src/tools/embed.sh put at the end of this file, the first time it is asked for
+char* std_text() {
+ int n = 0, i = 0, k = 0, j; bstr p; char* b;
+ if (std_buf) return std_buf;
+ while (1) { p = std_part(i); if (!p) break; n = n + rt.len_s(p); i++; }
+ b = rt.new(n + 2);
+ i = 0;
+ while (1) {
+ p = std_part(i); if (!p) break;
+ j = 0; while (j < rt.len_s(p)) { b[k] = rt.at_s(p, j); k++; j++; }
+ i++;
+ }
+ b[k] = 0;
+ std_buf = b;
+ return std_buf;
+}
+// the embedded icon module, assembled from its source pieces on first use.
+char* icon_text() {
+ int n = 0, i = 0, k = 0, j; bstr p; char* b;
+ if (icon_buf) return icon_buf;
+ while (1) { p = icon_part(i); if (!p) break; n = n + rt.len_s(p); i++; }
+ b = rt.new(n + 2);
+ i = 0;
+ while (1) {
+ p = icon_part(i); if (!p) break;
+ j = 0; while (j < rt.len_s(p)) { b[k] = rt.at_s(p, j); k++; j++; }
+ i++;
+ }
+ b[k] = 0;
+ icon_buf = b;
+ return icon_buf;
+}
+// a path with its . and .. segments and doubled slashes taken out: /a/b/../c/./d.zc is /a/c/d.zc
+char*.u inc_norm(char*.m p) {
+ int n = rt.len_w(p), i = 0, k = 0, s, e, j, isabs = 0; char* o;
+ o = rt.new(n + 4);
+ if (p[0] == '/') isabs = 1;
+ while (i < n) {
+ while (i < n && p[i] == '/') i++;
+ s = i;
+ while (i < n && p[i] != '/') i++;
+ e = i;
+ if (e == s) continue;
+ if (e - s == 1 && p[s] == '.') continue;
+ if (e - s == 2 && p[s] == '.' && p[s + 1] == '.') {
+ j = k;
+ while (j > 0 && o[j - 1] != '/') j--;
+ // the last segment is o[j..k); a leading .. of a relative path stays
+ if (k > 0 && !(k - j == 2 && o[j] == '.' && o[j + 1] == '.')) {
+ if (j > 0) k = j - 1; else k = 0;
+ continue;
+ }
+ if (k == 0 && isabs) continue;
+ }
+ if (k > 0 || isabs) { o[k] = '/'; k++; }
+ j = s; while (j < e) { o[k] = p[j]; k++; j++; }
+ }
+ if (k == 0 && isabs) { o[0] = '/'; k = 1; }
+ o[k] = 0;
+ return o;
+}
+// dir, a slash and path
+char*.u inc_cat(char*.m dir, char*.m path) {
+ int a = rt.len_w(dir), b = rt.len_w(path); char* o;
+ o = rt.new(a + b + 3);
+ rt.copy_w(o, dir, a);
+ o[a] = '/';
+ rt.copy_w(o + a + 1, path, b);
+ o[a + b + 1] = 0;
+ return o;
+}
+// the directory part of a path, without the last slash, or 0 when there is none
+char*.u inc_dir(char*.m p) {
+ int n = rt.len_w(p), k; char* o;
+ k = n;
+ while (k > 0 && p[k - 1] != '/') k--;
+ if (k == 0) return 0;
+ if (k > 1) k--;
+ o = rt.new(k + 2);
+ rt.copy_w(o, p, k);
+ o[k] = 0;
+ return o;
+}
+// the text of the file at path: the editor's copy if it sent one, else the file
+char*.u inc_try(char*.m path) {
+ if (inc_hook) {
+ char* text = inc_hook(path);
+ if (text) {
+ int n = rt.len_w(text); char* copy = rt.new(n + 1);
+ rt.copy_w(copy, text, n + 1); return copy;
+ }
+ }
+ char* copy = rt.readfile(path);
+ return copy;
+}
+// find an include: next to the file that has it, then in the workspace folder, then as written (relative to the
+// working directory). incres is where it was found
+char*.u inc_open(char*.m path) {
+ incres = inc_norm(path);
+ if (path[0] == '/' || path[1] == ':') {
+ char* c = inc_norm(path), b = inc_try(c);
+ if (b) incres = c;
+ return b;
+ }
+ if (incdir) {
+ char* combined = inc_cat(incdir, path); char* c = inc_norm(combined), b = inc_try(c);
+ if (b) { incres = c; return b; }
+ }
+ if (increoot) {
+ char* combined = inc_cat(increoot, path); char* c = inc_norm(combined), b = inc_try(c);
+ if (b) { incres = c; return b; }
+ }
+ return inc_try(path);
+}
+// compile the text buf as if it were written here, signatures first. name is what hovers and definitions call it
+// and dir the directory its own includes are found from
+int inc_compile(char* buf, char* name, char*.d dir) {
+ char* oldname = curinc; word olddir = incdir;
+ char* oldmodule = module_name; word oldimports = module_imports;
+ module_imports = 0; module_name = 0;
+ prescan(2, buf);
+ src_push(2, buf, 0, 0);
+ curinc = name; incdir = dir; incdepth++;
+ next();
+ while (tk != T_EOF) topitem();
+ curinc = oldname; incdir = olddir; incdepth--;
+ src_pop();
+ module_name = oldmodule; module_imports = oldimports;
return 0;
}
// include "path"; reads the file and compiles it here, signatures first
+// include std; does the same with the standard library, which is part of the compiler
int kw_include() {
- int path, buf;
+ int pp, sv; char* path; char* buf;
stmtonly();
next();
- if (tk != T_STR) { die("include needs a string"); return 0; }
+ if (tk == T_ID) {
+ while (1) {
+ pp = tpack(); path = tv;
+ if (tk != T_ID) { die("expected a module name in include"); return 0; }
+ next();
+ if (!module_imported(path)) {
+ word link = chk_resize(0, 0, 2); link[0] = module_imports; link[1] = path; module_imports = link;
+ }
+ if (path == s_std && !std_done) {
+ std_done = 1; if (incdepth == 0) incplace = pp;
+ inc_compile(std_text(), "tinyvm:std.zc", 0);
+ }
+ if (path == s_icon && !icon_done) {
+ icon_done = 1; if (incdepth == 0) incplace = pp;
+ inc_compile(icon_text(), "tinyvm:icon.zc", 0);
+ }
+ if (!module_known(path) && !module_loaded(path)) {
+ char* filename = inc_cat("src/lib", path); int length = rt.len_w(filename);
+ char* full = chk_resize(0, 0, length + 4); rt.copy_w(full, filename, length); rt.copy_w(full + length, ".zc", 4);
+ buf = m*inc_open(full);
+ if (!buf) { sv = tpack(); tunpack(pp); serr("can't read that module include"); tunpack(sv); return 0; }
+ word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = path; included_modules = link;
+ if (incdepth == 0) incplace = pp;
+ inc_compile(buf, incres, inc_dir(incres));
+ }
+ if (!accept(s_comma)) break;
+ }
+ expect(s_semi); return 0;
+ }
+ if (tk != T_STR) { die("include takes a path in quotes, or std"); return 0; }
+ pp = tpack();
path = tv;
next();
expect(s_semi);
- buf = readfile(path);
- if (!buf) { serr("can't read that include"); return 0; }
- prescan(2, buf);
- src_push(2, buf, 0, 0);
- next();
- while (tk != T_EOF) topitem();
- src_pop();
+ buf = m*inc_open(path);
+ if (!buf) {
+ sv = tpack(); tunpack(pp);
+ serr("can't read that include");
+ tunpack(sv);
+ return 0;
+ }
+ if (incdepth == 0) incplace = pp;
+ inc_compile(buf, incres, inc_dir(incres));
return 0;
}
+// file namespace and imports are restored when an included source ends.
+int kw_namespace() {
+ next();
+ if (is_op(s_semi)) module_name = 0;
+ else {
+ module_name = tv; name();
+ if (!module_known(module_name)) { word link = chk_resize(0, 0, 2); link[0] = included_modules; link[1] = module_name; included_modules = link; }
+ }
+ module_imports = 0; expect(s_semi); return 0;
+}
// typecheck on; / typecheck off;
int kw_typecheck() {
next();
@@ -3628,11 +4680,21 @@ int kw_docs() {
next(); expect(s_semi);
return 0;
}
+// hints on; / hints small; / hints off; from here to the end of the document (or the next such line)
+int kw_hints() {
+ int level = 2, at = toff;
+ next();
+ if (is_id(s_on)) level = 2; else if (is_id(s_small)) level = 1; else if (is_id(s_off)) level = 0;
+ else { die("hints on, hints small or hints off"); return 0; }
+ hint_set(level, at);
+ next(); expect(s_semi);
+ return 0;
+}
// the doc symbol of a name: the symbol "doc name" that holds its byte string,
// or -1. the chain is walked by hand because lookup hides what is below vislo
-int docsym(word nm) {
- int i, k = 0; word p = "doc ";
- doc_name_room(len_w(nm) + 5);
+int docsym(char* nm) {
+ int i, k = 0; char* p = "doc ";
+ doc_name_room(rt.len_w(nm) + 5);
while (p[k]) { dnmb[k] = p[k]; k++; }
i = 0; while (nm[i]) { dnmb[k] = nm[i]; k++; i++; }
dnmb[k] = 0;
@@ -3645,13 +4707,13 @@ int docsym(word nm) {
return -1;
}
// the packed documentation of a name (a byte string) or 0
-word docofs(int nm) {
+bstr docofs(char* nm) {
int s = docsym(nm);
if (s < 0) return 0;
return sval[s];
}
// the documentation of a function, by address
-word docof(int f) {
+bstr docof(int f) {
int i = nsym;
while (i) {
i--;
@@ -3661,9 +4723,9 @@ word docof(int f) {
}
// documentation uses stable packed objects. symbols keep them alive until an
// image copies their bytes after its code; ordinary docofs calls work too.
-word doc_pack(word text, int length) {
+bstr doc_pack(char* text, int length) {
int i = 0, k, c, w, nw = (length + 7) >> 3; word p = chk_resize(0, 0, nw + 2);
- p[-4] = p[-4] | F_SHARE | F_INTERNAL; p[0] = length;
+ p[-4] = p[-4] | rt.F_SHARE | rt.F_INTERNAL; p[0] = length;
while (i < length) {
w = 0; k = 0;
while (k < 8 && i < length) { c = text[i]; if (c > 255) c = 63; w = w | (c << (k * 8)); k++; i++; }
@@ -3671,12 +4733,26 @@ word doc_pack(word text, int length) {
}
return p;
}
+// section headings are organizational text, never documentation for a symbol.
+int doc_section(char* text, int length) {
+ int start = 0, end, a, b;
+ while (start < length) {
+ end = start; while (end < length && text[end] != 10) end++;
+ a = start; b = end;
+ while (a < b && (text[a] == 32 || text[a] == 9 || text[a] == 13)) a++;
+ while (b > a && (text[b - 1] == 32 || text[b - 1] == 9 || text[b - 1] == 13)) b--;
+ if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1;
+ start = end + 1;
+ }
+ return 0;
+}
+
// attach the doc comment above a declaration at name n.
-int doc_attach(word n) {
+int doc_attach(char* n) {
int i = 0, w, k, c, b, nw;
- if (!dsn || !n || dry || ginst) return 0;
+ if (!dsn || !n || dry || ginst || doc_section(dsnap, dsn)) return 0;
b = doc_pack(dsnap, dsn);
- doc_name_room(len_w(n) + 5);
+ doc_name_room(rt.len_w(n) + 5);
i = 0; while ("doc "[i]) { dnmb[i] = "doc "[i]; i++; }
k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; }
dnmb[i] = 0;
@@ -3684,50 +4760,51 @@ int doc_attach(word n) {
dsn = 0;
return 0;
}
-// the signature of a function as source text, "int put_c(int ch)", kept in the
-// image as the symbol "sig name" when docs are on, so the language server can
-// show it for any genome function, with or without a doc comment. the text is
-// printed through put_c with the output sent to sgb (the same redirect the
-// check uses for a parse word that prints)
-[400]word sgb_initial; word sgb = sgb_initial; int sigcap = 400;
-int sig_print(word n, int rty, int np, word pn, word pty, int typed) {
+//: the signature of a function as source text, "int put_c(int ch)", kept in the
+//: image as the symbol "sig name" when docs are on, so the language server can
+//: show it for any genome function, with or without a doc comment. the text is
+//: printed through put_c with the output sent to sgb (the same redirect the
+//: check uses for a parse word that prints)
+char[400] sgb_initial; char* sgb = sgb_initial; int sigcap = 400;
+int sig_print(char* n, int rty, int np, char** pn, word pty, int typed) {
int i = 0;
- if (typed) { tprint(rty); put_c(' '); } else put_s("fn ");
- put_s(n); put_c('(');
+ if (typed) { tprint(rty); rt.put_c(' '); } else rt.put_s("fn ");
+ rt.put_s(n); rt.put_c('(');
while (i < np) {
- if (i) put_s(", ");
- if (typed) { tprint(pty[i]); put_c(' '); }
- put_s(pn[i]); i++;
+ if (i) rt.put_s(", ");
+ if (typed) { tprint(pty[i]); rt.put_c(' '); }
+ rt.put_s(pn[i]); i++;
}
- put_c(')'); return 0;
+ rt.put_c(')'); return 0;
}
// attach a complete signature, measured before capture.
-int sig_attach(word n, int rty, int np, word pn, word pty, int typed) {
+int sig_attach(char* n, int rty, int np, char** pn, word pty, int typed) {
int i = 0, k, b, nw, sm, sb, sn, sc, sl, w, c;
if (!docs_on || dry || ginst || ntp || panic) return 0;
- sm = outmode; sb = outbuf; sn = outn; sc = outcap;
- outmode = 3; outn = 0; sig_print(n, rty, np, pn, pty, typed);
- if (outn > sigcap) { while (outn > sigcap) sigcap = sigcap * 2; sgb = chk_resize(sgb, 0, sigcap); }
- outmode = 2; outbuf = sgb; outn = 0; outcap = sigcap;
+ sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap;
+ rt.outmode = 3; rt.outn = 0; sig_print(n, rty, np, pn, pty, typed);
+ if (rt.outn > sigcap) { while (rt.outn > sigcap) sigcap = sigcap * 2; sgb = chk_resize(sgb, 0, sigcap); }
+ rt.outmode = 2; rt.outbuf = sgb; rt.outn = 0; rt.outcap = sigcap;
sig_print(n, rty, np, pn, pty, typed);
- sl = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ sl = rt.outn;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
b = doc_pack(sgb, sl);
- doc_name_room(len_w(n) + 5);
+ doc_name_room(rt.len_w(n) + 5);
i = 0; while ("sig "[i]) { dnmb[i] = "sig "[i]; i++; }
k = 0; while (n[k]) { dnmb[i] = n[k]; i++; k++; }
dnmb[i] = 0;
addsym(intern(dnmb), K_DOC, b);
return 0;
}
-// the field of a struct type, or -1
-// ---- built-ins that look like functions ----
+//: the field of a struct type, or -1
+//: ---- built-ins that look like functions ----
+
// sizeof and typeof take a type or an expression; the expression form is
// compiled with dry set so it contributes only its type. len, cap, push, pop
-// and resize work on growable arrays ([]T handles in rt.zc); arrarg parses
+// and resize work on growable arrays (T[?] handles in rt.zc); arrarg parses
// their first argument and remembers which local it was, so the o3 and
// bounds-proof machinery can see it.
-int tfindfield(word t, int n) {
+int tfindfield(word t, char* n) {
int i = 0;
tresolve(t);
while (i < t[3]) {
@@ -3751,27 +4828,33 @@ int typearg() {
return t;
}
// len(a), cap(a), push(a, x), pop(a), resize(a, n): growable arrays.
-// len of a [n]T is just n.
-word arrarg() {
- int c;
- expect(s_lp); c = expr();
+// len of a T[n] is just n.
+word arrvalue(int c) {
argloc = -1; arrloc = -1;
if (lv && lvk == LV_LOCAL && xty[0] == TY_DYN) {
arrloc = lvi;
if (o3on && o3cache[lvi]) argloc = lvi;
}
- rval(); plainuse(c); af_access(xaf, tpack());
+ effect_value = effect_mask(c); rval(); plainuse(c); af_access(xaf, tpack());
return xty;
}
+// parse an array argument and retain the receiver's mutation and bounds facts.
+word arrarg() {
+ int c;
+ expect(s_lp); c = expr();
+ return arrvalue(c);
+}
// report an error when a fixed array is asked to grow or shrink.
int nogrow(word t) {
- if (t[0] == TY_DYN && t[3]) serr("a fixed array can't grow or shrink");
+ require_mutable(t);
+ if (t[0] == TY_ARR || (t[0] == TY_DYN && t[3])) serr("a fixed array can't grow or shrink");
return 0;
}
// compile len(a): the length of an array.
-int kw_len() {
- word t; int a;
- next(); t = arrarg(); a = arrloc; expect(s_rp);
+int array_len(word t) {
+ int a = arrloc;
+ xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0;
+ expect(s_rp);
if (xstr) {
callname(s_len_s);
xty = tint;
@@ -3788,35 +4871,38 @@ int kw_len() {
if (a >= 0 && t[0] == TY_DYN && t[3]) xlenloc = a; // the whole expression is len of a fixed array
return 0;
}
+int kw_len() { next(); return array_len(arrarg()); }
// compile cap(a): the capacity of a growable array.
-int kw_cap() {
- word t;
- next();
- t = arrarg();
+int array_cap(word t) {
+ xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0;
expect(s_rp);
- tcneed(t[0] == TY_DYN || t == tword, "cap needs a []T", t);
- callname(s_dcap);
+ if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); }
+ else { tcneed(t[0] == TY_DYN || t == tword, "cap needs an array", t); callname(s_dcap); }
xty = tint;
return 0;
}
+int kw_cap() { next(); return array_cap(arrarg()); }
// compile push(a, v): add an element to the end of a growable array.
-int kw_push() {
- word t; int c;
- next(); t = arrarg(); af_escape(xaf); nogrow(t);
- if (t[0] == TY_DYN && tsz(t[4]) != 1) serr("push takes one-word elements; use resize for structs");
- expect(s_comma); c = expr(); rval(); argcheck(c, 2);
+int array_push(word t) {
+ int c;
+ tcneed(t[0] == TY_DYN || t == tword, "push needs a T[?]", t);
+ effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack()); nogrow(t);
+ if (t[0] == TY_DYN && isagg(t[4])) serr("push takes scalar or reference elements; use resize for aggregates");
+ c = expr(); rval(); if (t[0] == TY_DYN) fcoerce(t[4]);
+ if (isptr(xty) || xty == tword) effect_note_mask(effect_mask(c), EF_RETAIN); argcheck(c, 2);
if (t[0] == TY_DYN && !compat(t[4], xty)) tcerr("push", t[4], xty);
if (o3on && t[0] == TY_DYN && hasptr(t[4])) { lit(t[4]); callname(s_fatten); }
expect(s_rp);
if (t[0] == TY_DYN && tbytes(t[4])) { // packed: an f32 goes in as its 32 bits, the rest are cut by the store
if (isfl(t[4])) { if (!xfloat) op(OP_I2F); xenh("zf2s"); }
- callname(intern("dpushb")); xty = tint; xfloat = 0;
+ callname(intern("dpushb")); xty = tint; xfloat = 0; xcls = C_PLAIN;
return 0;
}
- if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) { pushfast(); xty = tint; xfloat = 0; return 0; }
- callname(s_dpush); xty = tint;
+ if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) { pushfast(); xty = tint; xfloat = 0; xcls = C_PLAIN; return 0; }
+ callname(s_dpush); xty = tint; xfloat = 0; xcls = C_PLAIN;
return 0;
}
+int kw_push() { word t; next(); t = arrarg(); expect(s_comma); return array_push(t); }
// pop in o3 code, inline: a non-empty array gives up its last word, an empty one is dpop's report
int popfast() {
int k, e;
@@ -3825,7 +4911,7 @@ int popfast() {
lit(0); op(O_PICK); op(O_LD); lit(1); op(O_SUB); lit(1); op(O_PICK); op(O_ST);
lit(0); op(O_PICK); op(O_LD);
lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD);
- op(O_ADD); op(O_LD);
+ op(O_ADD); lit(0); op(O_PICK); op(O_LD); op(O_SWAP); lit(0); op(O_SWAP); op(O_ST);
op(O_SWAP); op(O_DROP);
e = hole(); op(O_JMP);
setlit(k, here);
@@ -3853,41 +4939,63 @@ int pushfast() {
return 0;
}
// compile pop(a): remove and return the last element of a growable array.
-int kw_pop() {
- word t;
- next();
- t = arrarg(); af_escape(xaf);
+int array_pop(word t) {
+ xcls = C_PLAIN;
+ tcneed(t[0] == TY_DYN || t == tword, "pop needs a T[?]", t);
+ effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack());
nogrow(t);
+ if (t[0] == TY_DYN && isagg(t[4])) serr("pop takes scalar or reference elements; access the last aggregate before resizing");
expect(s_rp);
if (t[0] == TY_DYN && tbytes(t[4])) { // packed: read zero extended, then sign extend or widen
callname(intern("dpopb"));
xty = t[4]; xfloat = 0;
+ xaf = 0; xbor = -1; effect_value = 0;
pkfix(xty);
if (isfl(xty)) xfloat = 1;
return 0;
}
- if (o3on && t[0] == TY_DYN && tsz(t[4]) == 1) popfast(); else callname(s_dpop);
+ if (o3on && t[0] == TY_DYN && !hasptr(t[4]) && tsz(t[4]) == 1) popfast(); else callname(s_dpop);
xty = telem(t);
if (t[0] == TY_DYN) xty = t[4];
xfloat = 0;
if (isfl(xty)) xfloat = 1;
+ xaf = 0; xbor = -1; effect_value = 0;
+ if (isref(xty)) {
+ xcls = C_FRESH; xaf = af_new(-1, 2, tpack()); newsz = -1; newleaf = 0; return C_FRESH;
+ }
return 0;
}
+int kw_pop() { next(); return array_pop(arrarg()); }
// compile resize(a, n): set the length of a growable array.
-int kw_resize() {
- word t; int c;
- next();
- t = arrarg(); af_escape(xaf);
+int array_resize(word t) {
+ int c;
+ tcneed(t[0] == TY_DYN || t == tword, "resize needs a T[?]", t);
+ effect_note_mask(effect_value, EF_WRITE); af_mutate(xaf, tpack());
nogrow(t);
- expect(s_comma);
c = expr();
rval();
+ tcneed(isnum(xty) || isfl(xty) || xty == tword, "resize needs a numeric length", xty);
+ fcoerce(tint);
plainuse(c);
expect(s_rp);
if (t[0] == TY_DYN && tbytes(t[4])) callname(intern("dresizeb")); else callname(s_dresize);
- xty = tint;
+ xty = tint; xcls = C_PLAIN; xfloat = 0;
return 0;
}
+int kw_resize() { word t; next(); t = arrarg(); expect(s_comma); return array_resize(t); }
+// array receiver calls share the free-call lowering and ownership checks.
+int array_method(int n) {
+ return n == intern("len") || n == intern("cap") || n == intern("push") || n == intern("pop") || n == intern("resize");
+}
+int array_member(int c, int n) {
+ word t = arrvalue(c);
+ expect(s_lp);
+ if (n == intern("len")) return array_len(t);
+ if (n == intern("cap")) return array_cap(t);
+ if (n == intern("push")) return array_push(t);
+ if (n == intern("pop")) return array_pop(t);
+ return array_resize(t);
+}
// compile sizeof(T) or sizeof(e): the size of a type or expression in words.
int kw_sizeof() {
int t;
@@ -3906,7 +5014,8 @@ int kw_typeof() {
xty = tword;
return 0;
}
-// ---- c abi: extern and cfunc ----
+//: ---- c abi: extern and cfunc ----
+
// extern double pow(double, double); or with parameter names, and an
// optional "= "cname"" to give the zc function another name than the c symbol.
// the declaration is read with c's spelling of types and turned into an
@@ -3926,7 +5035,7 @@ int eb_char(int c) {
if (ebn + 2 > ebcap) { ebcap = ebcap * 2; ebuf = chk_resize(ebuf, ebn, ebcap); }
ebuf[ebn] = c; ebn++; ebuf[ebn] = 0; return 0;
}
-int ebs(word s) {
+int ebs(char* s) {
while (*s) { eb_char(*s); s++; }
ebuf[ebn] = 0;
return 0;
@@ -3940,14 +5049,14 @@ int ebn10(int n) {
}
// true for a c type keyword, like long, char or double.
int ctypeword(int w) {
- return eq_w(w, "unsigned") || eq_w(w, "signed") || eq_w(w, "long") || eq_w(w, "short") || eq_w(w, "int") || eq_w(w, "char") || eq_w(w, "double") || eq_w(w, "float") || eq_w(w, "void") || eq_w(w, "const") || eq_w(w, "volatile");
+ return rt.eq_w(w, "unsigned") || rt.eq_w(w, "signed") || rt.eq_w(w, "long") || rt.eq_w(w, "short") || rt.eq_w(w, "int") || rt.eq_w(w, "char") || rt.eq_w(w, "double") || rt.eq_w(w, "float") || rt.eq_w(w, "void") || rt.eq_w(w, "const") || rt.eq_w(w, "volatile");
}
// a c type at the current token: its signature kind, or -1 after an error.
// skipname also eats a parameter name that follows.
// a plain zc struct a native call takes by value: every field is one word (a long,
// a double or a pointer), so word g of the struct is eightbyte g. returns its
// word count, or -1 after an error.
-int cstruct_ok(word st, int nm) {
+int cstruct_ok(word st, char* nm) {
int i = 0, n; word ft;
tresolve(st);
n = st[1];
@@ -3961,27 +5070,27 @@ int cstruct_ok(word st, int nm) {
}
// the native signature kind of the c type at the current token, or -1 after an error.
int ckind(int skipname) {
- int w, uns = 0, lng = 0, shrt = 0, chr = 0, ints = 0, dbl = 0, flt = 0, vd = 0, other = 0, ptr = 0, seen = 0, k = -1, sw = 0, sk, nw;
+ char* w; int uns = 0, lng = 0, shrt = 0, chr = 0, ints = 0, dbl = 0, flt = 0, vd = 0, ptr = 0, seen = 0, k = -1, sk, nw; word other = 0; char* sw = 0;
ckst = 0; ckn = 0;
while (tk == T_ID) {
w = tv;
- if (eq_w(w, "const") || eq_w(w, "volatile") || eq_w(w, "restrict")) { next(); continue; }
- if (eq_w(w, "struct") || eq_w(w, "enum") || eq_w(w, "union")) {
+ if (rt.eq_w(w, "const") || rt.eq_w(w, "volatile") || rt.eq_w(w, "restrict")) { next(); continue; }
+ if (rt.eq_w(w, "struct") || rt.eq_w(w, "enum") || rt.eq_w(w, "union")) {
next();
- if (tk == T_ID) { if (eq_w(w, "struct")) sw = tv; next(); }
+ if (tk == T_ID) { if (rt.eq_w(w, "struct")) sw = tv; next(); }
other = 1; seen = 1;
continue;
}
if (seen && !ctypeword(w)) break;
- if (eq_w(w, "unsigned")) uns = 1;
- else if (eq_w(w, "signed")) ints = 1;
- else if (eq_w(w, "long")) lng++;
- else if (eq_w(w, "short")) shrt = 1;
- else if (eq_w(w, "char")) chr = 1;
- else if (eq_w(w, "int")) ints = 1;
- else if (eq_w(w, "double") || eq_w(w, "f64")) dbl = 1;
- else if (eq_w(w, "float") || eq_w(w, "f32")) flt = 1;
- else if (eq_w(w, "void")) vd = 1;
+ if (rt.eq_w(w, "unsigned")) uns = 1;
+ else if (rt.eq_w(w, "signed")) ints = 1;
+ else if (rt.eq_w(w, "long")) lng++;
+ else if (rt.eq_w(w, "short")) shrt = 1;
+ else if (rt.eq_w(w, "char")) chr = 1;
+ else if (rt.eq_w(w, "int")) ints = 1;
+ else if (rt.eq_w(w, "double") || rt.eq_w(w, "f64")) dbl = 1;
+ else if (rt.eq_w(w, "float") || rt.eq_w(w, "f32")) flt = 1;
+ else if (rt.eq_w(w, "void")) vd = 1;
else other = w;
seen = 1;
next();
@@ -3993,7 +5102,7 @@ int ckind(int skipname) {
}
if (skipname && tk == T_ID) next();
if (ptr) {
- if (other && other != 1 && eq_w(other, "word")) return 6;
+ if (other && other != 1 && rt.eq_w(other, "word")) return 6;
return 3;
}
if (dbl) return 2;
@@ -4013,22 +5122,22 @@ int ckind(int skipname) {
if (nw <= 2) return 13; // in registers when they fit (sysv: 16 bytes or less)
return 14; // in memory: on the stack, word after word
}
- if (eq_w(other, "str")) return 12;
- if (eq_w(other, "size_t") || eq_w(other, "ssize_t") || eq_w(other, "intptr_t") || eq_w(other, "uintptr_t") || eq_w(other, "ptrdiff_t") || eq_w(other, "int64_t") || eq_w(other, "uint64_t") || eq_w(other, "off_t") || eq_w(other, "time_t") || eq_w(other, "word")) return 1;
- if (eq_w(other, "int32_t") || eq_w(other, "i32")) return 4;
- if (eq_w(other, "uint32_t") || eq_w(other, "u32")) return 7;
- if (eq_w(other, "int16_t") || eq_w(other, "i16")) return 8;
- if (eq_w(other, "uint16_t") || eq_w(other, "u16")) return 9;
- if (eq_w(other, "int8_t") || eq_w(other, "i8")) return 10;
- if (eq_w(other, "uint8_t") || eq_w(other, "u8") || eq_w(other, "byte") || eq_w(other, "bool") || eq_w(other, "_Bool")) return 11;
+ if (rt.eq_w(other, "str")) return 12;
+ if (rt.eq_w(other, "size_t") || rt.eq_w(other, "ssize_t") || rt.eq_w(other, "intptr_t") || rt.eq_w(other, "uintptr_t") || rt.eq_w(other, "ptrdiff_t") || rt.eq_w(other, "int64_t") || rt.eq_w(other, "uint64_t") || rt.eq_w(other, "off_t") || rt.eq_w(other, "time_t") || rt.eq_w(other, "word")) return 1;
+ if (rt.eq_w(other, "int32_t") || rt.eq_w(other, "i32")) return 4;
+ if (rt.eq_w(other, "uint32_t") || rt.eq_w(other, "u32")) return 7;
+ if (rt.eq_w(other, "int16_t") || rt.eq_w(other, "i16")) return 8;
+ if (rt.eq_w(other, "uint16_t") || rt.eq_w(other, "u16")) return 9;
+ if (rt.eq_w(other, "int8_t") || rt.eq_w(other, "i8")) return 10;
+ if (rt.eq_w(other, "uint8_t") || rt.eq_w(other, "u8") || rt.eq_w(other, "byte") || rt.eq_w(other, "bool") || rt.eq_w(other, "_Bool")) return 11;
serrn(other, "isn't a c type extern knows (use long, int, double, a pointer, ...)");
return 0 - 1;
}
// true for a native kind that travels in a float register.
int ckindfloat(int k) { return k == 2 || k == 5; }
// parse an extern declaration: a c function with its signature, and a zc stub for it.
-unchecked int kw_extern() {
- int rk; [16]word ks; [16]word kst; [16]word ksn; int np = 0, k, fname; word cname; int i, a, j, n, w, sig, bad = 0; word buf; int pos, len, g; word st, rst; int rn, rsd, sret = 0;
+int kw_extern() {
+ int rk; word[16] ks; word[16] kst; char*[16] ksn; int np = 0, k; char* fname; char* cname; int i, a, j, n, w, sig, bad = 0; char* buf; word oldimports = module_imports; int pos, len, g; word st, rst; int rn, rsd, sret = 0;
stmtonly();
next();
rk = ckind(0);
@@ -4097,7 +5206,7 @@ unchecked int kw_extern() {
}
j = hole();
op(O_JMP);
- reserve_code(20 + (len_w(cname) >> 3)); a = here;
+ reserve_code(20 + (rt.len_w(cname) >> 3)); a = here;
emit((a + 18) << 3);
emit(sig);
emit(rsd << 32); // the cache's half is the host's; this half is the result's descriptor
@@ -4118,7 +5227,7 @@ unchecked int kw_extern() {
// for the places that take the function's address; a direct call to it
// compiles to the native-call opcode instead (see call)
ebn = 0; ebuf[0] = 0;
- if (sret) { ebs("unchecked "); ebs(rn); ebs("*.u "); }
+ if (sret) { ebs(rn); ebs("*.u "); }
else if (rk == 5) ebs("f32 "); else if (ckindfloat(rk)) ebs("float "); else ebs("int ");
ebs(fname); ebs("(");
i = 0;
@@ -4129,24 +5238,28 @@ unchecked int kw_extern() {
i++;
}
ebs(") { let b = "); ebn10(a); ebs(";");
- if (sret) { ebs(" "); ebs(rn); ebs("* r = new("); ebs(rn); ebs("); b[3] = r;"); }
+ // the native descriptor borrows an integer address, not ownership of r.
+ // clear that scratch address after the call so it cannot keep the result rooted.
+ if (sret) { ebs(" "); ebs(rn); ebs("* r = rt.new("); ebs(rn); ebs("); b[3] = core.addr_b(r) >> 3;"); }
i = 0;
while (i < np) { ebs(" b["); ebn10(i + 3 + sret); ebs("] = p"); ebn10(i); ebs(";"); i++; }
- if (sret) ebs(" syscall(14, b, 0, 0); return r; }");
+ if (sret) ebs(" core.syscall(14, b, 0, 0); b[3] = 0; return r; }");
else {
- ebs(" return syscall(14, b, 0, 0)");
+ ebs(" return core.syscall(14, b, 0, 0)");
if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float");
ebs("; }");
}
- buf = new(ebn + 2);
- copy_w(buf, ebuf, ebn + 1);
+ buf = rt.new(ebn + 19);
+ rt.copy_w(buf, "include rt, core; ", 18);
+ rt.copy_w(buf + 18, ebuf, ebn + 1);
src_push(2, buf, 0, 0);
next();
stubgen = 1; // the stub is ours, not the document's: its locals get no hints
while (tk != T_EOF) topitem();
stubgen = 0;
src_pop();
- free(buf);
+ module_imports = oldimports;
+ rt.free(buf);
k = lookup(fname);
if (k >= 0 && skind[k] == K_FN && (sflag[k] & F_DEF) && !sret) sflag[k] = sflag[k] | F_EXT; // a struct result is called as its stub
return 0;
@@ -4174,7 +5287,7 @@ int kw_cfunc() {
next();
expect(s_lp);
if (tk != T_ID) { die("cfunc needs the name of a function"); return 0; }
- s = lookup(tv);
+ s = source_lookup(tv);
if (s < 0 || skind[s] != K_FN) { serrn(tv, "isn't a function"); return 0; }
t = sigof(sval[s]);
if (!t) t = stype[s];
@@ -4199,6 +5312,7 @@ int kw_cfunc() {
// is_id(s_fn) is one pointer compare.
int types_init() {
word f32p;
+ readonly_types = 0;
tytop = R_TY + NTL; tyend = R_TYEND; typeblocks = 0; ntl = 0; fieldn = 0;
if (R_TY + typecap > R_TYEND && typelist == R_TY) typelist = chk_resize(0, 0, typecap);
tword = tnew(TY_WORD, 1, intern("word"), 0);
@@ -4207,6 +5321,7 @@ int types_init() {
tbool = tnew(TY_BOOL, 1, intern("bool"), 0);
tvoid = tnew(TY_VOID, 0, intern("void"), 0);
tfloat = tnew(TY_FLOAT, 1, intern("float"), 0);
+ tbstr = tnew(TY_BSTR, 1, intern("bstr"), 0); addsym(tbstr[2], K_TYPE, tbstr);
addsym(tword[2], K_TYPE, tword); addsym(tint[2], K_TYPE, tint); addsym(tchar[2], K_TYPE, tchar);
addsym(tbool[2], K_TYPE, tbool); addsym(tvoid[2], K_TYPE, tvoid); addsym(tfloat[2], K_TYPE, tfloat);
addsym(intern("f64"), K_TYPE, tfloat);
@@ -4224,39 +5339,39 @@ int types_init() {
s_le = intern("<=");
s_ge = intern(">=");
s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+");
- s_struct = intern("struct"); s_type = intern("type"); s_typecheck = intern("typecheck"); s_docs = intern("docs");
+ s_struct = intern("struct"); s_type = intern("type"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small");
s_on = intern("on");
s_off = intern("off");
- s_new = intern("new");
- s_new_t = intern("new_t");
- s_new_k = intern("new_k");
- s_recycle_leaf = intern("recycle_leaf");
- s_hitems = intern("#items"); s_hlen = intern("#len"); s_fatten = intern("fatten");
+ s_new = intern("rt.new");
+ s_new_t = intern("rt.new_t");
+ s_new_k = intern("rt.new_k");
+ s_recycle_leaf = intern("rt.recycle_leaf");
+ s_hitems = intern("#items"); s_hlen = intern("#len"); s_fatten = intern("run.fatten");
s_this = intern("this");
s_o3 = intern("o3");
- s_lfa = intern("lfa");
- s_outside = intern("outside");
- s_new_raw = intern("new_raw");
- s_oob = intern("oob");
- s_new_oz = intern("new_oz");
- s_ld8 = intern("ld8"); s_st8 = intern("st8"); s_ld16 = intern("ld16"); s_st16 = intern("st16");
- s_ld32 = intern("ld32"); s_st32 = intern("st32");
+ s_lfa = intern("rt.lfa");
+ s_outside = intern("rt.outside");
+ s_new_raw = intern("rt.new_raw");
+ s_oob = intern("rt.oob");
+ s_new_oz = intern("rt.new_oz");
+ s_ld8 = intern("rt.ld8"); s_st8 = intern("rt.st8"); s_ld16 = intern("rt.ld16"); s_st16 = intern("rt.st16");
+ s_ld32 = intern("rt.ld32"); s_st32 = intern("rt.st32");
s_fixed = intern("fixed");
- s_dlen = intern("dlen");
- s_dcap = intern("dcap");
- s_dnew = intern("dnew");
- s_dat = intern("dat");
- s_len_s = intern("len_s");
- s_at_s = intern("at_s");
- s_slice_s = intern("slice_s");
- s_cat_s = intern("cat_s");
- s_eq_s = intern("eq_s");
- s_lt_s = intern("lt_s");
- s_le_s = intern("le_s");
- s_put_bs = intern("put_bs");
- s_put_s = intern("put_s");
- s_n_to_s = intern("n_to_s"); s_new_s = intern("new_s");
- s_dpush = intern("dpush"); s_dpop = intern("dpop"); s_dresize = intern("dresize");
+ s_dlen = intern("rt.dlen");
+ s_dcap = intern("rt.dcap");
+ s_dnew = intern("rt.dnew");
+ s_dat = intern("rt.dat");
+ s_len_s = intern("rt.len_s");
+ s_at_s = intern("rt.at_s");
+ s_slice_s = intern("rt.slice_s");
+ s_cat_s = intern("rt.cat_s");
+ s_eq_s = intern("rt.eq_s");
+ s_lt_s = intern("rt.lt_s");
+ s_le_s = intern("rt.le_s");
+ s_put_bs = intern("rt.put_bs");
+ s_put_s = intern("rt.put_s");
+ s_n_to_s = intern("rt.n_to_s"); s_new_s = intern("rt.new_s");
+ s_dpush = intern("rt.dpush"); s_dpop = intern("rt.dpop"); s_dresize = intern("rt.dresize");
numops[0] = intern("*"); numops[1] = intern("/"); numops[2] = intern("%"); numops[3] = intern("&");
numops[4] = intern("|");
numops[5] = intern("^");
@@ -4278,62 +5393,64 @@ int isnumop(int o) {
// the result type of binary operator b, which is also where the typechecker
// complains: pointer plus int is that pointer, pointer minus pointer is an int,
// word on either side wins, and a float on either side makes arithmetic float.
-word bintype(int b, word lt, word rt) {
+word bintype(int b, word lt, word rhs_type) {
int o = bname[b];
- lt = decay(lt); rt = decay(rt);
- if (blen[b] < 0) { tcneed(!isagg(lt) && !isagg(rt), "&& and || need scalars", lt); return tbool; }
- if (isfl(lt) || isfl(rt)) {
+ lt = decay(lt); rhs_type = decay(rhs_type);
+ if (blen[b] < 0) { tcneed(!isagg(lt) && !isagg(rhs_type), "&& and || need scalars", lt); return tbool; }
+ if (isfl(lt) || isfl(rhs_type)) {
if (o == s_plus || o == s_minus || o == s_star || o == s_slash) {
- if (lt[0] == TY_PTR || rt[0] == TY_PTR) { tcerr("+", lt, rt); return tword; }
+ if (lt[0] == TY_PTR || rhs_type[0] == TY_PTR) { tcerr("+", lt, rhs_type); return tword; }
return tfloat;
}
if (o == s_eqeq || o == s_ne || o == s_lt || o == s_gt || o == s_le || o == s_ge) return tbool;
}
if (o == s_eqeq || o == s_ne) {
- if (!compat(lt, rt) && !compat(rt, lt)) tcerr("comparison", lt, rt);
+ if (!compat(lt, rhs_type) && !compat(rhs_type, lt)) tcerr("comparison", lt, rhs_type);
return tbool;
}
if (o == s_lt || o == s_gt || o == s_le || o == s_ge) {
- if (!(lt == tword || rt == tword || (isnum(lt) && isnum(rt)) || (lt[0] == TY_PTR && rt[0] == TY_PTR))) {
- tcerr("comparison", lt, rt);
+ if (!(lt == tword || rhs_type == tword || (isnum(lt) && isnum(rhs_type)) || (lt[0] == TY_PTR && rhs_type[0] == TY_PTR))) {
+ tcerr("comparison", lt, rhs_type);
}
return tbool;
}
if (o == s_plus) {
- if (lt[0] == TY_PTR && (isnum(rt) || rt == tword)) return lt;
- if (rt[0] == TY_PTR && isnum(lt)) return rt;
- if (lt == tword || rt == tword) return tword;
- if (isnum(lt) && isnum(rt)) return tint;
- tcerr("+", lt, rt); return tword;
+ if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt;
+ if (rhs_type[0] == TY_PTR && isnum(lt)) return rhs_type;
+ if (lt == tword || rhs_type == tword) return tword;
+ if (isnum(lt) && isnum(rhs_type)) return tint;
+ tcerr("+", lt, rhs_type); return tword;
}
if (o == s_minus) {
- if (lt[0] == TY_PTR && rt[0] == TY_PTR) return tint;
- if (lt[0] == TY_PTR && (isnum(rt) || rt == tword)) return lt;
- if (lt == tword || rt == tword) return tword;
- if (isnum(lt) && isnum(rt)) return tint;
- tcerr("-", lt, rt); return tword;
+ if (lt[0] == TY_PTR && rhs_type[0] == TY_PTR) return tint;
+ if (lt[0] == TY_PTR && (isnum(rhs_type) || rhs_type == tword)) return lt;
+ if (lt == tword || rhs_type == tword) return tword;
+ if (isnum(lt) && isnum(rhs_type)) return tint;
+ tcerr("-", lt, rhs_type); return tword;
}
if (isnumop(o)) {
- if (lt == tword || rt == tword) return tword;
- if (isnum(lt) && isnum(rt)) return tint;
- if (isnum(lt)) lt = rt;
+ if (lt == tword || rhs_type == tword) return tword;
+ if (isnum(lt) && isnum(rhs_type)) return tint;
+ if (isnum(lt)) lt = rhs_type;
tcneed(0, "arithmetic needs numbers", lt); return tword;
}
return tword; // operators a program added
}
-// ---- reflection ----
+//: ---- reflection ----
+
// for programs (and parse words) looking at types at runtime
word tkind(word t) { return t[0]; }
// the size of a type in words.
word tsize(int t) { return tsz(t); }
// the name of a type.
-word tname(word t) { return t[2]; }
+char* tname(word t) { return t[2]; }
// the length of an array type.
word tlen(word t) { return t[3]; }
// the number of fields of a struct type.
word tnfields(word t) { tresolve(t); return t[3]; }
-// ---- fattening ----
+//: ---- fattening ----
+
// typed new stamps the header, in o3 the same as outside it. fatten is for a
// value that still has new_raw's blank type word when it reaches code that
// isn't o3: a store into a normal local or a global, an argument to a
@@ -4369,7 +5486,7 @@ word isleaf(word t) {
// true when a value of type t holds a pointer that fattening must follow.
word hasptr(word t) {
int k = t[0], i, n;
- if (k == TY_PTR || k == TY_DYN) return 1;
+ if (k == TY_PTR || k == TY_DYN || k == TY_BSTR) return 1;
if (k == TY_ARR) return hasptr(t[4]);
if (k == TY_STRUCT && !t[5]) {
n = tnfields(t);
@@ -4388,7 +5505,7 @@ int fatten(int p, int t) { fat1(p, t, 32); return p; }
// -(n + 1) for an object of n words, which tells how many elements to type.
int fat1(word p, word t, int dl) {
word e; int n, k, i, es;
- if (dl <= 0 || !t || !isobj(p)) return 0;
+ if (dl <= 0 || !t || !rt.isobj(p)) return 0;
if (t[0] == TY_DYN) { // a handle is always fat; what it holds may not be
if (hasptr(t[4])) {
es = tsz(t[4]);
@@ -4404,7 +5521,7 @@ int fat1(word p, word t, int dl) {
if (e == tword || e[0] == TY_VAR) p[-1] = 0;
else if (n > es) p[-1] = tarr(e, 0);
else p[-1] = e;
- if (osize(p) > n) fill_w(p + n, 0, osize(p) - n);
+ if (rt.osize(p) > n) rt.fill_w(p + n, 0, rt.osize(p) - n);
if (hasptr(e)) { k = n / es; i = 0; while (i < k) { fatin(p + i * es, e, dl - 1); i++; } }
return 0;
}
@@ -4435,7 +5552,7 @@ word fatin(word a, word t, int dl) {
}
// reflection accessors over type records (see the layout under types). they
// resolve a generic instance first, so asking about vec<int> works.
-word tfname(word t, int i) { tresolve(t); return t[4][i * 3]; }
+char* tfname(word t, int i) { tresolve(t); return t[4][i * 3]; }
// the type of field i of a struct.
word tftype(word t, int i) { tresolve(t); return t[4][i * 3 + 1]; }
// the offset of field i of a struct.
@@ -4457,10 +5574,11 @@ word tnargs(word t) {
}
// type argument i of a generic struct instance.
word targ(word t, int i) { return t[6][1 + i]; }
-// the type of a function, by address. untyped functions get one made from their signature
-// ---- function types by address ----
+//: the type of a function, by address. untyped functions get one made from their signature
+//: ---- function types by address ----
+
word sigof(int f) {
- int i = nsym, s = -1; [24]word ws; int n, k;
+ int i = nsym, s = -1; word[24] ws; int n, k;
while (i) { i--; if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) { s = i; break; } }
if (s < 0) return 0;
if (stype[s]) return stype[s];
@@ -4469,13 +5587,14 @@ word sigof(int f) {
return tfn(tword, n, ws, ssig[s]);
}
// the function type of the function named nm, or 0 when there is none.
-word sigofs(int nm) {
+word sigofs(char* nm) {
int s = lookups(nm);
if (s < 0 || skind[s] != K_FN) return 0;
return sigof(sval[s]);
}
-// ---- expressions ----
+//: ---- expressions ----
+
// every expression leaves its value on the stack, returns its analyzer class,
// and sets xty to its type. lv means the stack holds an address that hasn't
// been loaded yet; structs and arrays are never loaded, their address is
@@ -4497,7 +5616,13 @@ int dbg_addr() {
// the thing is an array or struct, whose address is its value. every place
// that needs a value calls this after parsing an expression.
int rval() {
- if (lv && lvk == LV_LOCAL) ltag[lvi] = ltag[lvi] | LREAD;
+ if (lv && lvk == LV_LOCAL) {
+ ltag[lvi] = ltag[lvi] | LREAD;
+ if (rt.eq_w(lname[lvi], "_") && af_warning(lreadpos[lvi])) {
+ rt.put_s("discard placeholder `_` is read\nhelp: give the parameter or pattern binding a name when its value is needed");
+ af_warning_end();
+ }
+ }
if (lv) {
if (ispk(xty)) pkld(xty);
else if (!isagg(xty)) { dbg_addr(); op(O_LD); }
@@ -4512,7 +5637,7 @@ int rval() {
int kwcall(int s, int e) {
int f = sval[s], old = kw_expr, c, n0 = ntok;
kw_expr = e; xcls = C_PLAIN; xty = tword; xzero = 0;
- f(); xaf = 0;
+ f(); xaf = 0; xvk = 0;
// a handler that never reads a token (not even its own name) would be called
// again on the same token forever, so it is an error, and the token is skipped
if (ntok == n0) die("a parse word has to read its own name with next()");
@@ -4533,20 +5658,47 @@ int stmtonly() {
// fat asks for every pointer argument to be fattened first, for o3 code
// calling code that is not o3.)
int args(int tags, word ft, int bs, int fat) {
- int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0;
+ int np = 0, c, pt, t, cs = ifcall, freeing = 0, harmless = 0; word first = 0; int origin = -1, anytemp = 0, current, tag, place, parameter_base = effect_argument_base, owner = -1, current_owner, owner_seen = 0, aliases; word origins = 0, current_mask, owner_fact = 0;
if (cs >= 0 && cs < ubase) {
freeing = sname[cs] == s_free || sname[cs] == s_recycle;
- harmless = sname[cs] == s_put_s || eq_w(sname[cs], "put_n") || eq_w(sname[cs], "put_c") || eq_w(sname[cs], "len") || eq_w(sname[cs], "cap");
+ harmless = sname[cs] == s_put_s || rt.eq_w(sname[cs], "rt.put_n") || rt.eq_w(sname[cs], "rt.put_c") || rt.eq_w(sname[cs], "len") || rt.eq_w(sname[cs], "cap");
}
- xgive = 0; ifcall = 0 - 1;
+ xgive = 0; ifcall = 0 - 1; effect_argument_base = 0;
expect(s_lp);
if (!is_op(s_rp)) {
while (1) {
- c = expr(); rval();
+ word expected = closure_expected;
+ closure_expected = 0;
+ if (ft && np + parameter_base < ft[3] && is_id(s_fn)) closure_expected = subst(ft[7 + np + parameter_base], bs);
+ place = tpack(); c = expr(); rval(); closure_expected = expected;
+ current = effect_origin(c); current_mask = effect_mask(c); origins = origins | current_mask; effect_args_mask(cs, np + parameter_base, current_mask);
+ if (!np) origin = current; else if (origin != current) origin = -1;
+ tag = (tags >> (np + np)) & 3;
+ current_owner = xbor;
+ if (c >= C_VAR && tracked(c - C_VAR)) current_owner = lroot(c - C_VAR);
+ if (tag <= 1 && current_owner >= 0 && tracked(current_owner)) effect_require_mode(cs, np + parameter_base, place, 1);
+ aliases = effect_get(cs, np + parameter_base);
+ if (aliases & (EF_ALIAS | EF_IDENTITY | EF_UNKNOWN)) {
+ if (!owner_seen) { owner = current_owner; owner_fact = xaf; owner_seen = 1; }
+ else if (owner != current_owner) { owner = -1; owner_fact = 0; }
+ }
+ if (fchk && c == C_FRESH && tag != 2) {
+ if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); emit_borrow_hold(); c = C_PLAIN; anytemp = 1;
+ }
+ if (xaf && xaf[2] == 5) { anytemp = 1; if (!(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place); }
+ if (fchk && xaf && xaf[2] == 4 && (isptr(xty) || isagg(xty)) && !(cs < 0 && tag == 1)) effect_require(cs, np + parameter_base, place);
if (!np) first = xaf;
- if (!freeing && !harmless) af_escape(xaf);
- if (cs >= 0) if_arg(cs, np, xty, xzero);
- if (ft && np < ft[3]) fcoerce(subst(ft[7 + np], bs));
+ if (!freeing && !harmless && (effect_get(cs, np + parameter_base) & (EF_WRITE | EF_RETAIN | EF_CONSUME | EF_UNKNOWN))) af_escape(xaf);
+ if (ifs && if_value && cs >= 0 && sname[cs] == s_new && !np) if_join(if_value, tint);
+ if (cs >= 0) if_arg(cs, np + parameter_base, xty, xzero);
+ if (if_value && cs >= 0 && np + parameter_base < IFP) {
+ word infer_callee = if_get(cs);
+ if (infer_callee) {
+ if (ft && np + parameter_base < ft[3]) if_join(infer_callee + 8 + 3 * (np + parameter_base), subst(ft[7 + np + parameter_base], bs));
+ if_link(if_value, infer_callee + 8 + 3 * (np + parameter_base));
+ }
+ }
+ if (ft && np + parameter_base < ft[3]) fcoerce(subst(ft[7 + np + parameter_base], bs));
t = xty;
argcheck(c, (tags >> (np + np)) & 3);
// o3 data handed to normal code
@@ -4555,8 +5707,9 @@ int args(int tags, word ft, int bs, int fat) {
callname(s_fatten);
xty = t;
}
- if (ft && np < ft[3]) {
- pt = ft[7 + np];
+ if (ft && np + parameter_base < ft[3]) {
+ pt = ft[7 + np + parameter_base];
+ if (cs >= 0 && sname[cs] == s_put_s && np == 0 && xstr) pt = tbstr;
if (!unify(pt, xty, bs)) tcerr("argument", subst(pt, bs), xty);
}
np++;
@@ -4564,7 +5717,7 @@ int args(int tags, word ft, int bs, int fat) {
}
}
expect(s_rp);
- argfact = first; xaf = 0;
+ argfact = first; argorigin = origin; argorigin_mask = origins; argtemporary = anytemp; argowner = owner; argowner_fact = owner_fact; xaf = 0; vf_effect();
return np;
}
// memdebug records the source line on the next new, via the runtime global
@@ -4579,7 +5732,7 @@ int stamp_site() {
return 0;
}
// new(T) and new(T, count): a typed object, its type stamped into the header
-// new(T), new(T, n), new([]T), new(fixed []T, n). a growable array goes to
+// new(T), new(T, n), new(T[?]), new(fixed T[?], n). a growable array goes to
// dnew. otherwise the call is new_t(size, type), unless the size is known at
// compile time and we are not in o3 or a dry run: then the size class is
// computed here and the call is new_k(size, type, class), which pops a block
@@ -4591,11 +5744,11 @@ int newtyped() {
if (t[0] == TY_DYN) { // a growable array: element size, length, type
if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4])); // packed elements say so with a negative size: minus their bytes
if (accept(s_comma)) { c = expr(); rval(); plainuse(c); length = -1; if (xconst && xcval >= 0) length = xcval; }
- else { length = 0; lit(0); if (t[3]) serr("a fixed array needs its length: new(fixed []T, n)"); }
+ else { length = 0; lit(0); if (t[3]) serr("a fixed array needs its length: new(fixed T[?], n)"); }
lit(t);
expect(s_rp);
stamp_site();
- callname(s_dnew);
+ callname(s_dnew); vf_effect();
xaf = af_new(length, 3, place);
lv = 0; lvk = LV_NONE; xty = t; xzero = 0; xstamp = 1;
return C_FRESH;
@@ -4617,7 +5770,7 @@ int newtyped() {
}
expect(s_rp);
stamp_site();
- if (fast) callname(s_new_k); else callname(s_new_t);
+ if (fast) callname(s_new_k); else callname(s_new_t); vf_effect();
xaf = af_new(length, 2, place);
lv = 0; lvk = LV_NONE; xty = tptr(t); xzero = 0; xstamp = 1;
return C_FRESH;
@@ -4635,7 +5788,8 @@ int byteop(int s) {
if (sname[s] == s_st32) return 617;
return 0;
}
-// ---- type facts ----
+//: ---- type facts ----
+
// annotate mode (the language server asks for it, ifon) collects the types the
// untyped declarations of a document could be given, as edits: a place, how
// many chars it replaces (0 inserts) and the text. a local gets the type of
@@ -4651,7 +5805,7 @@ word ifpure(word t) {
if (!t || t == tword || t == tvoid) return 0;
k = t[0];
if (k == TY_INT) return t[5] == 0;
- if (k == TY_CHAR || k == TY_BOOL || k == TY_STRUCT) return t[2] != 0;
+ if (k == TY_CHAR || k == TY_BOOL || k == TY_STRUCT || k == TY_BSTR) return t[2] != 0;
if (k == TY_PTR) {
k = t[4][0];
if (k == TY_ARR || k == TY_DYN || k == TY_FN) return 0;
@@ -4663,19 +5817,19 @@ word ifpure(word t) {
// one edit: the token at place p is replaced by the type t and then the text
// after (len < 0: the token's own length; 0: nothing is replaced)
int fact_add(int p, int len, int t, int after) {
- int sv, o, at, n; word e; int osm = outmode, osb = outbuf, osn = outn, osc = outcap;
+ int sv, o, at, n; word e; int osm = rt.outmode, osb = rt.outbuf, osn = rt.outn, osc = rt.outcap;
// the text is printed with tprint into the pool: output mode 2 writes into outbuf
if (!ifx || dry || panic || chkabort) return 0;
sv = tpack(); tunpack(p); o = toff; if (len < 0) len = toklen; tunpack(sv);
if (o < 0) return 0;
at = nift;
- outn = 0; outmode = 3; tprint(t); put_s(after); n = outn;
+ rt.outn = 0; rt.outmode = 3; tprint(t); rt.put_s(after); n = rt.outn;
if (nifx >= ifxcap) { ifxcap = ifxcap * 2; ifx = chk_resize(ifx, nifx * 4, ifxcap * 4); }
if (at + n > iftcap) { while (at + n > iftcap) iftcap = iftcap * 2; ift = chk_resize(ift, at, iftcap); }
- outbuf = ift + at; outn = 0; outcap = n; outmode = 2;
- tprint(t); put_s(after);
- n = outn;
- outmode = osm; outbuf = osb; outn = osn; outcap = osc;
+ rt.outbuf = ift + at; rt.outn = 0; rt.outcap = n; rt.outmode = 2;
+ tprint(t); rt.put_s(after);
+ n = rt.outn;
+ rt.outmode = osm; rt.outbuf = osb; rt.outn = osn; rt.outcap = osc;
e = ifx + nifx * 4;
e[0] = o; e[1] = len; e[2] = at; e[3] = n;
nifx++; nift = at + n;
@@ -4690,18 +5844,46 @@ word if_get(int s) {
if (nif >= ifrcap) { ifrcap = ifrcap * 2; ifr = chk_resize(ifr, nif, ifrcap); }
nif++; ifs[s] = nif;
r = chk_resize(0, 0, IFW); ifr[nif - 1] = r;
- fill_w(r, 0, IFW);
+ rt.fill_w(r, 0, IFW);
r[0] = s;
return r;
}
// a state is 0 (nothing seen), a type, or -1 (no single type): p points at it
int if_join(word p, int t) {
+ if (!t || t == tword) return 0;
if (*p == 0 - 1) return 0;
if (!ifpure(t)) *p = 0 - 1;
else if (!*p) *p = t;
else if (*p != t) *p = 0 - 1;
return 0;
}
+// equal type flows connect caller parameters, callee parameters and results.
+// nodes are addresses of inference states, retained by the function records.
+int if_link(word a, word b) {
+ word edge;
+ if (!ifs || !a || !b || a == b || dry) return 0;
+ edge = chk_resize(0, 0, 3); edge[0] = if_edges; edge[1] = a; edge[2] = b; if_edges = edge;
+ return 0;
+}
+int if_solve() {
+ word edge, a, b, record; int changed = 1, left, right, index = 0;
+ while (index < nif) {
+ record = ifr[index];
+ if (!record[3] && (!record[2] || !record[1])) record[3] = -1;
+ index++;
+ }
+ while (changed) {
+ changed = 0; edge = if_edges;
+ while (edge) {
+ a = edge[1]; b = edge[2]; left = *a; right = *b;
+ if (left == -1 || right == -1 || (left && right && left != right)) { *a = -1; *b = -1; }
+ else if (left) *b = left; else if (right) *a = right;
+ if (*a != left || *b != right) changed = 1;
+ edge = edge[0];
+ }
+ }
+ return 0;
+}
// argument j of a call of s has the type t (a literal 0 says nothing: it is a
// null pointer as often as a number)
int if_arg(int s, int j, int t, int zero) {
@@ -4723,10 +5905,10 @@ int if_begin(int s, int k) {
}
// parameter j: p is where its type would go, kind 0 for a typed one, 1 to
// insert the type and a space, 2 to insert it right in front of a tag
-int if_param(int j, int p, int kind) {
+int if_param(int j, int p, int kind, word type) {
word r = ifcur;
if (!r || j >= IFP) return 0;
- r[8 + 3 * j] = 0; r[9 + 3 * j] = p; r[10 + 3 * j] = kind;
+ if_join(r + 8 + 3 * j, type); r[9 + 3 * j] = p; r[10 + 3 * j] = kind;
if (kind) r[7] = j + 1;
return 0;
}
@@ -4735,6 +5917,7 @@ int if_ret(int t, int zero) {
word r = ifcur;
if (!r || nclo || dry) return 0;
r[1] = 1;
+ if (if_value && !zero) if_link(r + 3, if_value);
if (zero) r[4] = 1; else if_join(r + 3, t);
return 0;
}
@@ -4750,6 +5933,7 @@ int if_fin() {
// the edits of every function, once all the calls have been seen
int if_emit() {
int i = 0, j; word r; int t, k;
+ if_solve();
while (i < nif) {
r = ifr[i];
i++;
@@ -4771,28 +5955,30 @@ int if_emit() {
}
return 0;
}
-// ---- hints ----
+//: ---- hints ----
+
// findings that are not errors. each is written only in check mode, through
// hint_begin, and none of them changes the code the compiler emits.
int magic(int v) {
- if (!hint_begin(tpack())) return 0;
- put_s("the number "); put_n(v); put_s(" has no name; a const says what it means");
+ if (literal_documented(toff) || !hint_begin(tpack(), HINT_ALL)) return 0;
+ rt.put_s("unannotated numeric literal "); rt.put_n(v);
+ rt.put_s("\nhelp: use a named constant or add an inline block comment before the literal to suppress this hint");
dg_close();
return 0;
}
// warn that a new inside a loop allocates on every pass.
int newloop(int p) {
- if (!hint_begin(p)) return 0;
- put_s("new inside a loop allocates on every pass; allocate once above the loop if the object can be reused");
+ if (!hint_begin(p, HINT_SMALL)) return 0;
+ rt.put_s("new inside a loop allocates on every pass; allocate once above the loop if the object can be reused");
dg_close();
return 0;
}
// hint when a shift by v is past the width of the value.
int shifthint(int p, int v) {
int k = 0;
- if (!hint_begin(p)) return 0;
+ if (!hint_begin(p, HINT_ALL)) return 0;
while ((1 << k) < v) k++;
- put_s("multiplying by "); put_n(v); put_s(" is a shift; write << "); put_n(k);
+ rt.put_s("multiplying by "); rt.put_n(v); rt.put_s(" is a shift; write << "); rt.put_n(k);
dg_close();
return 0;
}
@@ -4811,11 +5997,13 @@ int shifthint(int p, int v) {
// 6. the result's type and class are set: .u functions return C_FRESH, which
// is how the analyzer knows the caller now owns a new object
int call(int s) {
- int np, tags = 0, ret = 0; word ft = stype[s]; [8]word bs; int rs, rt = 0, kop, cp = callpos, original = s; word storage;
+ int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact;
+ word requirements = effect_requirements, edges = effect_edges;
+ borrow_depth++;
if (sname[s] == s_new && skind[s] == K_FN) {
peek();
- if (pk_tk == T_OP && pk_tv == s_lk) { next(); return newtyped(); }
- if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { next(); return newtyped(); }
+ if (pk_tk == T_OP && pk_tv == s_lk) { next(); borrow_depth--; return newtyped(); }
+ if (pk_tk == T_ID && (istypename(pk_tv) || pk_tv == s_fn)) { next(); borrow_depth--; return newtyped(); }
}
if (o3on && sname[s] == s_new) { tags = lookup(s_new_oz); if (tags >= 0) s = tags; tags = 0; }
if (o3on) {
@@ -4831,21 +6019,21 @@ int call(int s) {
}
lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1;
xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0;
- return C_PLAIN;
+ borrow_depth--; return C_PLAIN;
}
}
if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; }
else sflag[s] = sflag[s] | F_UNTYPED;
if (sflag[s] & F_RETOWN) ret = 1;
- fill_w(bs, 0, 8);
- if (ngbind) { copy_w(bs, gbind, ngbind); ngbind = 0; }
+ rt.fill_w(bs, 0, 8);
+ if (ngbind) { rt.copy_w(bs, gbind, ngbind); ngbind = 0; }
np = 0;
if (o3on && !(sflag[s] & F_O3) && sname[s] != s_free && sname[s] != s_recycle) np = 1; // o3 calling normal code: fatten the arguments
- ifcall = s;
+ ifcall = s; effect_argument_base = 0;
np = args(tags, ft, bs, np);
- storage = argfact;
+ storage = argfact; origin = argorigin; origins = argorigin_mask; temporary = argtemporary; owner = argowner; owner_fact = argowner_fact;
if (original < ubase && (sname[original] == s_free || sname[original] == s_recycle)) af_end(storage, cp);
- if (sflag[s] & F_GEN) s = ginstance(s, bs);
+ if (sflag[s] & F_GEN) { int template = s; s = ginstance(s, bs); effect_specialize(template, s, requirements, edges); }
if (sname[s] == s_free && xgive) { // the last pointer: straight back to the size-class list
rs = lookup(s_recycle);
if (rs >= 0) s = rs;
@@ -4871,8 +6059,8 @@ int call(int s) {
if (sflag[s] & F_EXT) { lit(sval[s][-1]); op(620); } // an extern: the native call opcode, with the block's address
else { fnref(s); op(O_CALL); }
if (!o3on && (sflag[s] & F_O3) && ft) { // non-o3 called o3: fatten the result here, not inside the callee
- rt = subst(ft[4], bs);
- if (hasptr(rt)) { lit(rt); callname(s_fatten); }
+ rhs_type = subst(ft[4], bs);
+ if (hasptr(rhs_type)) { lit(rhs_type); callname(s_fatten); }
}
xstr = 0;
if (sname[s] == s_cat_s || sname[s] == s_slice_s || sname[s] == s_n_to_s || sname[s] == s_new_s) {
@@ -4881,10 +6069,28 @@ int call(int s) {
lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; xconst = 0;
xty = tword;
if (ft) xty = subst(ft[4], bs);
+ if (xstr || xty == tbstr) { xstr = 1; xty = tbstr; }
xfloat = 0;
if (isfl(xty)) xfloat = 1;
xlenloc = -1; xstamp = 0;
if (o3on && (sflag[s] & F_O3) && ft && hasptr(xty)) xstamp = 1;
+ borrow_depth--; effect_value = 0;
+ if (!ret && (isptr(xty) || xty == tword)) {
+ effect_value = origins;
+ if (owner >= 0 && (isptr(xty) || (effect_get(original, 0) & EF_IDENTITY))) xbor = owner;
+ if (owner_fact && (effect_get(original, 0) & EF_IDENTITY)) xaf = owner_fact;
+ if (origin >= 0 && owner < 0) { int oi = lbase; while (oi < nloc) { if (lrskind[oi] == RS_PARAM && oi - lbase == origin) { xbor = oi; break; } oi++; } }
+ if (temporary) { int ai = 0, aliases = 0; while (ai < np) { aliases = aliases | effect_get(original, ai); ai++; } if (aliases & (EF_ALIAS | EF_UNKNOWN)) xaf = af_new(-1, 5, cp); }
+ }
+ if_value = 0;
+ if (ifs && !dry) {
+ word result_node = if_get(original);
+ if (result_node) {
+ if (sname[original] == s_new && !result_node[3]) result_node[3] = tword;
+ else if_join(result_node + 3, xty);
+ if_value = result_node + 3;
+ }
+ }
if (ret) return C_FRESH;
return C_PLAIN;
}
@@ -4905,6 +6111,7 @@ int enhcall(int s) {
}
}
expect(s_rp);
+ vf_effect();
if (n != nin) serrn(sname[s], "wrong number of inputs");
lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1; xlenloc = -1; xstamp = 0;
if (sflag[s] & F_FALLBACK) { fnref(sfix[s] - 1); op(O_CALL); return C_PLAIN; }
@@ -4913,15 +6120,47 @@ int enhcall(int s) {
if (nout > 1) serrn(sname[s], "leaves several values; use it from asm");
return C_PLAIN;
}
-// ---- closures ----
+//: ---- closures ----
+
// fn(a, b) { ... } is a function whose last, hidden parameter is a pointer to
// its environment. the closure value is a heap block: word 0 is the body's
// entry, and the captured variables start at word 3. the block itself is never
// run as code. callable_code distinguishes plain function addresses, including
// linked heap code segments, from closure blocks. a plain function is called
// as it is. a closure is called with block + 3 as its env, at the entry in
-// word 0. a captured variable is copied in when the closure is made; the body
-// reads and writes its own copy.
+// word 0. a capture is a shared cell. the enclosing function and every closure
+// read and write the same value; cells remain reachable through escaping closures.
+// scan with the compiler lexer, restoring its source and lookahead. only functions containing a closure
+// need cells; their scalars get cells from declaration time, so loop back edges and earlier references agree.
+int capture_scan() {
+ word old, f; int depth = 1, found = 0, docs = docs_on, record = grec;
+ if (isp < 0 || R_SRC[isp * 32] == 0 || !is_op(s_lb)) return 0;
+ old = R_SRC + isp * 32;
+ src_push(1, capture_dummy, 0, 0); old = R_SRC + (isp - 1) * 32; f = R_SRC + isp * 32;
+ f[0] = old[0]; f[1] = old[1]; f[2] = old[2]; f[3] = old[3]; f[4] = 0; pq_n = f[14];
+ docs_on = 0; grec = 0; dry++;
+ while (depth) {
+ next(); if (tk == T_EOF) break;
+ if (is_op(s_lb)) depth++;
+ else if (is_op(s_rb)) depth--;
+ else if (is_id(s_fn)) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) found = 1; }
+ }
+ dry--; src_pop(); docs_on = docs; grec = record; return found;
+}
+int capture_cell(int i, int copy) {
+ if (!capture_enabled || o3on || (ltag[i] & 16) || larr[i] || local_cell[i]) return 0;
+ if (copy) { laddr(lslot[i]); op(O_LD); }
+ lit(1); lit(ltype[i]); callname(s_new_t); callname(intern("share"));
+ if (copy) { lit(0); op(O_PICK); laddr(lslot[i]); op(O_ST); op(O_ST); }
+ else { laddr(lslot[i]); op(O_ST); }
+ local_cell[i] = 1; return 0;
+}
+int capture_prepare() {
+ int i = lbase; capture_enabled = capture_scan();
+ if (!capture_enabled) return 0;
+ while (i < nloc) { if (lrskind[i] == RS_PARAM) capture_cell(i, 1); i++; }
+ return 0;
+}
// grow closure frames and capture rows while keeping closure and capture indices.
int reserve_closures(int need) {
int cap = closure_cap;
@@ -4938,22 +6177,22 @@ int reserve_closures(int need) {
closure_cap = cap; return cap;
}
int reserve_captures(int need) {
- int cap = capture_width, i = 0; word names, locals, via;
+ int cap = capture_width, i = 0; char** names; word locals, via;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
names = chk_resize(0, 0, closure_cap * cap);
locals = chk_resize(0, 0, closure_cap * cap);
via = chk_resize(0, 0, closure_cap * cap);
while (i < nclo) {
- copy_w(names + i * cap, clo_name + i * capture_width, clo_n[i]);
- copy_w(locals + i * cap, clo_loc + i * capture_width, clo_n[i]);
- copy_w(via + i * cap, clo_via + i * capture_width, clo_n[i]); i++;
+ rt.copy_w(names + i * cap, clo_name + i * capture_width, clo_n[i]);
+ rt.copy_w(locals + i * cap, clo_loc + i * capture_width, clo_n[i]);
+ rt.copy_w(via + i * cap, clo_via + i * capture_width, clo_n[i]); i++;
}
clo_name = names; clo_loc = locals; clo_via = via; capture_width = cap; return cap;
}
// find or create the capture slot for variable n in the innermost closure
// being compiled: if the name is one of the enclosing function's locals, it is
-// recorded (once) and the body reads it from its own environment copy.
+// recorded once and the body reads its shared cell through the environment.
// a name from further out is captured through each closure in between: the
// closure that encloses this one captures it too, and this one copies it from
// that closure's environment (clo_via is the slot there, -1 for a local).
@@ -4961,12 +6200,17 @@ word clocap(int n) { return clocap_at(nclo - 1, n); }
// the slot of the name n in closure c's captures, adding it when the closure has
// not captured it yet. returns the capture index, or -1 when no such name is visible.
word clocap_at(int c, int n) {
- int i = 0, j, base = c * capture_width, r;
+ int i = 0, j, base = c * capture_width, r, root;
while (i < clo_n[c]) { if (clo_name[base + i] == n) return i; i++; }
j = clo_hi[c];
while (j > clo_lo[c]) {
j--;
if (lname[j] != n) continue;
+ effect_note(effect_origin(C_VAR + j), EF_RETAIN);
+ root = lroot(j); local_captured[j] = 1; local_captured[root] = 1;
+ if (lafact[j] && (lafact[j][2] == 1 || lafact[j][2] == 4 || lafact[j][2] == 5)) serrn(n, "a stack or temporary borrow cannot escape through a closure");
+ if (root != j && lname[root]) { clocap_at(c, lname[root]); base = c * capture_width; }
+ if (ltag[j] & 16) serrn(n, "an o3 variable cannot be captured by reference; use an ordinary local");
if (larr[j]) serrn(n, "an array can't be captured; capture a pointer to it");
reserve_captures(clo_n[c] + 1); base = c * capture_width;
clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = j; clo_via[base + clo_n[c]] = 0 - 1; clo_n[c]++;
@@ -4979,6 +6223,18 @@ word clocap_at(int c, int n) {
clo_name[base + clo_n[c]] = n; clo_loc[base + clo_n[c]] = clo_loc[(c - 1) * capture_width + r]; clo_via[base + clo_n[c]] = r; clo_n[c]++;
return clo_n[c] - 1;
}
+// closure environments retain precise capture-cell types for runtime reflection.
+word closure_layout(int closure_index, int count) {
+ word t = tnew(TY_STRUCT, 3 + count, 0, 0), fields = talloc((3 + count) * 3); int i = 0, local;
+ t[3] = 3 + count; t[4] = fields;
+ while (i < 3 + count) {
+ fields[i * 3 + 2] = i;
+ if (i < 3) fields[i * 3 + 1] = tword;
+ else { local = clo_loc[closure_index * capture_width + i - 3]; fields[i * 3 + 1] = tptr(ltype[local]); }
+ i++;
+ }
+ return t;
+}
// fn(params) { body } as an expression. the body is compiled in place behind
// a jump (hop) as a nested function with a hidden last parameter, env. when
// the code that creates the closure runs, it allocates the block (code, then
@@ -4986,18 +6242,26 @@ word clocap_at(int c, int n) {
// variable in. ctx_push / ctx_pop save and restore the enclosing function's
// compiler state around the body.
int closure() {
- int np = 0; [20]word pn; [20]word pty; int hop, entry, i, t, envl, ncap, c, result;
+ int np = 0; char*[20] pn; word[20] pty; int hop, entry, i, t, envl, ncap, c, result, declared = 0, tags = 0, tag; word[20] ptags;
+ word expected = closure_expected; closure_expected = 0;
reserve_closures(nclo + 1);
if (o3on) serr("closures can't be made inside an o3 region");
next();
+ // fn:type(args) names the result; returns are coerced and checked against it
+ if (accept(s_colon)) declared = ptype0();
expect(s_lp);
if (!is_op(s_rp)) {
while (1) {
pty[np] = tword;
+ if (expected && expected[0] == TY_FN && np < expected[3]) pty[np] = expected[7 + np];
if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) {
- pty[np] = ptype();
+ pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
}
- pnoffs[np] = toff; rs_note(RS_PARAM, toff); pn[np] = name();
+ tag = 0;
+ if (accept(s_dot)) { if (is_id(s_m)) tag = 1; else if (is_id(s_d)) tag = 2; else die("a parameter tag is .m or .d"); next(); }
+ ptags[np] = tag; if (np < 12) tags = tags | (tag << (np + np)); else if (tag) die("only the first 12 parameters can be tagged");
+ if (expected && expected[0] == TY_FN && !tag && (isptr(pty[np]) || pty[np][0] == TY_DYN || pty[np][0] == TY_FN)) ptags[np] = 1;
+ pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name();
np++;
if (np >= 20) { die("too many parameters"); break; }
if (!accept(s_comma)) break;
@@ -5010,11 +6274,14 @@ int closure() {
hop = hole(); op(O_JMP);
entry = here;
fn_begin(1, 0);
- prologue();
+ if (declared) fty = declared;
+ prologue(); borrow_prologue();
i = 0;
while (i < np) {
- t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; fslots++; lsmark(1, pn[i]);
+ t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]);
ltype[t] = pty[i];
+ if (fchk && ptags[i] == 1) lstate[t] = S_BORROW;
+ if (fchk && ptags[i] == 2) { lstate[t] = S_M; lown[t] = 1; }
if (isfl(pty[i])) setflt(t, 1);
i++;
}
@@ -5025,21 +6292,28 @@ int closure() {
body();
result = clo_ret[nclo - 1];
if (result <= 0 || live) result = tword;
+ if (declared) result = declared;
epilogue();
nclo--;
c = nclo;
ncap = clo_n[c];
setlit(hop, here);
ctx_pop();
+ if (probe_off >= 0 && probe_has == 1 && tpend <= probe_off && !ginst) probe_snap(); // past the closure: what the function around it has
// the block: [push env][push body][jump] and the captured values
// the block: word 0 is the body's entry, words 3 on are the captured values.
// a call through the closure pushes block + 3 as the env and jumps to word 0.
- lit(3 + ncap); callname(s_new);
+ lit(3 + ncap); lit(closure_layout(c, ncap)); callname(s_new_t);
lit(entry); lit(1); op(O_PICK); op(O_ST);
i = 0;
while (i < ncap) {
t = clo_loc[c * capture_width + i];
- if (clo_via[c * capture_width + i] < 0) { touch(t); vaddr(t); op(O_LD); }
+ if (clo_via[c * capture_width + i] < 0) {
+ touch(t); effect_note_mask(effect_mask(C_VAR + t), EF_RETAIN); ev_put(t, EV_CAPTURE, -1);
+ if (cmode && !dry) { lvescaped[t] = 1; lvkind[t] = 0; }
+ if (!isalias(t)) { vaddr(t); op(O_LD); callname(intern("share")); op(O_DROP); }
+ vaddr(t);
+ }
else { vaddr(clo_env[c - 1]); op(O_LD); lit(clo_via[c * capture_width + i]); op(O_ADD); op(O_LD); }
lit(1); op(O_PICK); lit(3 + i); op(O_ADD); op(O_ST);
i++;
@@ -5053,10 +6327,211 @@ int closure() {
xstr = 0;
xlenloc = -1;
xstamp = 0;
- xty = tfn(result, np, pty, np + np);
+ xty = tfn(result, np, pty, np + np + tags * 64);
+ return C_FRESH;
+}
+
+//: ---- array initializer lists and compound literals ----
+//: keep original token places while measuring and replaying an initializer. measurement never evaluates
+//: value expressions: values run once, in source order, after zero filling the storage.
+word array_tokens() {
+ word q = chk_resize(0, 0, 97); int cap = 32, n = 0, depth = 0;
+ while (tk != T_EOF) {
+ if (n == cap) { cap = cap * 2; q = chk_resize(q, n * 3 + 1, cap * 3 + 1); }
+ q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++;
+ if (is_op(s_lb)) depth++;
+ if (is_op(s_rb)) depth--;
+ next(); if (!depth) break;
+ }
+ if (depth) die("unterminated array initializer");
+ q[0] = n; return q;
+}
+word array_string_tokens(word t) {
+ int count, i = 0, place = tpack(); char* text = tv; word q;
+ count = rt.len_w(text);
+ if (t[3] >= 0 && t[3] == count) count--; // the array has no room for the optional terminating zero
+ q = chk_resize(0, 0, (count + 3) * 6 + 1); q[0] = 1;
+ q[1] = T_OP; q[2] = s_lb; q[3] = place;
+ while (i <= count) {
+ if (i) { q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_comma; q[3 + q[0] * 3] = place; q[0]++; }
+ q[1 + q[0] * 3] = T_NUM; q[2 + q[0] * 3] = text[i];
+ q[3 + q[0] * 3] = place; q[0]++; i++;
+ }
+ q[1 + q[0] * 3] = T_OP; q[2 + q[0] * 3] = s_rb; q[3 + q[0] * 3] = place; q[0]++;
+ next(); return q;
+}
+word array_initializer_tokens(word t) {
+ if (tk == T_STR || tk == T_BSTR) return array_string_tokens(t);
+ return array_tokens();
+}
+int array_skip_value() {
+ int depth = 0;
+ while (tk != T_EOF) {
+ if (!depth && (is_op(s_comma) || is_op(s_rb))) break;
+ if (is_op(s_lp) || is_op(s_lk) || is_op(s_lb)) depth++;
+ if (is_op(s_rp) || is_op(s_rk) || is_op(s_rb)) depth--;
+ next();
+ }
+ return 0;
+}
+int ar_flat; word ar_target;
+int array_scalar_count(word t) {
+ if (t[0] != TY_ARR) return 1;
+ if (t[3] < 0) return 0;
+ return t[3] * array_scalar_count(t[4]);
+}
+// a chain [row][column] selects a subarray or scalar; flat positions resume after that subobject.
+int array_designator(word t) {
+ int index, stride; word part = t;
+ ar_flat = 0;
+ while (accept(s_lk)) {
+ index = cexpr(); expect(s_rk);
+ if (index < 0) { serr("a negative array designator"); index = 0; }
+ if (part[0] != TY_ARR) { serr("too many indices in array designator"); break; }
+ if (part[3] >= 0 && index >= part[3]) serr("array designator index is out of range");
+ stride = array_scalar_count(part[4]); if (!stride) { serr("array designator needs a complete element type"); stride = 1; }
+ ar_flat = ar_flat + index * stride; part = part[4];
+ }
+ expect(s_eq); ar_target = part; return 0;
+}
+word array_shape(word t) {
+ int flat = 0, count = 0, width, stride, end; word elem = t[4], row, target;
+ expect(s_lb);
+ while (tk != T_EOF && !is_op(s_rb)) {
+ stride = array_scalar_count(elem); if (!stride) stride = 1;
+ target = elem;
+ if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; }
+ if (is_op(s_lb)) {
+ if (target[0] != TY_ARR) { serr("nested initializer needs an array element type"); array_skip_value(); flat++; }
+ else {
+ row = array_shape(target);
+ if (elem[0] == TY_ARR && elem[3] < 0) { elem = row; stride = array_scalar_count(elem); }
+ width = array_scalar_count(row); flat = flat + width;
+ }
+ } else { array_skip_value(); flat++; }
+ end = (flat + stride - 1) / stride; if (end > count) count = end;
+ if (t[3] >= 0 && count > t[3]) serr("too many elements in array initializer");
+ if (!accept(s_comma)) break;
+ }
+ expect(s_rb);
+ if (t[3] >= 0) count = t[3];
+ return tarr(elem, count);
+}
+word array_measure(word q, word t) {
+ word result;
+ src_push(1, q, 0, 0); next(); result = array_shape(t); src_pop();
+ if (treadonly(t)) result = tconst(result);
+ return result;
+}
+int array_address(int base, int local, int offset) {
+ if (local) laddr(base + offset); else lit(base + offset);
+ return 0;
+}
+// flatten a scalar position while preserving each packed row's independent word alignment.
+int array_store_scalar(word t, int flat, int base, int local, int offset) {
+ int c, at, stride, bytes, value, address, shift, mask; word elem = t;
+ while (elem[0] == TY_ARR) {
+ stride = array_scalar_count(elem[4]); if (!stride) stride = 1;
+ at = flat / stride; flat = flat % stride;
+ if (elem[4][0] == TY_ARR) offset = offset + at * tsz(elem[4]);
+ else { flat = at; elem = elem[4]; break; }
+ elem = elem[4];
+ }
+ if (isagg(elem)) { serr("aggregate array elements need nested initializer lists"); array_skip_value(); return 0; }
+ if (local) {
+ c = expr(); rval(); plainuse(c); fcoerce(elem);
+ if (!compat(elem, xty)) tcerr("array initializer", elem, xty);
+ if (tbytes(elem)) { array_address(base, 1, offset); lit(8); op(15); lit(flat * tbytes(elem)); op(O_ADD); pkst(elem); }
+ else { array_address(base, 1, offset + flat * tsz(elem)); op(O_ST); }
+ } else {
+ if (isfl(elem) && tk == T_FLT) { value = tv; next(); if (elem == tf32) value = rt.zf2s(value); }
+ else { value = cnorm(elem, cexpr()); if (isfl(elem)) { value = vf_i2f(value); if (elem == tf32) value = rt.zf2s(value); } }
+ bytes = tbytes(elem);
+ if (bytes) {
+ address = base + offset + (flat * bytes >> 3) + hdelta; shift = ((flat * bytes) & 7) * 8;
+ mask = ((1 << (bytes * 8)) - 1) << shift; *address = (*address & ~mask) | ((value << shift) & mask);
+ } else *(base + offset + flat * tsz(elem) + hdelta) = value;
+ }
+ return 0;
+}
+// globals use constant expressions, locals use ordinary expressions. brace elision walks scalar elements.
+int array_suboffset(word t, int flat, word target) {
+ int offset = 0, stride, at;
+ while (t != target && t[0] == TY_ARR) {
+ stride = array_scalar_count(t[4]); if (!stride) stride = 1;
+ at = flat / stride; flat = flat % stride; offset = offset + at * tsz(t[4]); t = t[4];
+ }
+ return offset;
+}
+int array_values(word t, int base, int local, int offset) {
+ int flat = 0, total = array_scalar_count(t), stride = array_scalar_count(t[4]), dest, width;
+ word target;
+ expect(s_lb);
+ while (tk != T_EOF && !is_op(s_rb)) {
+ target = t[4];
+ if (is_op(s_lk)) { array_designator(t); flat = ar_flat; target = ar_target; }
+ if (flat < 0 || flat >= total) { serr("array initializer index is out of range"); array_skip_value(); }
+ else if (target[0] == TY_ARR && is_op(s_lb)) {
+ // the selected subarray starts at its scalar position, converted to an in-place word offset.
+ dest = array_suboffset(t, flat, target);
+ array_values(target, base, local, offset + dest); flat = flat + array_scalar_count(target);
+ } else { array_store_scalar(t, flat, base, local, offset); flat++; }
+ if (!accept(s_comma)) break;
+ }
+ expect(s_rb); return 0;
+}
+int array_initialize(word q, word t, int base, int local) {
+ if (local && tsz(t)) { array_address(base, 1, 0); lit(0); lit(tsz(t)); callname(intern("fill_w")); op(O_DROP); }
+ src_push(1, q, 0, 0); next(); array_values(t, base, local, 0); src_pop();
+ return 0;
+}
+int array_literal_tokens(word q, word t) {
+ if (!infn) {
+ int base, cells; t = array_measure(q, t); cells = tsz(t); reserve_code(cells); base = here;
+ while (cells) { emit(0); cells--; } array_initialize(q, t, base, 0); rt.free(q);
+ if_value = 0;
+ xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = 0; xconst = 1; xcval = base;
+ return C_PLAIN;
+ }
+ int i = newloc(intern("<array literal>")), place = tpack();
+ t = array_measure(q, t); ltype[i] = t; larr[i] = 1; fslots = fslots + tsz(t);
+ array_initialize(q, t, lslot[i], 1);
+ lafact[i] = af_new(t[3], 4, place); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i;
+ laddr(lslot[i]); xty = t; lv = 0; lvk = LV_NONE; xbor = i; xaf = lafact[i];
+ xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; rt.free(q);
+ return C_PLAIN;
+}
+// a typed growable initializer owns a heap handle, filled from temporary in-place values.
+int array_dynamic_literal(word t) {
+ word q = array_tokens(), shape = array_measure(q, tarr(t[4], -1));
+ int slot = fslots, cells = tsz(shape), place = tpack(); fslots = fslots + cells;
+ if (!infn) serr("a growable array initializer requires function scope");
+ array_initialize(q, shape, slot, 1); rt.free(q);
+ if (tbytes(t[4])) lit(0 - tbytes(t[4])); else lit(tsz(t[4]));
+ lit(shape[3]); lit(t); callname(s_dnew); vf_effect();
+ lit(0); op(O_PICK); lit(2); op(O_ADD); op(O_LD); laddr(slot); lit(cells); callname(intern("copy_w")); op(O_DROP);
+ xty = t; lv = 0; lvk = LV_NONE; xbor = -1; xaf = af_new(shape[3], 3, place);
+ xzero = 0; xfloat = 0; xstr = 0; xconst = 0; xvk = 0; xstamp = 1; newsz = -1; newleaf = 0;
return C_FRESH;
}
-// ---- the expression grammar ----
+int array_literal(word t) { word q = array_tokens(); return array_literal_tokens(q, t); }
+// statement-bearing braces keep block-expression semantics; expression-only braces are array literals.
+int brace_primary() {
+ word q = array_tokens(); int k = 0, depth = 0, block = 0, c;
+ while (k < q[0]) {
+ if (q[1 + k * 3] == T_OP) {
+ if (q[2 + k * 3] == s_lb) depth++;
+ if (q[2 + k * 3] == s_rb) depth--;
+ if (q[2 + k * 3] == s_semi && depth == 1) block = 1;
+ }
+ k++;
+ }
+ if (!block) return array_literal_tokens(q, tarr(tint, -1));
+ src_push(1, q, 0, 0); next(); c = blockexpr(); src_pop(); rt.free(q); return c;
+}
+
+//: ---- the expression grammar ----
+
// precedence, loosest to tightest:
// expr (assignment, ?:) -> bin (binary operators by precedence) ->
// unary (prefix operators, casts, recasts) -> postfix (index, field, call,
@@ -5074,25 +6549,27 @@ int closure() {
// the analyzer tracks returns C_VAR + its index so the caller can tell which
// variable the value is.
int primary() {
- int n, i, s, c, k; [8]word bs0; int ft, mn, ms;
+ effect_value = 0; xprojection = 0; if_value = 0;
+ int n, i, s, c, k; word[8] bs0; int ft, mn, ms;
lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1;
- xconst = 0; xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0;
- if (tk == T_FLT) { op(OP_FLIT); emit(tv); xfloat = 1; xty = tfloat; next(); return C_PLAIN; }
+ xconst = 0; xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0;
+ if (tk == T_FLT) { op(OP_FLIT); emit(tv); xvk = 2; xvv = tv; xfloat = 1; xty = tfloat; next(); return C_PLAIN; }
if (tk == T_NUM) {
if (cmode && tv >= 100 && tglue != 3 && !nomagic && (tv & (tv - 1)) && (tv & (tv + 1))) {
magic(tv);
}
- lit(tv); xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); return C_PLAIN;
+ lit(tv); xvk = 1; xvv = tv; xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); return C_PLAIN;
}
- if (tk == T_BSTR) { bstrlit(tv); xstr = 1; xty = tword; next(); return C_PLAIN; }
- if (tk == T_STR) { strlit(tv); next(); xty = tptr(tchar); return C_PLAIN; }
+ if (tk == T_BSTR) { xvk = 4; xvv = vf_string(tv); bstrlit(tv); xstr = 1; xty = tbstr; next(); return C_PLAIN; }
+ if (tk == T_STR) { xvk = 3; xvv = vf_string(tv); strlit(tv); next(); xty = tptr(tchar); return C_PLAIN; }
if (tk == T_OP) {
+ if (rt.eq_w(tv, "$")) { next(); return quote_tokens(); }
if (tv == s_lp) { next(); c = expr(); expect(s_rp); return c; }
- if (tv == s_lb) return blockexpr();
+ if (tv == s_lb) return brace_primary();
diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN;
}
if (tk != T_ID) { diefound("expected an expression"); lit(0); xty = tword; return C_PLAIN; }
- if (tv == s_fn) { peek(); if (pk_tk == T_OP && pk_tv == s_lp) return closure(); }
+ if (tv == s_fn) { peek(); if (pk_tk == T_OP && (pk_tv == s_lp || pk_tv == s_colon)) return closure(); }
n = tv; callpos = tpack();
i = findloc(n);
if (i < 0 && nclo) {
@@ -5101,7 +6578,7 @@ int primary() {
rs_note(RS_LOCAL, 0 - 1);
next();
i = nclo - 1;
- vaddr(clo_env[i]); op(O_LD); lit(k); op(O_ADD);
+ vaddr(clo_env[i]); op(O_LD); lit(k); op(O_ADD); op(O_LD);
i = clo_loc[i * capture_width + k];
xty = ltype[i]; xfloat = getflt(i); if (isfl(xty)) xfloat = 1; xstr = getstr(i);
lv = 1; lvk = LV_OTHER;
@@ -5109,9 +6586,12 @@ int primary() {
}
}
if (i >= 0) {
- luse(i);
- rs_note(lrskind[i], lrsoff[i]);
+ if (if_local) if_value = if_local[i];
+ lreadpos[i] = tpack();
+ luse(i); effect_note_mask(effect_mask(C_VAR + i), EF_READ);
+ rs_note(lrskind[i], lrsoff[i]); os_at(toff, i);
xaf = lafact[i]; af_record(toff, xaf);
+ if (cmode) { xvk = lvkind[i]; xvv = lvvalue[i]; vf_at(rsn - 1, xvk, xvv); }
next();
if (!fchk && tracked(i)) {
serrn(n, "is tracked by the analyzer and can't be used in unchecked code");
@@ -5121,13 +6601,14 @@ int primary() {
if (larr[i]) return C_PLAIN;
xfloat = getflt(i);
if (isfl(ltype[i])) xfloat = 1;
- xstr = getstr(i);
+ xstr = getstr(i); if (xty == tbstr) xstr = 1;
lv = 1; lvk = LV_LOCAL; lvi = i;
- if (isalias(i)) { touch(i); xbor = lroot(i); return C_PLAIN; }
- if (tracked(i)) return C_VAR + i;
+ if (isalias(i)) { touch(i); xbor = lroot(i); return C_VAR + i; }
+ if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i;
return C_PLAIN;
}
- s = lookup(n);
+ s = source_lookup(n);
+ if (s < 0 && rt.eq_w(n, "new")) s = source_lookup(s_new);
if (s >= 0) rs_put(toff, RS_SYM, rs_sym(s), s);
if (s >= 0 && skind[s] == K_KW) {
if (!(sflag[s] & F_EXPR)) dien(n, "can't be used inside an expression");
@@ -5136,10 +6617,14 @@ int primary() {
if (s >= 0 && skind[s] == K_TYPE) {
next();
if (is_op(s_dot)) { // Struct.name(args): a static function of the struct
+ c = toff + toklen;
next();
- mn = name();
+ if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= c && ((tk == T_ID && probe_off <= toff + toklen) || (tk != T_ID && probe_off <= toff))) { probe_static = n; probe_has = 3; chkabort = 1; }
+ k = toff; mn = name();
ms = lookup(methname(n, mn));
+ if (ms >= 0) rs_put(k, RS_SYM, rs_sym(ms), ms);
if (ms >= 0 && skind[ms] == K_CONST) { // Enum.MEMBER
+ xvk = 1; xvv = sval[ms];
lit(sval[ms]);
xty = tint;
if (stype[ms]) xty = stype[ms];
@@ -5165,6 +6650,7 @@ int primary() {
next();
xty = tword;
if (s >= 0 && skind[s] == K_CONST) {
+ xvk = 1; xvv = sval[s];
lit(sval[s]);
xty = tint;
if (stype[s]) xty = stype[s];
@@ -5174,11 +6660,12 @@ int primary() {
return C_PLAIN;
}
if (s >= 0 && skind[s] == K_GVAR) {
+ if (cmode) { vf_symbols(); xvk = svkind[s]; xvv = svvalue[s]; vf_at(rsn - 1, xvk, xvv); }
lit(sval[s]);
if (stype[s]) xty = stype[s];
- if (isfl(xty)) xfloat = 1;
+ if (isfl(xty)) xfloat = 1; if (xty == tbstr) xstr = 1;
lv = 1;
- lvk = LV_GLOBAL;
+ lvk = LV_GLOBAL; lvi = s;
return C_PLAIN;
}
if (s >= 0 && skind[s] == K_GARR) { lit(sval[s]); if (stype[s]) xty = stype[s]; return C_PLAIN; }
@@ -5194,7 +6681,7 @@ int primary() {
}
closeangle();
if (!is_op(s_lp)) { // f<int> alone is that instance, as a function value
- fill_w(bs0, 0, 8); copy_w(bs0, gbind, ngbind); ngbind = 0;
+ rt.fill_w(bs0, 0, 8); rt.copy_w(bs0, gbind, ngbind); ngbind = 0;
ft = stype[s];
s = ginstance(s, bs0);
fnref(s);
@@ -5212,7 +6699,9 @@ int primary() {
// is the result; dlt is +1 or -1. it also updates the loop-bound facts: a
// decrement or a store of an unknown value ends any proof about the variable.
int incdec(int post, int dlt) {
+ require_mutable(xty);
int t;
+ if (cmode && !dry) { if (lvk == LV_LOCAL) lvkind[lvi] = 0; else vf_effect(); } xvk = 0;
if (lv && lvk == LV_LOCAL) lafact[lvi] = 0; xaf = 0;
if (!lv) die("++ and -- need something to assign to");
if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
@@ -5252,21 +6741,53 @@ int incdec(int post, int dlt) {
// name is followed by ( it is a method call instead. a pointer-valued field
// of a tracked variable is a borrow of that variable (xbor).
word field(int c) {
+ int readonly = treadonly(xty);
+ if_value = 0;
int n, fieldoff; word st;
- af_access(xaf, tpack()); xaf = 0; int i, b = -1, k = 0, scanned = 0, ms;
+ word borrowed = xaf, base_mask = effect_mask(c); af_use(xaf, tpack(), c); xaf = 0; xvk = 0; int i, b = xbor, k = 0, scanned = 0, ms, de, pmatch = 0;
+ de = toff + toklen;
next();
+ // is the place asked about on this member name (or just after the dot, with none typed)
+ if (probe_off >= 0 && cmode && !dry && !ginst && incdepth == 0 && probe_off >= de) {
+ if (tk == T_ID) { if (probe_off <= toff + toklen) pmatch = 1; }
+ else if (probe_off <= toff) pmatch = 1;
+ }
n = name(); fieldoff = nameoff;
+ st = xty;
+ if (st[0] == TY_ARR || st[0] == TY_DYN) {
+ if (pmatch) probe_member(st);
+ if (is_op(s_lp) && array_method(n)) { xaf = borrowed; return array_member(c, n); }
+ if (is_op(s_lp)) {
+ ms = receiver_method(st, n);
+ if (ms == -2) serrn(n, "matches more than one receiver method");
+ if (ms >= 0) {
+ rval(); plainuse(c);
+ if (c >= C_VAR) b = lroot(c - C_VAR);
+ if (st[0] == TY_ARR && stype[ms][7][0] == TY_DYN) {
+ int slot = fslots; fslots = fslots + 4;
+ laddr(slot + 2); op(O_ST);
+ lit(st[3]); laddr(slot); op(O_ST); lit(st[3]); laddr(slot + 1); op(O_ST);
+ if (tbytes(st[4])) lit(0 - tbytes(st[4])); else lit(tsz(st[4]));
+ laddr(slot + 3); op(O_ST); laddr(slot); st = tdyn(st[4], 1);
+ }
+ return methcall(ms, st, borrowed, b, base_mask);
+ }
+ }
+ }
rval();
if (c == C_FRESH) cerr("use a field of a new object only after binding it to a name");
else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; }
st = xty;
if (st[0] == TY_PTR) st = st[4];
+ if (pmatch) probe_member(st);
if (is_op(s_lp)) { // e.name(...): a method, unless the struct has a field of that name
ms = 0 - 1;
if (st[0] == TY_STRUCT) { if (tfindfield(st, n) < 0) ms = methof(st, n); }
else if (st == tword || isnum(st)) { if (fieldscan(n) == 0) ms = methscan(n); }
if (ms == 0 - 2) { serrn(n, "is a method of more than one struct"); ms = 0 - 1; }
- if (ms >= 0) return methcall(ms, st);
+ if (ms >= 0) {
+ return methcall(ms, st, borrowed, b, base_mask);
+ }
}
if (st[0] != TY_STRUCT && (st == tword || isnum(st))) {
k = fieldscan(n);
@@ -5274,9 +6795,9 @@ word field(int c) {
else if (k > 1) {
if (!panic && !dry) {
errat();
- put_c('.');
- put_s(n);
- put_s(" is a field of more than one struct");
+ rt.put_c('.');
+ rt.put_s(n);
+ rt.put_s(" is a field of more than one struct");
errend();
}
xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN;
@@ -5285,31 +6806,35 @@ word field(int c) {
if (st[0] != TY_STRUCT) {
if (!panic && !dry) {
errat();
- put_c('.');
- put_s(n);
- put_s(": that's a ");
+ rt.put_c('.');
+ rt.put_s(n);
+ rt.put_s(": that's a ");
tprint(xty);
- put_s(", not a struct or a pointer to one");
+ rt.put_s(", not a struct or a pointer to one");
errend();
}
xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN;
}
if (scanned) i = fldi; else i = tfindfield(st, n);
if (i < 0) {
- if (!panic && !dry) { errat(); tprint(st); put_s(" has no field "); put_s(n); errend(); }
+ if (!panic && !dry) { errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); errend(); }
xty = tword; lv = 1; lvk = LV_OTHER; xbor = -1; return C_PLAIN;
}
rs_put(fieldoff, RS_FIELD, field_decl(st, n), st[4][i * 3 + 1]);
k = st[4][i * 3 + 2];
xty = st[4][i * 3 + 1];
+ if (readonly) xty = tconst(xty);
if (k < 0) { // a packed field: the lvalue is a byte address, of the packed twin
lit(8); op(15);
k = 0 - k - 1;
if (k) { lit(k); op(O_ADD); }
xty = tpk(xty);
} else if (k) { lit(k); op(O_ADD); }
- if (xty[0] == TY_PTR) { if (b >= 0) xbor = lroot(b); }
+ if (isref(xty)) { if (b >= 0) xbor = lroot(b); }
else xbor = -1;
+ if (borrowed && borrowed[2] == 5) xaf = borrowed;
+ effect_value = base_mask;
+ xprojection = af_project(borrowed, k, tsz(xty), tpack(), 0);
lv = 1; xzero = 0;
if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER;
return C_PLAIN;
@@ -5371,19 +6896,23 @@ int range_jz(int ix, int src, int fld) {
// compared with the length). a plain pointer or fixed array is just base +
// index * size, and a constant index against a known length is checked at
// compile time.
-int postfix() {
- int c = primary(), b, n, t, et; word ft; [8]word bs; int k, ci, was, al, ix, enh, cz, cj, place; word storage;
+int postfix() { return postfix_tail(primary()); }
+int postfix_tail(int c) {
+ int b, n, t, et; word ft; word[8] bs; int k, ci, was, al, ix, enh, cz, cj, place, offset, temporary, call_owner, readonly; word storage, storage_mask, call_origins;
while (1) {
if (tk == T_ID && tv == s_as) { // e as T: same bits, another type
- next(); t = ptype(); rval(); xaf = 0;
+ next(); t = ptype(); rval(); if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 0);
if (tbytes(t) && !xfloat && !samesz(t, xty)) { tnorm(t); xconst = 0; } // x as u8 cuts the value to size
+ if_value = 0;
xty = t; xfloat = 0; lvk = LV_NONE;
if (isfl(t)) xfloat = 1;
continue;
}
if (tk != T_OP) return c;
if (tv == s_lk) {
- storage = xaf; place = tpack(); af_access(storage, place);
+ readonly = treadonly(xty);
+ xvk = 0;
+ storage = xaf; storage_mask = effect_mask(c); offset = -1; n = -1; place = tpack(); af_use(storage, place, c);
was = xstr;
ci = 0;
if (lv && lvk == LV_LOCAL) lptr[lvi] = 1;
@@ -5395,13 +6924,13 @@ int postfix() {
if (accept(s_colon)) {
c = expr(); rval(); plainuse(c);
expect(s_rk);
- callname(s_slice_s);
- lv = 0; lvk = LV_NONE; xty = tword; xfloat = 0; xstr = 1; xzero = 0; xbor = -1;
+ callname(s_slice_s); xvk = 0;
+ lv = 0; lvk = LV_NONE; xty = tbstr; xfloat = 0; xstr = 1; xzero = 0; xbor = -1;
c = C_PLAIN;
continue;
}
expect(s_rk);
- callname(s_at_s);
+ callname(s_at_s); xvk = 0;
lv = 0; lvk = LV_NONE; xty = tint; xfloat = 0; xstr = 0; xzero = 0; xbor = -1;
c = C_PLAIN;
continue;
@@ -5412,7 +6941,7 @@ int postfix() {
if (xty[0] == TY_DYN && tbytes(xty[4])) { // packed elements: the runtime checks and gives the byte address
et = xty[4];
c = expr(); rval(); plainuse(c);
- af_index(storage, place);
+ if (xconst) offset = xcval; af_index(storage, place);
tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty);
expect(s_rk);
callname(intern("datb"));
@@ -5426,7 +6955,7 @@ int postfix() {
ix = -1;
if (lv && lvk == LV_LOCAL) ix = lvi;
rval(); plainuse(c);
- af_index(storage, place);
+ if (xconst) offset = xcval; af_index(storage, place);
tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty);
expect(s_rk);
k = lookup(s_outside);
@@ -5472,7 +7001,7 @@ int postfix() {
n = 0 - 1; if (xty[0] == TY_ARR && xty[3] > 0) n = xty[3];
xconst = 0;
c = expr(); rval(); plainuse(c);
- if (n < 0) af_index(storage, place);
+ if (xconst) offset = xcval; if (n < 0) af_index(storage, place);
if (n >= 0 && xconst && (xcval < 0 || xcval >= n)) {
serr("the index is out of range for this array");
}
@@ -5487,9 +7016,14 @@ int postfix() {
op(O_ADD);
}
}
- xaf = 0;
- lv = 1; xty = et; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0;
- if (xty[0] == TY_PTR) { if (b >= 0) xbor = lroot(b); }
+ xprojection = af_project(storage, offset, 1, place, 1);
+ if (xprojection && n >= 0 && offset >= 0) { xprojection[0] = n - offset; xprojection[13] = 0 - offset; xprojection[14] = 1; }
+ xaf = 0; if (storage && storage[2] == 5) xaf = storage;
+ effect_value = 0; if (isptr(et) || et == tword) effect_value = storage_mask;
+ if_value = 0;
+ if (readonly) et = tconst(et);
+ lv = 1; xty = et; xzero = 0; xconst = 0; xvk = 0; xlenloc = -1; xstamp = 0;
+ if (isref(xty)) { if (b >= 0) xbor = lroot(b); }
else xbor = -1;
if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER;
c = C_PLAIN;
@@ -5501,16 +7035,19 @@ int postfix() {
c = field(c);
xfloat = 0;
if (isfl(xty)) xfloat = 1;
- xstr = 0;
+ xstr = xty == tbstr;
continue;
}
if (tv == s_lp) { // call through a value
+ effect_note_mask(effect_mask(c), EF_READ);
rval(); plainuse(c);
ft = 0;
if (xty[0] == TY_FN) ft = xty;
else tcneed(xty == tword, "only functions can be called", xty);
- fill_w(bs, 0, 8);
- n = args(0, ft, bs, o3on);
+ rt.fill_w(bs, 0, 8);
+ ifcall = -1; effect_argument_base = 0;
+ int call_tags = 0; if (ft) call_tags = ft[5] >> 6;
+ n = args(call_tags, ft, bs, o3on); temporary = argtemporary; call_owner = argowner; call_origins = argorigin_mask;
// raw code can live in a heap segment; a closure has an environment block
lit(n); op(O_PICK); callname(intern("callable_code"));
cz = hole(); op(O_JZ);
@@ -5522,9 +7059,12 @@ int postfix() {
op(O_CALL);
setlit(cj, here);
op(O_SWAP); op(O_DROP);
+ if_value = 0;
lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xbor = -1; xconst = 0;
xty = tword;
if (ft) xty = subst(ft[4], bs);
+ if (ft && (ft[5] & 1)) c = C_FRESH;
+ else if (isptr(xty) || xty == tword) { effect_value = call_origins; if (isptr(xty)) xbor = call_owner; if (temporary) xaf = af_new(-1, 5, place); }
xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0;
continue;
}
@@ -5538,7 +7078,7 @@ int postfix() {
// tag), casts like (int)x and (node*)x, dereference, address-of, prefix ++ and
// --, and any unary operator a program added (- ! ~ built in).
int unary() {
- int c, un, t, place, constant, value; word storage;
+ int c, un, t, place, constant, value; word storage, dependency;
if (tk == T_ID && (tv == s_u || tv == s_m || tv == s_d)) { // u*x, d*x, m*x: recast only when * is glued
peek();
if (pk_tk == T_OP && pk_tv == s_star && pq_gl[0]) {
@@ -5553,22 +7093,26 @@ int unary() {
next();
t = ptype();
expect(s_rp);
- c = unary(); rval(); plainuse(c); xaf = 0;
+ if (t[0] == TY_ARR && is_op(s_lb)) return postfix_tail(array_literal(t));
+ if (t[0] == TY_DYN && is_op(s_lb)) return postfix_tail(array_dynamic_literal(t));
+ c = unary(); dependency = effect_mask(c); rval(); plainuse(c); effect_value = dependency; if (!xaf || xaf[2] != 5) xaf = 0; vf_coerce(t, 1);
if (isfl(t)) { if (!xfloat) { op(OP_I2F); xfloat = 1; } if (t == tf32) { xenh("zf2s"); xenh("zs2f"); } }
else if (xfloat && isnum(t)) { op(OP_F2I); xfloat = 0; }
else xfloat = 0;
if (tbytes(t) && !samesz(t, xty)) { tnorm(t); xconst = 0; } // (u8)x cuts the value to size
+ if_value = 0;
xty = t; lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1;
return C_PLAIN;
}
if (tk == T_OP) {
if (tv == s_star) {
- place = tpack(); next(); c = unary();
- af_access(xaf, place); xaf = 0;
+ place = tpack(); next(); c = unary(); dependency = effect_mask(c);
+ storage = xaf; af_use(xaf, place, c); xaf = 0; if (storage && storage[2] == 5) xaf = storage; xvk = 0;
if (lv && lvk == LV_LOCAL) lptr[lvi] = 1;
rval(); plainuse(c);
tcneed(xty[0] == TY_PTR || xty == tword, "only pointers can be dereferenced", xty);
- xty = telem(xty);
+ if_value = 0;
+ xty = telem(xty); effect_value = 0; if (isptr(xty) || xty == tword) effect_value = dependency;
if (tbytes(xty)) { lit(8); op(15); xty = tpk(xty); } // the first packed element: a byte address
lv = 1;
lvk = LV_OTHER;
@@ -5581,10 +7125,11 @@ int unary() {
}
if (tv == s_amp) {
place = tpack(); next(); c = unary();
- storage = 0;
+ storage = xprojection; if (xaf && xaf[2] == 5) storage = xaf;
if (lv && lvk == LV_LOCAL) {
+ if (cmode && !dry) { lvescaped[lvi] = 1; lvkind[lvi] = 0; }
storage = laaddr[lvi];
- if (!storage) { storage = af_new(1, 1, place); laaddr[lvi] = storage; if (storage) storage[5] = lvi; }
+ if (!storage) { int storage_kind = 1; if (local_cell[lvi]) storage_kind = 2; storage = af_new(1, storage_kind, place); laaddr[lvi] = storage; if (storage) storage[5] = lvi; }
af_forget();
}
else if (isagg(xty)) storage = 0;
@@ -5593,8 +7138,10 @@ int unary() {
if (lv && lvk == LV_LOCAL && (ltag[lvi] & 16)) serr("an o3 variable has no address");
if (lv && lvk == LV_LOCAL) ltag[lvi] = ltag[lvi] | LREAD | LASG;
if (c >= C_VAR) touch(c - C_VAR);
- lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1; xfloat = 0; xstr = 0;
- xty = tptr(xty); xaf = storage;
+ if (lvk == LV_ELEM && lvi >= 0) xbor = lroot(lvi);
+ lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0;
+ if_value = 0;
+ xty = tptr(xty); xaf = storage; xvk = 0;
return C_PLAIN;
}
if (tv == s_inc) { next(); unary(); return incdec(0, 1); }
@@ -5603,6 +7150,9 @@ int unary() {
if (un >= 0) {
next(); c = unary(); rval(); plainuse(c);
constant = xconst && !xfloat && uname[un] == s_minus; value = xcval; xaf = 0;
+ if (uname[un] == s_minus && xvk == 1) xvv = 0 - xvv;
+ else if (uname[un] == s_minus && xvk == 2) xvv = vf_negf(xvv);
+ else xvk = 0;
if (un <= 1) ftest();
if (uname[un] == s_minus && xfloat) {
op(OP_FLIT);
@@ -5616,6 +7166,7 @@ int unary() {
if (un <= 1) xty = tbool;
else if (xty != tword && !isfl(xty)) { tcneed(isnum(xty), "this operator needs a number", xty); xty = tint; }
}
+ if (un <= 1) if_value = 0;
xstr = 0;
xconst = constant; if (constant) xcval = 0 - value; xlenloc = -1; xstamp = 0;
lv = 0; lvk = LV_NONE; xzero = 0; xbor = -1;
@@ -5629,8 +7180,11 @@ int unary() {
// level tighter and emit its code sequence from the operator table. && and ||
// short-circuit with jumps; floats get their own opcodes; a * or / by a
// constant that is a power of two gets a shift hint in check mode.
+int if_comparison(int name) {
+ return name == s_eqeq || name == s_ne || name == s_lt || name == s_gt || name == s_le || name == s_ge;
+}
int bin(int minp) {
- int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp;
+ int c = unary(), b, p, a, j, lt, lb, rb, src, lf, rf, ls, rs, fresh, top = 0, il = -1, opp; word borrowed, rightborrow, left_mask, right_mask, infer_left, infer_right;
if (fortest == 1) { fortest = 2; top = 1; ft_nops = 0; }
while (tk == T_OP) {
b = binfind(tv);
@@ -5639,9 +7193,10 @@ int bin(int minp) {
if (p < minp) return c;
lb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor;
- lf = xfloat; ls = xstr; fresh = 0;
+ borrowed = xaf; left_mask = effect_mask(c); lf = xfloat; ls = xstr; fresh = 0;
il = -1;
if (top && lv && lvk == LV_LOCAL) il = lvi;
+ infer_left = if_value; infer_right = 0;
opp = tpack();
next(); rval(); plainuse(c);
lt = xty;
@@ -5669,7 +7224,7 @@ int bin(int minp) {
setlit(j, here);
xfloat = 0; xstr = 0;
} else {
- c = bin(p + 1);
+ c = bin(p + 1); infer_right = if_value; rightborrow = xaf; right_mask = effect_mask(c);
rb = -1;
if (c >= C_VAR && tracked(c - C_VAR)) rb = lroot(c - C_VAR); else rb = xbor;
if (top) { ft_ir = -1; if (lv && lvk == LV_LOCAL) ft_ir = lvi; }
@@ -5691,12 +7246,19 @@ int bin(int minp) {
} else if ((lf || rf) && isfop(bname[b])) { femit(bname[b], lf, rf); xstr = 0; }
else { emitseq(bseq[b], blen[b]); xfloat = 0; xstr = 0; }
if (bname[b] == s_plus || bname[b] == s_minus) {
+ if ((!borrowed || borrowed[2] != 5) && rightborrow && rightborrow[2] == 5) borrowed = rightborrow;
if (lb >= 0) src = lb; else src = rb;
if (bname[b] == s_minus && lb >= 0 && rb >= 0) src = -1; // pointer difference is a distance
}
}
if (blen[b] < 0) af_forget();
- xty = bintype(b, lt, xty);
+ if (xstr) xty = tbstr; else xty = bintype(b, lt, xty);
+ if_value = 0;
+ if (blen[b] > 0 && !if_comparison(bname[b]) && !xstr && !lf && !rf && (xty == tint || xty == tword) && (infer_left || infer_right)) {
+ if_link(infer_left, infer_right);
+ if_value = infer_left; if (!if_value) if_value = infer_right;
+ if_join(if_value, xty);
+ }
if (src >= 0 && xty[0] != TY_PTR && xty != tword) src = -1;
xbor = src;
if (top) {
@@ -5704,12 +7266,15 @@ int bin(int minp) {
if (ft_nops > 1) ft_iv = -1;
else if (bname[b] == s_lt && blen[b] > 0 && il >= 0 && ft_ir >= 0 && !lf && !rf && !ls && !rs) { ft_iv = il; ft_bd = ft_ir; }
}
- lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0;
+ lv = 0; lvk = LV_NONE; c = C_PLAIN; xzero = 0; xconst = 0; xlenloc = -1; xstamp = 0; xaf = 0; xvk = 0;
+ effect_value = 0; if (isptr(xty) || xty == tword) effect_value = left_mask | right_mask;
if (fresh) c = C_FRESH;
+ if (borrowed && borrowed[2] == 5 && (isptr(xty) || xty == tword) && (bname[b] == s_plus || bname[b] == s_minus)) xaf = borrowed;
}
return c;
}
-// ---- assignment, conditionals and blocks as expressions ----
+//: ---- assignment, conditionals and blocks as expressions ----
+
int storekeep(int t) {
if (mdbg && !dry && !ispk(t)) { lit(1); op(O_PICK); callname(s_check); op(O_DROP); }
op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP);
@@ -5720,20 +7285,20 @@ int storekeep(int t) {
// restored for the second side) and then joined, so a variable can't be given
// away on one side only. both sides are fresh objects, or neither is.
int ternary(int c) {
- int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb;
+ int a, b, s0, sa, livea, live0 = live, n0 = nloc, ca, cb, ta, ba, bb; word temp_a, temp_b, mask_a, mask_b;
rval(); plainuse(c);
ftest();
next();
a = hole(); op(O_JZ);
s0 = snap_push();
- ca = expr(); rval(); ta = xty; ba = xbor;
+ ca = expr(); rval(); ta = xty; ba = xbor; temp_a = xaf; mask_a = effect_mask(ca);
if (ca != C_FRESH) plainuse(ca);
livea = live; sa = snap_push();
b = hole(); op(O_JMP);
expect(s_colon);
setlit(a, here);
snap_restore(s0); live = live0;
- cb = expr(); rval(); bb = xbor;
+ cb = expr(); rval(); bb = xbor; temp_b = xaf; mask_b = effect_mask(cb);
if (cb != C_FRESH) plainuse(cb);
join2(sa, livea, b, live, n0, "ends the two sides of ?: in different states");
snap_pop(s0);
@@ -5741,8 +7306,11 @@ int ternary(int c) {
if (!compat(ta, xty) && !compat(xty, ta)) tcerr("the two sides of ?:", ta, xty);
if (ta != tword && !isfl(ta)) xty = ta;
else if (isfl(ta) || isfl(xty)) { xty = tfloat; xfloat = 1; }
+ if_value = 0;
+ effect_value = mask_a | mask_b;
if (ba != bb) xbor = -1;
- lv = 0; lvk = LV_NONE; xzero = 0;
+ if (temp_a && temp_a[2] == 5) xaf = temp_a; else if (temp_b && temp_b[2] == 5) xaf = temp_b;
+ lv = 0; lvk = LV_NONE; xzero = 0; xvk = 0;
if (ca == C_FRESH && cb == C_FRESH) return C_FRESH;
if (ca == C_FRESH || cb == C_FRESH) cerr("both sides of ?: have to be new objects, or neither");
return C_PLAIN;
@@ -5761,7 +7329,7 @@ int expr() {
if (tv == s_q) return ternary(c);
if (tv == s_eq) {
if (!lv) die("this can't be assigned to");
- k = lvk; i = lvi; lt = xty;
+ k = lvk; i = lvi; lt = xty; require_mutable(lt);
if (isagg(lt)) serr("a whole struct or array can't be assigned; assign its fields");
next();
a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i];
@@ -5773,8 +7341,10 @@ int expr() {
ltag[i] = ltag[i] | LASG;
setflt(i, xfloat);
setstr(i, xstr);
- note_store(i); af_store(i);
+ note_store(i); af_store(i); vf_store(i);
}
+ if (cmode && !dry && k == LV_GLOBAL) { vf_symbols(); svkind[i] = xvk; svvalue[i] = xvv; }
+ else if (k != LV_LOCAL) vf_effect();
if (!compat(lt, xty)) tcerr("assignment", lt, xty);
if (a) { lit(0); op(O_PICK); vaddr(i); op(O_ST); o3refresh(i); }
else {
@@ -5790,7 +7360,7 @@ int expr() {
if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
cerrn(lvi, "owns an object; compound assignment would lose it");
}
- lt = xty;
+ lt = xty; require_mutable(lt);
fk = -1; lf = xfloat;
if (lvk == LV_LOCAL) fk = lvi;
if (isfl(lt)) lf = 1;
@@ -5806,7 +7376,7 @@ int expr() {
else { emitseq(bseq[abin[a]], blen[abin[a]]); xfloat = 0; }
fcoerce(lt);
if (fk >= 0) {
- lafact[fk] = 0; xaf = 0;
+ lafact[fk] = 0; xaf = 0; if (cmode && !dry) lvkind[fk] = 0;
ltag[fk] = ltag[fk] | LASG;
setflt(fk, xfloat); setstr(fk, 0);
if (forstep && fk == ft_iv && bname[abin[a]] == s_plus && xconst && xcval == 1) fs_ok = 1;
@@ -5814,6 +7384,7 @@ int expr() {
else putfact(fk, -1, -1);
bpoison(fk);
}
+ if (fk < 0) vf_effect(); xvk = 0;
k = bintype(abin[a], lt, xty);
if (!compat(lt, k)) tcerr("compound assignment", lt, k);
if (i) { lit(0); op(O_PICK); vaddr(i - 1); op(O_ST); } else storekeep(lt);
@@ -5848,7 +7419,7 @@ int blockexpr() {
tail = 1; t = xty;
}
next();
- if (tail) c = handup(c); else { lit(0); xbor = -1; xfloat = 0; xstr = 0; }
+ if (tail) c = handup(c); else { lit(0); xbor = -1; xfloat = 0; xstr = 0; xvk = 1; xvv = 0; }
if (xbor >= save) xbor = -1; // that local dies with this block; the pointer leaves untracked
block_close(save);
fdepth--; bxdepth--;
@@ -5856,7 +7427,8 @@ int blockexpr() {
return c;
}
-// ---- constant expressions ----
+//: ---- constant expressions ----
+
// constant expressions, evaluated while compiling: numbers, named constants,
// addresses of globals and functions, unary minus and ~, parentheses, and the
// operators + - * | & << >>. used for array lengths, enum values, const
@@ -5904,7 +7476,8 @@ int cexpr() {
return v;
}
-// ---- statements ----
+//: ---- statements ----
+
// after a syntax error: skip to the end of the statement
// statements. stmt is the dispatcher: a keyword statement (if, while, return,
// a parse word...), a declaration, an empty statement, a block, or an
@@ -5918,7 +7491,11 @@ int resync() {
}
// compile one statement.
int stmt() {
+ word before = borrow_slots; int result; emit_statement_hooks(); result = stmt_inner(); borrow_cleanup_since(before); return result;
+}
+int stmt_inner() {
int s, c;
+ if (probe_off >= 0) probe_stmt();
if (panic) return resync();
if (tk == T_ID && findloc(tv) < 0) {
s = lookup(tv);
@@ -5940,6 +7517,7 @@ int block() {
expect(s_lb);
fdepth++;
while (!is_op(s_rb)) { if (tk == T_EOF) { die("missing }"); break; } stmt(); }
+ if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; }
block_close(save); // before the }, so its line is the one reported
next();
fdepth--;
@@ -5988,29 +7566,29 @@ int lt_note(int o, int t) {
lt_off = chk_resize(lt_off, lt_n, ltcap); lt_at = chk_resize(lt_at, lt_n, ltcap);
lt_len = chk_resize(lt_len, lt_n, ltcap); lt_type = chk_resize(lt_type, lt_n, ltcap);
}
- sm = outmode; sb = outbuf; sn = outn; sc = outcap;
- outmode = 3; outn = 0; tprint(t); need = ltxn + outn;
+ sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap;
+ rt.outmode = 3; rt.outn = 0; tprint(t); need = ltxn + rt.outn;
if (!ltxt || need > ltxtcap) {
while (need > ltxtcap) ltxtcap = ltxtcap * 2;
ltxt = chk_resize(ltxt, ltxn, ltxtcap);
}
- outmode = 2; outbuf = ltxt; outn = ltxn; outcap = ltxtcap;
+ rt.outmode = 2; rt.outbuf = ltxt; rt.outn = ltxn; rt.outcap = ltxtcap;
tprint(t);
- sl = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ sl = rt.outn;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
lt_off[lt_n] = o; lt_at[lt_n] = ltxn; lt_len[lt_n] = sl - ltxn; lt_type[lt_n] = t; lt_n++;
ltxn = sl;
return 0;
}
// compile a local declaration, typed or let-style.
int localdecl(int typed) {
- int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o;
+ int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr; word iq, it;
if (typed) { dtype(); t = dty; tg = dtag; if (t == tvoid) serr("a variable can't be void"); }
if ((o3on || o3one) && !typed) serr("o3 variables need a type");
while (1) {
pp = tpack();
o = toff;
- rs_note(RS_LOCAL, o);
+ rs_note(RS_LOCAL, o); vr = rsn - 1;
n = name();
i = newloc(n);
lrsoff[i] = o;
@@ -6023,10 +7601,17 @@ int localdecl(int typed) {
if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
} else if (t && isagg(t)) { larr[i] = 1; ltype[i] = t; fslots = fslots + tsz(t); }
else { fslots++; if (t) ltype[i] = t; if (isfl(t)) setflt(i, 1); if (o3on) { o3promote(i); o3mkcache(i); } }
+ capture_cell(i, 0);
if (accept(s_eq)) {
- if (larr[i]) serr("an array or a struct can't be initialized with =");
+ if (larr[i] && ltype[i][0] == TY_ARR && (is_op(s_lb) || tk == T_STR || tk == T_BSTR)) {
+ iq = array_initializer_tokens(ltype[i]); it = array_measure(iq, ltype[i]);
+ fslots = fslots + tsz(it) - tsz(ltype[i]);
+ ltype[i] = it; array_initialize(iq, it, lslot[i], 1); rt.free(iq);
+ } else {
+ if (larr[i]) serr("an array or a struct needs an initializer list");
newsz = -1; newleaf = 0; if (tk == T_NUM) nomagic = 1;
- c = expr(); rval(); nomagic = 0; kc = xconst && !xfloat;
+ if (t && t[0] == TY_DYN && is_op(s_lb)) c = array_dynamic_literal(t); else c = expr();
+ rval(); nomagic = 0; kc = xconst && !xfloat;
if (kc) ltag[i] = ltag[i] | LCONST;
if (c == C_FRESH) { lnsz[i] = newsz; lleaf[i] = newleaf; }
if (t) fcoerce(t);
@@ -6035,7 +7620,7 @@ int localdecl(int typed) {
o3refresh(i);
setflt(i, xfloat);
setstr(i, xstr);
- note_store(i); af_store(i);
+ note_store(i); af_store(i); vf_store(i);
if (t) { if (!compat(t, xty)) tcerr("initializer", t, xty); }
else if (!isnum(xty)) {
if (xty[0] == TY_ARR) ltype[i] = decay(xty);
@@ -6044,9 +7629,12 @@ int localdecl(int typed) {
}
assigncheck(LV_LOCAL, i, c);
if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; }
+ }
} else if (gok && !larr[i]) gok = 0; // no initializer: nothing says what it is
+ if (larr[i] && ltype[i][0] == TY_ARR && ltype[i][3] < 0) serr("T[] needs an initializer to determine its length");
if (larr[i]) { k = 1; if (ltype[i][0] == TY_ARR) k = ltype[i][3]; lafact[i] = af_new(k, 1, pp); laaddr[i] = lafact[i]; if (lafact[i]) lafact[i][5] = i; }
- af_declaration(o, lafact[i]);
+ af_declaration(o, lafact[i]); os_at(o, i);
+ if (cmode) vf_at(vr, lvkind[i], lvvalue[i]);
kc = tword;
if (ltype[i]) kc = ltype[i];
lt_note(((pp >> 40) & 0x1FFFFF) - 1, kc);
@@ -6073,7 +7661,7 @@ int kw_if() {
int a, b, c, s0, sa, live0, livea, n0 = nloc, nt, cj;
stmtonly();
next();
- expect(s_lp); c = expr(); rval(); plainuse(c); ftest(); expect(s_rp);
+ expect(s_lp); c = borrow_condition(); expect(s_rp);
a = hole(); op(O_JZ);
live0 = live;
s0 = snap_push();
@@ -6172,7 +7760,7 @@ int kw_while() {
peek();
if (pk_tk == T_OP && pk_tv == s_rp) inf = 1;
}
- c = expr(); rval(); plainuse(c); ftest(); expect(s_rp);
+ c = borrow_condition(); expect(s_rp);
a = hole(); op(O_JZ);
if (!inf && live) sc = snap_push();
loop_push(c0, s0);
@@ -6190,7 +7778,7 @@ int kw_while() {
// if it holds and the bound is len(a) of a fixed array, the body is told that
// a[i] is in range (lpfact), and its bounds check disappears.
int kw_for() {
- int save = nloc, s0, sc = -1, c0, a, b, st, c, inf = 0;
+ int save = nloc, s0, sc = -1, c0, a, b, st, c, inf = 0; word borrow_mark = borrow_slots;
stmtonly();
next();
expect(s_lp);
@@ -6198,12 +7786,13 @@ int kw_for() {
if (is_id(s_var)) kw_var();
else if (tstart()) localdecl(1);
else { if (!is_op(s_semi)) { c = expr(); rval(); op(O_DROP); discard(c); } expect(s_semi); }
+ borrow_cleanup_since(borrow_mark);
s0 = snap_push();
c0 = here;
ft_iv = -1; fs_ok = 0; fortest = 1;
if (!is_op(s_semi)) {
if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_semi) inf = 1; }
- c = expr(); rval(); plainuse(c); ftest();
+ c = borrow_condition();
} else { lit(1); inf = 1; xfloat = 0; }
fortest = 0;
expect(s_semi);
@@ -6211,8 +7800,9 @@ int kw_for() {
if (!inf && live) sc = snap_push();
b = hole(); op(O_JMP);
st = here;
- forstep = 1;
+ forstep = 1; borrow_mark = borrow_slots;
if (!is_op(s_rp)) { c = expr(); rval(); op(O_DROP); discard(c); }
+ borrow_cleanup_since(borrow_mark);
forstep = 0;
expect(s_rp);
jmp(c0);
@@ -6247,7 +7837,7 @@ int kw_do() {
next();
expect(s_lp);
if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; }
- c = expr(); rval(); plainuse(c); ftest();
+ c = borrow_condition();
expect(s_rp);
expect(s_semi);
if (!inf && live) sc = snap_push();
@@ -6312,7 +7902,7 @@ int casebody(int s0, int live0) {
int kw_switch() {
int sl = tline;
int save = nloc, tmp, c, s0, live0, defent = -1, skip, nxt, bj, v, a = -1, st, tg = 0, cov = 0, gi, nb = 0, j, arm, bi;
- [8]word pbn; [8]word pbl;
+ word[8] pbn; word[8] pbl;
stmtonly();
next();
expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp);
@@ -6328,6 +7918,13 @@ int kw_switch() {
if (tk == T_EOF) { die("missing }"); break; }
if (is_id(s_case)) {
next();
+ if (tk == T_ID && rt.eq_w(tv, "_")) {
+ next(); expect(s_colon);
+ if (defent >= 0) serr("a switch has one catch-all case");
+ skip = hole(); op(O_JMP); defent = here;
+ casebody(s0, live0); setlit(skip, here);
+ continue;
+ }
bj = 0;
gi = -1;
peek();
@@ -6358,6 +7955,7 @@ int kw_switch() {
nxt = hole(); op(O_JMP);
fixall(bj, here);
for (j = 0; j < nb && j < pm_nf[gi]; j++) {
+ capture_cell(pbl[j], 0);
laddr(lslot[tmp]); op(O_LD); lit(pm_off[gi * 8 + j]); op(O_ADD); op(O_LD);
vaddr(pbl[j]); op(O_ST);
}
@@ -6412,6 +8010,10 @@ int kw_return() {
else {
c = expr(); rval();
returned = xty;
+ if (!nclo && !xzero) {
+ if (!fn_return_type) fn_return_type = returned;
+ else if (fn_return_type != returned) fn_return_type = 0 - 1;
+ }
if_ret(xty, xzero);
if (fty && fty != tvoid) fcoerce(fty);
if (fty == tvoid) tcerr("return value", tvoid, xty);
@@ -6423,9 +8025,10 @@ int kw_return() {
}
expect(s_semi);
retcheck(c);
+ borrow_cleanup();
exit_vars(1, 0);
epichain = fixnew(here, epichain);
- lit(MW - 1); op(O_JMP);
+ lit(rt.MW - 1); op(O_JMP);
live = 0;
return 0;
}
@@ -6458,7 +8061,7 @@ int kw_o3() {
if (no3pend >= o3pendcap) { o3pend = chk_resize(o3pend, no3pend * 67, o3pendcap * 2 * 67); o3pendcap = o3pendcap * 2; }
{
p = o3pend + no3pend * 67; p[0] = lo; p[1] = here; p[2] = no3sl;
- copy_w(p + 3, o3sl, no3sl); copy_w(p + 35, o3rg, no3sl); no3pend++;
+ rt.copy_w(p + 3, o3sl, no3sl); rt.copy_w(p + 35, o3rg, no3sl); no3pend++;
}
local_drop(save); fdrop(save); // the hidden cache variables
i = lbase;
@@ -6487,12 +8090,13 @@ int kw_unchecked() {
return 0;
}
-// ---- asm blocks: raw vm words, one per token ----
-// numbers push, -n pushes a negative number, #n emits a raw word, one-char
-// tokens from the mnemonic table are opcodes, names push the address of a
-// variable or function, :name defines a label for this block.
-// (labels are local to the block: :name defines one, a later name refers to it,
-// and references to labels not yet defined are resolved when the block ends)
+//: ---- asm blocks: raw vm words, one per token ----
+
+//: numbers push, -n pushes a negative number, #n emits a raw word, one-char
+//: tokens from the mnemonic table are opcodes, names push the address of a
+//: variable or function, :name defines a label for this block.
+//: (labels are local to the block: :name defines one, a later name refers to it,
+//: and references to labels not yet defined are resolved when the block ends)
word asm_names = R_ASM, asm_sites = R_ASM + ASM_LABELS;
word asm_refs = R_ASM + ASM_LABELS + ASM_LABELS, asm_fixes = R_ASM + ASM_LABELS + ASM_LABELS + ASM_REFS;
int asm_labelcap = ASM_LABELS, asm_refcap = ASM_REFS;
@@ -6549,7 +8153,11 @@ int asmblock() {
if (tk != T_ID) die("bad token in asm");
i = findloc(tv);
if (i >= 0 && (ltag[i] & 16) && !larr[i]) serr("asm can't reach an o3 variable");
- if (i >= 0) { luse(i); ltag[i] = ltag[i] | LREAD | LASG; laddr(lslot[i]); next(); continue; }
+ if (i >= 0) { luse(i); ltag[i] = ltag[i] | LREAD | LASG; vaddr(i); next(); continue; }
+ if (nclo) {
+ k = clocap(tv);
+ if (k >= 0) { vaddr(clo_env[nclo - 1]); op(O_LD); lit(k); op(O_ADD); op(O_LD); next(); continue; }
+ }
k = -1; i = 0;
while (i < nl) { if (ln[i] == tv) k = i; i++; }
if (k >= 0) { lit(la[k]); next(); continue; }
@@ -6576,27 +8184,29 @@ int contiguous_asm() {
word q = grab(), wrapped; int n = q[0], i = 0, need = 2, entry, at;
while (i < n) {
need = need + 16;
- if (q[1 + i * 3] == T_STR || q[1 + i * 3] == T_BSTR) need = need + len_w(q[2 + i * 3]);
+ if (q[1 + i * 3] == T_STR || q[1 + i * 3] == T_BSTR) need = need + rt.len_w(q[2 + i * 3]);
i++;
}
reserve_code(need); entry = here;
wrapped = chk_resize(0, 0, n * 3 + 7); wrapped[0] = n + 2;
wrapped[1] = T_OP; wrapped[2] = s_lb; wrapped[3] = tpack();
- copy_w(wrapped + 4, q + 1, n * 3); at = 4 + n * 3;
+ rt.copy_w(wrapped + 4, q + 1, n * 3); at = 4 + n * 3;
wrapped[at] = T_OP; wrapped[at + 1] = s_rb; wrapped[at + 2] = tpack();
src_push(1, wrapped, 0, 0); next(); asmblock(); src_pop(); op(O_RET);
- free(d*q); return entry;
+ rt.free(d*q); return entry;
}
// asm as a keyword (statement or expression): the block's own stack effect is
// the program's responsibility, so the result is a plain word
int kw_asm() {
+ if (effect_current) effect_current[0] = 2;
next();
asmblock();
xcls = C_PLAIN;
return 0;
}
-// ---- quote: code as data ----
+//: ---- quote: code as data ----
+
// a token list is an object: [n] then kind, value, line for each token. names,
// strings and operators are interned pointers.
// grab reads the { ... } after quote or times (a parse word) into a token list,
@@ -6616,14 +8226,14 @@ word .u grab() {
next();
}
next();
- q = new(n * 3 + 1);
+ q = rt.new(n * 3 + 1);
q[0] = n;
- copy_w(q + 1, R_QBUF, n * 3);
+ rt.copy_w(q + 1, R_QBUF, n * 3);
return u*q;
}
-unchecked word .u qnew(word src) { int n = src[0] * 3 + 1; word q = new(n); copy_w(q, src, n); return q; }
-word .u qdup(.m q) { int n = q[0] * 3 + 1; word r = new(n); copy_w(r, q, n); return u*r; }
-// emit n words copied from the array q.
+word .u qnew(word src) { int n = src[0] * 3 + 1; word q = rt.new(n); rt.copy_w(q, src, n); return q; }
+word .u qdup(.m q) { int n = q[0] * 3 + 1; word r = rt.new(n); rt.copy_w(r, q, n); return u*r; }
+// emit n words copied from a well-formed token buffer q; references in the emitted words must outlive the image.
int wcopyto(.m q, int n) {
int i = 0; reserve_code(n);
while (i < n) { emit(q[i]); i++; }
@@ -6632,22 +8242,22 @@ int wcopyto(.m q, int n) {
// quote { tokens }: the list is emitted into the code behind a jump, and the
// expression's value is a fresh copy of it (qnew), so a program can edit its
// copy without touching the original
-int kw_quote() {
+int quote_tokens() {
word q; int j, a, n;
- next();
q = grab();
j = hole(); op(O_JMP);
n = q[0] * 3 + 1; reserve_code(n); a = here;
wcopyto(q, n);
- free(d*q);
+ rt.free(d*q);
setlit(j, here);
lit(a);
callname(s_qnew);
xcls = C_FRESH;
return 0;
}
+int kw_quote() { next(); return quote_tokens(); }
// tokens of a string, as a token list
-word .u tokens(int s) {
+word*.u tokens(char* s) {
int n = 0; word q;
src_push(2, s, 0, 0);
next();
@@ -6657,20 +8267,21 @@ word .u tokens(int s) {
next();
}
src_pop();
- q = new(n * 3 + 1);
+ q = rt.new(n * 3 + 1);
q[0] = n;
- copy_w(q + 1, R_QBUF, n * 3);
+ rt.copy_w(q + 1, R_QBUF, n * 3);
return u*q;
}
// feed a token list into the input: the parser reads it next, then carries
// on where it was. the list is freed once it has been read.
-unchecked int feed(.d q) {
+int feed(.d q) {
src_push(1, q, 1, 1);
next();
return 0;
}
-// ---- reading a unit and scanning it for signatures ----
+//: ---- reading a unit and scanning it for signatures ----
+
// a unit from stdin is read whole first (4 chars to a word), then scanned
// once for function signatures, so a function's tags are known before its first
// call even when it's defined further down.
@@ -6678,21 +8289,22 @@ unchecked int feed(.d q) {
// literals, see srcend) or end of input, packing 4
// characters to a word, so prescan can run over it and then the real parse can
// run over the same text
-unchecked word readsrc() {
+word .u readsrc() {
int cap = 4090; word b; int n = 0, c; word nb; int w;
- b = new(4090);
+ b = rt.new(4090);
qst = 0; qp = 0; qesc = 0;
while (!asm { ? }) {
c = asm { , } & 255;
+ if (c == 255 && asm { ? }) break; // the end of a slow pipe, found by the read itself
// srcend only has anything to decide at a quote, a slash or a star (or
// inside a comment or literal); every other character in plain code just
// clears the "previous char was a slash" memory
if (qst || c == 34 || c == 39 || c == '/' || c == '*' || c == 96) { if (srcend(c)) break; }
else qp = 0;
if ((n >> 2) + 2 >= cap) {
- nb = new(cap + cap + 6);
- copy_w(nb, b, cap);
- free(b);
+ nb = rt.new(cap + cap + 6);
+ rt.copy_w(nb, b, cap);
+ rt.free(b);
b = nb;
cap = cap + cap + 6;
}
@@ -6701,7 +8313,7 @@ unchecked word readsrc() {
b[w] = b[w] | (c << ((n & 3) * 8));
n++;
}
- b[n >> 2] = b[n >> 2] & (pow2[(n & 3) * 8] - 1); // the 0 at the end
+ b[n >> 2] = b[n >> 2] & (rt.pow2[(n & 3) * 8] - 1); // the 0 at the end
if ((n & 3) == 0) b[n >> 2] = 0;
b[(n >> 2) + 1] = 0;
return b;
@@ -6744,6 +8356,7 @@ int prescan_fn(int ret, int n, int o3) {
if (!is_op(s_lb) && !is_op(s_semi)) return 0; // not a function: an enhancement, a call in a parse word...
if (!dnm) dnm = n;
s = lookup(n);
+ if (s >= 0 && skind[s] == K_KW) return 0;
if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) s = fwdfn(n);
if (!(sflag[s] & F_SIG)) {
ssig[s] = ret + np + np + tags * 64;
@@ -6754,7 +8367,7 @@ int prescan_fn(int ret, int n, int o3) {
}
// scan ahead, without emitting code, to learn what a block of tokens contains.
int prescan(int kind, int obj) {
- int depth = 0, sp = panic, sn = nerr, pend = 0, po3 = 0, n, dep;
+ int depth = 0, sp = panic, sn = nerr, pend = 0, po3 = 0, n, dep; char* oldmodule = module_name; word oldimports = module_imports;
dry++;
src_push(kind, obj, 0, 0);
next();
@@ -6762,6 +8375,8 @@ int prescan(int kind, int obj) {
if (is_op(s_lb)) depth++;
else if (is_op(s_rb)) { depth--; pend = 0; po3 = 0; }
else if (depth <= 0) {
+ if (is_id(s_type)) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; }
+ if (tk == T_ID && rt.eq_w(tv, "namespace")) { kw_namespace(); continue; }
if (is_op(s_semi)) { pend = 0; po3 = 0; }
else if (is_op(s_dot)) { peek(); if (pk_tk == T_ID && pk_tv == s_u) pend = 1; }
else if (is_id(s_o3)) po3 = 1;
@@ -6794,31 +8409,33 @@ int prescan(int kind, int obj) {
next();
}
src_pop();
- dry--; panic = sp; nerr = sn;
+ dry--; panic = sp; nerr = sn; module_name = oldmodule; module_imports = oldimports;
return 0;
}
// compile a whole unit from stdin
int unit_stdin() {
int src = readsrc();
+ module_name = 0; module_imports = 0;
prescan(3, src);
src_push(3, src, 0, 0);
next();
unit_top();
unit_end(ubase);
src_pop();
- free(src);
+ rt.free(src);
return 0;
}
-// ---- nested compiles ----
-// compile() and load() can run while another function is being compiled (from
-// a parse word). the function context is saved, the new code is emitted in
-// place with a jump around it, and then everything is put back.
-// the context frame is 34 words: everything about the function being compiled
-// that a nested compile would otherwise clobber (locals, depth, frame size,
-// loop stack position, the expression-state globals). closures use the same
-// pair.
-const int CTX_WORDS = 34;
+//: ---- nested compiles ----
+
+//: compile() and load() can run while another function is being compiled (from
+//: a parse word). the function context is saved, the new code is emitted in
+//: place with a jump around it, and then everything is put back.
+//: the context frame is 34 words: everything about the function being compiled
+//: that a nested compile would otherwise clobber (locals, depth, frame size,
+//: loop stack position, the expression-state globals). closures use the same
+//: pair.
+const int CTX_WORDS = 48;
int ctx_push() {
af_forget();
word f;
@@ -6827,8 +8444,9 @@ int ctx_push() {
f[0] = nloc; f[1] = lbase; f[2] = fdepth; f[3] = fslots; f[4] = fchk; f[5] = fret; f[6] = live;
f[7] = bxdepth; f[8] = epichain; f[9] = fframe; f[10] = infn; f[11] = lsp; f[12] = loopbase;
f[13] = lv; f[14] = lvk; f[15] = lvi; f[16] = kw_expr; f[17] = xcls; f[18] = snaptop;
- f[19] = fty; f[20] = ntp; f[21] = xty; copy_w(f + 22, tpn, 8);
- f[30] = finsym; f[31] = finfer; f[32] = ifcur; f[33] = ifcall;
+ f[19] = fty; f[20] = ntp; f[21] = xty; rt.copy_w(f + 22, tpn, 8);
+ f[30] = finsym; f[31] = finfer; f[32] = ifcur; f[33] = ifcall; f[34] = effect_current; f[35] = borrow_slots; f[36] = borrow_pending; f[37] = borrow_depth; f[38] = borrow_jump; f[39] = borrow_entry; f[40] = capture_enabled; f[41] = effect_value; f[42] = xprojection; f[43] = effect_floor; f[44] = module_name; f[45] = module_imports;
+ f[46] = fn_return_type; f[47] = if_value;
return 0;
}
// pop the innermost function context.
@@ -6846,13 +8464,16 @@ int ctx_pop() {
live = f[6];
bxdepth = f[7]; epichain = f[8]; fframe = f[9]; infn = f[10]; lsp = f[11]; loopbase = f[12];
lv = f[13]; lvk = f[14]; lvi = f[15]; kw_expr = f[16]; xcls = f[17]; snaptop = f[18];
- fty = f[19]; ntp = f[20]; xty = f[21]; copy_w(tpn, f + 22, 8);
- finsym = f[30]; finfer = f[31]; ifcur = f[32]; ifcall = f[33];
+ fty = f[19]; ntp = f[20]; xty = f[21]; rt.copy_w(tpn, f + 22, 8);
+ finsym = f[30]; finfer = f[31]; ifcur = f[32]; ifcall = f[33]; effect_current = f[34]; borrow_slots = f[35]; borrow_pending = f[36]; borrow_depth = f[37]; borrow_jump = f[38]; borrow_entry = f[39]; capture_enabled = f[40]; effect_value = f[41]; xprojection = f[42]; effect_floor = f[43]; module_name = f[44]; module_imports = f[45]; xvk = 0;
+ fn_return_type = f[46]; if_value = f[47];
return 0;
}
-// ---- functions and the top level ----
-// ---- a function, start to finish ----
+//: ---- functions and the top level ----
+
+//: ---- a function, start to finish ----
+
// fndef reads the header, then: fn_begin resets the per-function state,
// prologue emits the frame setup, the parameters are declared and stored from
// the stack into their slots, body parses the statements, and epilogue emits
@@ -6861,23 +8482,25 @@ int ctx_pop() {
// that epilogue fills with fslots.
// fn_begin: chk is 0 for unchecked, ret is 1 for a .u return
int fn_begin(int chk, int ret) {
- lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 0;
+ capture_enabled = 0; effect_value = 0; effect_current = 0; borrow_slots = 0; borrow_pending = 0; borrow_depth = 0; borrow_jump = -1; borrow_entry = 0;
+ if (cmode && svkind && !dry) rt.fill_w(svkind, 0, nsym); xvk = 0;
+ lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; fty = 0; finfer = 0; fn_return_type = 0;
live = 1; bxdepth = 0; epichain = 0; infn = 1; loopbase = lsp; lv = 0; lvk = LV_NONE; xbor = -1;
return 0;
}
// allocate the frame, retaining the caller's pointer until any growth finishes.
int prologue() {
int k, busy, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave);
- lit(ZFP); op(O_LD);
+ lit(rt.ZFP); op(O_LD);
if (!helper) { lit(0); op(O_PICK); }
- fframe = hole(); op(O_SUB); lit(ZFP); op(O_ST);
+ fframe = hole(); op(O_SUB); lit(rt.ZFP); op(O_ST);
if (helper) return 0;
lit(sval[lookup(s_frame_busy)]); op(O_LD); busy = hole(); op(O_JZ);
done = hole(); op(O_JMP); setlit(busy, here);
- lit(ZFP); op(O_LD); lit(sval[lookup(s_frame_floor)]); op(O_LD);
+ lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_floor)]); op(O_LD);
lit(sval[lookup(s_frame_guard)]); op(O_LD); op(O_ADD); op(O_LT); k = hole(); op(O_JZ);
- lit(ZFP); op(O_LD); lit(1); op(O_PICK); lit(ZFP); op(O_ST);
- callname(s_frame_grow); lit(ZFP); op(O_ST);
+ lit(rt.ZFP); op(O_LD); lit(1); op(O_PICK); lit(rt.ZFP); op(O_ST);
+ callname(s_frame_grow); lit(rt.ZFP); op(O_ST);
busy = hole(); op(O_JMP);
setlit(k, here); setlit(done, here); op(O_DROP); setlit(busy, here);
return 0;
@@ -6887,14 +8510,14 @@ int epilogue() {
int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave);
lit(0);
fixall(epichain, here);
- lit(ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(ZFP); op(O_ST);
+ lit(rt.ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(rt.ZFP); op(O_ST);
if (!helper) {
lit(sval[lookup(s_frame_node)]); op(O_LD); node = hole(); op(O_JZ);
- lit(ZFP); op(O_LD); lit(sval[lookup(s_frame_top)]); op(O_LD); op(O_SUB); k = hole(); op(O_JZ);
+ lit(rt.ZFP); op(O_LD); lit(sval[lookup(s_frame_top)]); op(O_LD); op(O_SUB); k = hole(); op(O_JZ);
done = hole(); op(O_JMP); setlit(k, here);
- callname(s_frame_leave); lit(ZFP); op(O_ST); setlit(done, here); setlit(node, here);
+ callname(s_frame_leave); lit(rt.ZFP); op(O_ST); setlit(done, here); setlit(node, here);
}
- op(O_RET);
+ op(O_RET); borrow_initializer();
setlit(fframe, fslots);
infn = 0;
local_drop(lbase);
@@ -6905,11 +8528,15 @@ int epilogue() {
// checks: hints, unused m objects, anything still owned is freed, and a .u
// function that falls off the end is an error
int body() {
+ int closed, endplace;
+ capture_prepare(); borrow_begin();
expect(s_lb);
while (!is_op(s_rb)) {
if (tk == T_EOF) { die("missing } at the end of a function"); break; }
stmt();
}
+ closed = is_op(s_rb); endplace = toff + toklen;
+ if (probe_off >= 0 && !chkabort && !dry && !ginst && incdepth == 0 && ctxsp <= nclo && toff >= probe_off) { probe_snap(); chkabort = 1; }
local_hints(lbase);
unused_check(lbase);
if (ifcur) if_fin();
@@ -6919,6 +8546,7 @@ int body() {
if (fret) cerr("a function returning .u has to end with a return");
}
next();
+ if (probe_off >= 0 && !chkabort && closed && nclo == 0 && probe_has == 1 && endplace <= probe_off && !ginst && ctxsp == 0) { probe_has = 0; probe_n = 0; } // the place is past this function
return 0;
}
// the rest of a function, after its return type and name:
@@ -6928,7 +8556,7 @@ int body() {
// into stype, checked against any earlier prototype or prescan result, and
// where the o3 region for an o3 function is handed to the core)
int fndef(int chk, int rty, int ret, int n) {
- int s, np = 0, tags = 0, t, sig, i; [24]word pn; [24]word pt; [24]word pty; int typed = 0, ft, o3f = o3nxt, lo, hi, pk;
+ int s, np = 0, tags = 0, t, sig, i; char*[24] pn; word[24] pt; word[24] pty; int typed = 0, ft, explicit_rty = !!rty, o3f = o3nxt, lo, hi, pk;
o3nxt = 0;
if (rty || ntp) typed = 1;
if (!dnm) dnm = n;
@@ -6937,6 +8565,7 @@ int fndef(int chk, int rty, int ret, int n) {
s = addsym(n, K_FN, 0);
sflag[s] = 0;
}
+ def_note(s, nameoff);
ifcur = 0;
if (ifon && ifkk && !ginst && !ntp && !dry) {
if (ifkk > 0 && !rty) if_begin(s, ifkk); else if_begin(s, 0);
@@ -6950,15 +8579,16 @@ int fndef(int chk, int rty, int ret, int n) {
pk = 0;
if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) {
pty[np] = ptype(); typed = 1; pk = 1;
+ if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
if (isagg(pty[np])) {
if (ginst) pty[np] = tptr(pty[np]); else if (!stubgen) serr("structs and arrays are passed by pointer");
// (an extern stub takes its struct by value, which is an address here)
}
}
if (ifcur) {
- if (pk) if_param(np, tpack(), 0);
- else if (is_op(s_dot)) if_param(np, tpack(), 2);
- else if_param(np, tpack(), 1);
+ if (pk) if_param(np, tpack(), 0, pty[np]);
+ else if (is_op(s_dot)) if_param(np, tpack(), 2, pty[np]);
+ else if_param(np, tpack(), 1, pty[np]);
}
if (accept(s_dot)) { // .m lends, .d takes over
if (tv == s_m) {
@@ -6966,7 +8596,7 @@ int fndef(int chk, int rty, int ret, int n) {
} else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d");
next();
}
- pnoffs[np] = toff; rs_note(RS_PARAM, toff); pn[np] = name();
+ pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name();
if (o3f && pty[np] == tword) serr("o3 parameters need a type");
if (np >= 12 && t) die("only the first 12 parameters can be tagged");
pt[np] = t;
@@ -6991,6 +8621,7 @@ int fndef(int chk, int rty, int ret, int n) {
if (stype[s] && stype[s] != ft) serrn(n, "doesn't match its earlier declaration");
stype[s] = ft;
}
+ if (ifcur && explicit_rty) if_join(ifcur + 3, rty);
sig_attach(n, rty, np, pn, pty, typed);
if (accept(s_semi)) { sflag[s] = sflag[s] | F_DECL; ntp = 0; return s; }
if (sflag[s] & F_DEF) serrn(n, "is defined twice");
@@ -7003,7 +8634,7 @@ int fndef(int chk, int rty, int ret, int n) {
sval[s] = here; // the name still has an address and a type, so reflection can ask for them; it halts if it is ever jumped to
sflag[s] = sflag[s] | F_DEF;
fixall(sfix[s], here); sfix[s] = 0;
- lit(MW - 1); op(O_JMP);
+ lit(rt.MW - 1); op(O_JMP);
return s;
}
sval[s] = here;
@@ -7011,14 +8642,17 @@ int fndef(int chk, int rty, int ret, int n) {
fixall(sfix[s], here); sfix[s] = 0;
fn_begin(chk, ret);
finsym = s; finfer = !ret;
- if (typed) fty = rty;
+ effect_current = effect_record(s); rt.fill_w(effect_current, 0, 50); effect_current[0] = 1; if (!chk || stubgen) effect_current[0] = 2; effect_current[1] = np;
+ i = 0; while (i < np) { if (pt[i] == 2) { effect_current[2 + i] = EF_CONSUME; effect_current[26 + i] = EF_CONSUME; } i++; }
+ if (typed && explicit_rty) fty = rty;
no3pend = 0;
if (o3f) { o3on = 1; o3reset(); sflag[s] = sflag[s] | F_O3; }
- prologue();
+ prologue(); borrow_prologue();
i = 0;
while (i < np) {
- t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; fslots++; lsmark(1, pn[i]);
+ t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]);
ltype[t] = pty[i]; lptr[t] = 0;
+ if (ifcur && if_local && i < IFP) if_local[t] = ifcur + 8 + 3 * i;
lt_note(pnoffs[i], pty[i]);
if (o3f) { ltag[t] = 16; o3promote(t); }
if (isfl(pty[i])) setflt(t, 1);
@@ -7033,6 +8667,10 @@ int fndef(int chk, int rty, int ret, int n) {
i = 0; while (i < np) { t = lbase + i; o3mkcache(t); o3refresh(t); i++; }
}
body();
+ if (!explicit_rty && fn_return_type > 0 && fn_return_type != tword) {
+ stype[s] = tfn(fn_return_type, np, pty, ssig[s]);
+ sig_attach(n, fn_return_type, np, pn, pty, 1);
+ }
hi = here;
epilogue();
if (o3f) {
@@ -7044,10 +8682,12 @@ int fndef(int chk, int rty, int ret, int n) {
}
o3flush();
ntp = 0; ifcur = 0;
+ fn_remember(s, n);
return s;
}
-// let g = 1, buf[10]; int g = 3; [10]int buf; point origin;
-// ---- globals, constants, enhancements ----
+//: let g = 1, buf[10]; int g = 3; int[10] buf; point origin;
+//: ---- globals, constants, enhancements ----
+
// a global's storage is emitted in place among the code (one word, or the
// whole array), and the symbol's value is its address. a string initializer
// points at characters emitted right after.
@@ -7059,7 +8699,7 @@ int gdecl(int typed) {
}
// compile the rest of a global declaration: its type, name and initial value.
int gdecl2(int t, int tg, int n) {
- int a, ng = nameoff; word k, arraytype; int s, kp = ifgk, gty = 0, gok = ifon && !t && !dry;
+ int a, ng = nameoff; word k, arraytype, iq; int s, kp = ifgk, gty = 0, gok = ifon && !t && !dry, vr, vk; word vv;
ifgk = 0;
if (!dnm) dnm = n;
if (tg) serr("tags go on locals and parameters, not globals");
@@ -7073,28 +8713,45 @@ int gdecl2(int t, int tg, int n) {
while (k) { emit(0); k--; }
if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
} else if (t && isagg(t)) {
- k = tsz(t); reserve_code(k);
- s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = t;
+ arraytype = t; iq = 0;
+ if (t[0] == TY_ARR && accept(s_eq)) { if (!is_op(s_lb) && tk != T_STR && tk != T_BSTR) serr("an array needs an initializer list"); else { iq = array_initializer_tokens(t); arraytype = array_measure(iq, t); } }
+ if (arraytype[0] == TY_ARR && arraytype[3] < 0) serr("T[] needs an initializer to determine its length");
+ k = tsz(arraytype); reserve_code(k); a = here;
+ s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype;
while (k) { emit(0); k--; }
+ if (iq) { array_initialize(iq, arraytype, a, 0); rt.free(iq); }
} else {
reserve_code(1); a = here;
- s = addsym(n, K_GVAR, a); rs_put(ng, RS_SYM, rs_sym(s), s);
+ s = addsym(n, K_GVAR, a); rs_put(ng, RS_SYM, rs_sym(s), s); vr = rsn - 1; vk = 1; vv = 0;
if (t) stype[s] = t;
emit(0);
if (accept(s_eq)) {
if (tk == T_STR) {
- reserve_code(len_w(tv) + 1); *(a + hdelta) = here;
+ vk = 3; vv = vf_string(tv); if (!t) stype[s] = tptr(tchar);
+ reserve_code(rt.len_w(tv) + 1); *(a + hdelta) = here;
k = tv;
while (*k) { emit(*k); k++; }
emit(0);
next();
if (gok) { if (!gty) gty = tptr(tchar); else if (gty != tptr(tchar)) gok = 0; }
}
+ else if (tk == T_BSTR) {
+ vk = 4; vv = vf_string(tv); *(a + hdelta) = bstrdata(tv); next();
+ if (!t) stype[s] = tbstr; else if (t != tbstr) tcerr("initializer", t, tbstr);
+ if (gok) { if (!gty) gty = tbstr; else if (gty != tbstr) gok = 0; }
+ }
+ else if (is_op(s_lb) || (is_op(s_lp) && caststart())) {
+ arraytype = tarr(tint, -1);
+ if (accept(s_lp)) { arraytype = ptype(); expect(s_rp); }
+ if (arraytype[0] != TY_ARR || !is_op(s_lb)) serr("a global compound literal needs an array initializer");
+ else { array_literal(arraytype); vv = xcval; *(a + hdelta) = vv; if (!t) stype[s] = decay(xty); }
+ }
else {
- *(a + hdelta) = cnorm(t, cexpr());
+ vv = cnorm(t, cexpr()); *(a + hdelta) = vv;
if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
}
} else if (gok) gok = 0; // no initializer: nothing says what it is
+ vf_at(vr, vk, vv);
}
if (stype[s]) lt_note(ng, stype[s]); else lt_note(ng, tword);
if (!accept(s_comma)) break;
@@ -7118,10 +8775,10 @@ int cnorm(word t, int v) {
// const A = 1, B = A + 2;
// const [type] NAME = e, NAME = e; the type is optional and is read and dropped, as in zc0
int gconst() {
- int n;
+ int n, off, s;
next();
if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); }
- while (1) { n = name(); if (!dnm) dnm = n; expect(s_eq); addsym(n, K_CONST, cexpr()); if (!accept(s_comma)) break; }
+ while (1) { off = toff; n = name(); if (!dnm) dnm = n; expect(s_eq); s = addsym(n, K_CONST, cexpr()); rs_put(off, RS_SYM, rs_sym(s), s); if (!accept(s_comma)) break; }
expect(s_semi);
return 0;
}
@@ -7130,7 +8787,7 @@ int gconst() {
// lays out the descriptor the install opcode reads, and wires it in right
// away (an image wires its own in when it boots).
int enhdef() {
- int n, np = 0, nout = 1, dsc, na, pay, len = 0, kind = 0, i; word p; int s, slot, r; [8]word pn;
+ int n, np = 0, nout = 1, dsc, na, pay, len = 0, kind = 0, i; word p; int s, slot, r; char*[8] pn;
next();
// enhance 2 divmod(a, b)
if (tk == T_NUM) {
@@ -7143,13 +8800,13 @@ int enhdef() {
if (!is_op(s_rp)) {
while (1) {
if (np == 8) die("an enhancement takes at most 8 inputs");
- pnoffs[np] = toff; rs_note(RS_PARAM, toff); pn[np] = name();
+ pnoffs[np] = toff; pnplaces[np] = tpack(); rs_note(RS_PARAM, toff); pn[np] = name();
np++;
if (!accept(s_comma)) break;
}
}
expect(s_rp);
- len = 7 + np; i = 0; while (i < np) { len = len + len_w(pn[i]) + 1; i++; }
+ len = 7 + np; i = 0; while (i < np) { len = len + rt.len_w(pn[i]) + 1; i++; }
reserve_code(len); len = 0; dsc = here; i = 0; while (i < 7) { emit(0); i++; }
na = here; i = 0; while (i < np) { emit(0); i++; }
i = 0;
@@ -7170,7 +8827,7 @@ int enhdef() {
if (accept(s_else)) { r = lookup(tv); if (r < 0) r = fwdfn(tv); next(); sfix[s] = r + 1; }
expect(s_semi);
if (!hdelta) {
- r = install(dsc);
+ r = rt.install(dsc);
if (r < 0) {
if (!sfix[s]) serrn(n, "the enhancement didn't install (see install's codes in core.c)");
sflag[s] = sflag[s] | F_FALLBACK;
@@ -7196,7 +8853,7 @@ int topsync() {
// after a function's name: <T, U> declares its type parameters. any the return
// type used were taken on trust; now they get their real numbers
int genparams() {
- int i, j, n = 0; [8]word ps; [8]word bs;
+ int i, j, n = 0; char*[8] ps; word[8] bs;
if (!is_op(s_lt)) { if (ntp) serrn(tpn[0], "isn't a type"); ntp = 0; return 0; }
next();
while (1) {
@@ -7206,7 +8863,7 @@ int genparams() {
if (!accept(s_comma)) break;
}
closeangle();
- fill_w(bs, 0, 8);
+ rt.fill_w(bs, 0, 8);
i = 0;
while (i < ntp) {
j = 0;
@@ -7215,7 +8872,7 @@ int genparams() {
i++;
}
if (ntp && dty) dty = subst(dty, bs);
- copy_w(tpn, ps, n); ntp = n;
+ rt.copy_w(tpn, ps, n); ntp = n;
if (ginst) ntp = 0;
return 0;
}
@@ -7300,8 +8957,12 @@ int topitem1() {
if (is_id(s_memdebug)) return kw_memdebug();
if (is_id(s_enum)) return kw_enum();
if (is_id(s_include)) return kw_include();
+ if (tk == T_ID && rt.eq_w(tv, "namespace")) return kw_namespace();
if (is_id(s_enhance)) return enhdef();
- if (is_id(s_const)) return gconst();
+ if (is_id(s_const)) {
+ peekat(0);
+ if (pk_tk != T_ID || pk_tv != s_fixed) return gconst();
+ }
if (tk == T_ID) {
s = lookup(tv);
if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_TOP)) { kwcall(s, 0); return 0; }
@@ -7322,7 +8983,8 @@ int topitem1() {
die("expected a declaration");
return 0;
}
-// ---- generic functions: templates and instances ----
+//: ---- generic functions: templates and instances ----
+
// which type parameter does name n stand for while an instance is compiled
int gfind(int n) {
int i = 0;
@@ -7356,12 +9018,12 @@ word hasvar(word t) {
return 0;
}
// add the current token to the item being recorded
-unchecked int grec_add() {
+int grec_add() {
word nb;
if (gn * 3 + 4 > gcap) {
- nb = new(gcap + gcap);
- copy_w(nb, gbuf, gcap);
- free(gbuf);
+ nb = rt.new(gcap + gcap);
+ rt.copy_w(nb, gbuf, gcap);
+ rt.free(gbuf);
gbuf = nb;
gcap = gcap + gcap;
}
@@ -7370,10 +9032,10 @@ unchecked int grec_add() {
return 0;
}
// the template entry for generic function s, made on first sight of its header
-unchecked int ginit() {
+int ginit() {
gtab = 1;
gt_sym = chk_resize(0, 0, gtcap); gt_tok = chk_resize(0, 0, gtcap); gt_np = chk_resize(0, 0, gtcap);
- gt_nm = chk_resize(0, 0, gtcap * 8);
+ gt_nm = chk_resize(0, 0, gtcap * 8); gt_module = chk_resize(0, 0, gtcap); gt_imports = chk_resize(0, 0, gtcap); gt_source = chk_resize(0, 0, gtcap);
gi_t = chk_resize(0, 0, gicap); gi_sym = chk_resize(0, 0, gicap); gi_done = chk_resize(0, 0, gicap);
gi_ar = chk_resize(0, 0, gicap * 8);
return 0;
@@ -7384,6 +9046,7 @@ int reserve_generics(int need) {
while (cap < need) cap = cap * 2;
gt_sym = chk_resize(gt_sym, gt_n, cap); gt_tok = chk_resize(gt_tok, gt_n, cap);
gt_np = chk_resize(gt_np, gt_n, cap); gt_nm = chk_resize(gt_nm, gt_n * 8, cap * 8);
+ gt_module = chk_resize(gt_module, gt_n, cap); gt_imports = chk_resize(gt_imports, gt_n, cap); gt_source = chk_resize(gt_source, gt_n, cap);
gtcap = cap; return cap;
}
int reserve_instances(int need) {
@@ -7395,7 +9058,7 @@ int reserve_instances(int need) {
gicap = cap; return cap;
}
// make the generic instance of the function or struct s for its type arguments.
-unchecked int gtmake(int s) {
+int gtmake(int s) {
int k = 0;
if (!gtab) ginit();
while (k < gt_n) {
@@ -7405,10 +9068,10 @@ unchecked int gtmake(int s) {
if (k == gt_n) {
if (gt_n >= gtcap) reserve_generics(gt_n + 1);
gt_n++;
- gt_sym[k] = s; gt_tok[k] = 0;
+ gt_sym[k] = s; gt_tok[k] = 0; gt_module[k] = module_name; gt_imports[k] = module_imports; gt_source[k] = curinc;
}
gt_np[k] = ntp;
- copy_w(gt_nm + k * 8, tpn, ntp);
+ rt.copy_w(gt_nm + k * 8, tpn, ntp);
return k;
}
// skip a function body without compiling it: from the { to its matching }
@@ -7426,18 +9089,98 @@ int gskip() {
return 0;
}
// the item just read defined generic function gnew: keep its tokens
-unchecked int gattach(word buf, int n) {
+int gattach(word .d buf, int n) {
n--; // the last token belongs to the next item
- if (nerr) { free(buf); return 0; }
+ if (nerr) { rt.free(buf); return 0; }
buf[0] = n;
gt_tok[gnew] = buf;
return 0;
}
+// retain one ordinary function's source tokens and the namespace needed to compile it again.
+int fn_remember(int s, int name) {
+ int count = gn - 1, i = 0, j, depth, namepos = -1, open = -1, close = -1; word* q; word meta;
+ if (!grec || !gbuf || fn_method_compile || dry || ginst || nerr || (sflag[s] & F_GEN) || count <= 0) return 0;
+ while (i < count && open < 0) {
+ if (gbuf[1 + i * 3] == T_ID && gbuf[2 + i * 3] == name) {
+ j = i + 1;
+ if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) {
+ depth = 1; j++;
+ while (j < count && depth) {
+ if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lt) depth++;
+ else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_gt) depth--;
+ j++;
+ }
+ }
+ if (j < count && gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) {
+ depth = 1; j++;
+ while (j < count && depth) {
+ if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lp) depth++;
+ else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rp) depth--;
+ j++;
+ }
+ while (j < count && !(gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb)) j++;
+ if (j < count) { namepos = i; open = j; }
+ }
+ }
+ i++;
+ }
+ if (open < 0) return 0;
+ depth = 1; j = open + 1;
+ while (j < count && depth) {
+ if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_lb) depth++;
+ else if (gbuf[1 + j * 3] == T_OP && gbuf[2 + j * 3] == s_rb) depth--;
+ if (depth) j++;
+ }
+ if (depth) return 0;
+ close = j;
+ q = rt.new(1 + count * 3); q[0] = count; rt.copy_w(q + 1, gbuf + 1, count * 3);
+ meta = rt.new(6); meta[0] = q; meta[1] = module_name; meta[2] = module_imports;
+ meta[3] = namepos; meta[4] = open; meta[5] = close; fnmeta[s] = meta;
+ return 1;
+}
+// compile one retained source function for a syntax callback's host context.
+// calls into emitted source are cloned recursively; existing host bindings retain
+// their original globals and entry addresses.
+int syntax_host_function(int symbol) {
+ word meta = fnmeta[symbol], source, full; int clone, count, name;
+ char[40] generated; char[24] reversed; int sequence = fnpatchseq, digits = 0, i = 0;
+ if (syntax_host_symbols[symbol]) return syntax_host_symbols[symbol] - 1;
+ if (!meta || (sflag[symbol] & F_GEN)) { serr("an emitted syntax dependency requires retained nongeneric source"); return -1; }
+ fnpatchseq++;
+ rt.copy_w(generated, "__run_syntax_", 13);
+ if (!sequence) reversed[digits++] = '0';
+ while (sequence) { reversed[digits++] = '0' + sequence % 10; sequence = sequence / 10; }
+ while (i < digits) { generated[13 + i] = reversed[digits - i - 1]; i++; }
+ generated[13 + digits] = 0; name = intern(generated);
+ source = meta[0]; count = source[0]; full = rt.new(1 + count * 3); full[0] = count;
+ rt.copy_w(full + 1, source + 1, count * 3); full[2 + meta[3] * 3] = name;
+ if (meta[1]) clone = fwdfn(methname(meta[1], name)); else clone = fwdfn(name);
+ syntax_host_symbols[symbol] = clone + 1;
+ compile_list_context(full, 1, meta[1], meta[2]); rt.free(full);
+ return clone;
+}
+// source syntax executes in resident host code even when its library's runtime
+// functions are emitted into a compiler-free image. staged symbols never enter
+// the image blob; executable storage stays rooted by the ordinary code blocks.
+int syntax_resident(int symbol) {
+ int saved_here = here, saved_delta = hdelta, saved_limit = hlimit, saved_ptop = ptop, saved_vislo = vislo;
+ int saved_active = image_active, saved_chunk = code_chunk, from = nsym, clone, entry = -1, top;
+ word block = chk_resize(0, 0, 4096);
+ syntax_host_symbols = chk_resize(0, 0, from); syntax_host_floor = ubase; syntax_host_from = from;
+ top = block + block[-5] - rt.HDR;
+ block[0] = rt.code_blocks; block[1] = block + 3; block[2] = top; rt.code_blocks = m*block;
+ if (rt.syscall(9, 9, top, 0) < top) fatal("syntax code address reservation failed");
+ here = block + 3; hdelta = 0; hlimit = top; image_active = 0; vislo = 0;
+ clone = syntax_host_function(symbol); if (clone >= 0 && (sflag[clone] & F_DEF)) entry = sval[clone];
+ popsyms(from); syntax_host_floor = 0; syntax_host_from = 0; rt.free(syntax_host_symbols); syntax_host_symbols = 0;
+ here = saved_here; hdelta = saved_delta; hlimit = saved_limit; ptop = saved_ptop; vislo = saved_vislo; image_active = saved_active; code_chunk = saved_chunk;
+ return entry;
+}
// the symbol for template s with these type arguments, made on first use and
// queued to be compiled once the template's tokens are in. a type parameter
// that nothing bound is a plain word.
word ginstance(int s, word bs) {
- int k = 0, t = 0 - 1, np, i, j, ns; word nm; [24]word dg; int nd; word a;
+ int k = 0, t = 0 - 1, np, i, j, ns; char* nm; char[24] dg; int nd; char* a;
if (dry || !gtab) return s;
while (k < gt_n) {
if (gt_sym[k] == s) { t = k; break; }
@@ -7462,7 +9205,7 @@ word ginstance(int s, word bs) {
}
if (gi_n >= gicap) reserve_instances(gi_n + 1);
i = 0; j = 0;
- a = sname[s]; nm = chk_resize(0, 0, len_w(a) + np * 18 + 3);
+ a = sname[s]; nm = chk_resize(0, 0, rt.len_w(a) + np * 18 + 3);
while (a[j]) { nm[i] = a[j]; i++; j++; }
nm[i] = '<'; i++;
k = 0;
@@ -7479,23 +9222,25 @@ word ginstance(int s, word bs) {
ssig[ns] = ssig[s];
k = gi_n; gi_n++;
gi_t[k] = t; gi_sym[k] = ns; gi_done[k] = 0;
- copy_w(gi_ar + k * 8, bs, np);
+ rt.copy_w(gi_ar + k * 8, bs, np);
return ns;
}
// compile one instance: replay the template's tokens with its type
// parameters bound
-unchecked int gcompile(int k) {
- int t = gi_t[k];
+int gcompile(int k) {
+ int t = gi_t[k]; char* oldmodule = module_name; word oldimports = module_imports; char* oldsource = curinc; int olddepth = incdepth;
+ curinc = gt_source[t]; if (curinc) incdepth++;
+ module_name = gt_module[t]; module_imports = gt_imports[t];
ginst = 1; gng = gt_np[t];
- copy_w(gnm, gt_nm + t * 8, 8);
- copy_w(gib, gi_ar + k * 8, 8);
+ rt.copy_w(gnm, gt_nm + t * 8, 8);
+ rt.copy_w(gib, gi_ar + k * 8, 8);
ginsn = sname[gi_sym[k]];
src_push(1, gt_tok[t], 0, 0);
next();
topitem0();
if (tk != T_EOF) die("unexpected tokens after a generic function");
src_pop();
- ginst = 0; gng = 0;
+ ginst = 0; gng = 0; module_name = oldmodule; module_imports = oldimports; curinc = oldsource; incdepth = olddepth;
return 0;
}
// compile the instances asked for so far whose templates have been read, and
@@ -7522,25 +9267,30 @@ int gdrain() {
int topitem() {
int sb = gbuf, sn = gn, sc = gcap, sr = grec, sg = gnew, rb, rn;
if (ginst) return topitem0();
- gbuf = new(256); gcap = 256; gn = 0; grec = 1; gnew = 0 - 1;
+ gbuf = rt.new(256); gcap = 256; gn = 0; grec = 1; gnew = 0 - 1;
grec_add();
topitem0();
rb = gbuf; rn = gn;
grec = sr; gbuf = sb; gn = sn; gcap = sc;
- if (gnew >= 0) gattach(rb, rn); else free(rb);
+ if (gnew >= 0) gattach(rb, rn); else rt.free(rb);
gnew = sg;
gdrain();
+ if (chk_hook && cmode && isp == 0 && !chkabort) chk_hook();
return 0;
}
// a unit is compiled item by item; unit_end then reports any function that
// was called but never defined, and stops if any error was found
int unit_top() {
- while (tk != T_EOF) topitem();
+ if (effect_floor < ubase) effect_floor = ubase;
+ while (tk != T_EOF) {
+ if (probe_off >= 0 && incdepth == 0 && toff > probe_off) { probe_n = 0; probe_has = 0; chkabort = 1; break; }
+ topitem();
+ }
return 0;
}
// the edit distance between two names (levenshtein), or 99 when one is too long
word editdist(word a, word b) {
- int la = len_w(a), lb = len_w(b), i, j, x, y, z, last, row[65];
+ int la = rt.len_w(a), lb = rt.len_w(b), i, j, x, y, z, last, row[65];
if (la > 60 || lb > 60) return 99;
j = 0; while (j <= lb) { row[j] = j; j++; }
i = 1;
@@ -7563,7 +9313,7 @@ word editdist(word a, word b) {
// the defined function, global or constant whose name is closest to n, if it
// is close enough to be a likely typo; 0 when none is
int similar(int n) {
- int j = 0, best = 0, bd = 99, dist, k, lim = len_w(n) / 3;
+ int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3;
if (lim < 1) lim = 1;
if (lim > 3) lim = 3;
while (j < nsym) {
@@ -7584,6 +9334,7 @@ int similar(int n) {
// end a compile unit from token from onward, and report what it defined.
int unit_end(int from) {
int i = from, best;
+ effect_finish(from);
panic = 0;
while (i < nsym) {
// a bodyless declaration (fn f();) promises a definition elsewhere. in check mode that
@@ -7593,8 +9344,8 @@ int unit_end(int from) {
best = similar(sname[i]);
if (!dry) {
errat();
- put_s(sname[i]); put_s(" is used but never defined");
- if (best) { put_s("; did you mean '"); put_s(best); put_s("'?"); }
+ rt.put_s(sname[i]); rt.put_s(" is used but never defined");
+ if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
errend();
}
}
@@ -7609,9 +9360,9 @@ int unit_end(int from) {
// (this is what eval, compile and load are built on. a function body made this
// way is emitted behind a jump when another function is being compiled, so
// compile() works inside a parse word.)
-unchecked int compile_list(int q, int top) {
+int compile_list_context(int q, int top, char* module, word imports) {
int hop = -1, entry = 0, from = nsym;
- ctx_push(); ifcur = 0; ifcall = -1;
+ ctx_push(); ifcur = 0; ifcall = -1; effect_floor = from; module_name = module; module_imports = imports;
if (infn) { hop = hole(); op(O_JMP); }
if (top) prescan(1, q);
src_push(1, q, 0, 0);
@@ -7620,7 +9371,7 @@ unchecked int compile_list(int q, int top) {
else {
entry = here;
fn_begin(1, 0);
- prologue();
+ prologue(); borrow_prologue(); borrow_begin();
while (tk != T_EOF) stmt();
unused_check(lbase);
if (fchk && live) exit_vars(1, 1);
@@ -7633,6 +9384,50 @@ unchecked int compile_list(int q, int top) {
ctx_pop();
return entry;
}
+int compile_list(int q, int top) { return compile_list_context(q, top, module_name, module_imports); }
+// return an independent copy of a retained function body, excluding its braces.
+word*.u function_tokens(int addr) {
+ int s = 0, open, close, count; word meta, source, result;
+ while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++;
+ if (s >= nsym) return 0;
+ meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; count = close - open - 1;
+ result = rt.new(1 + count * 3); result[0] = count;
+ if (count) rt.copy_w(result + 1, source + 1 + (open + 1) * 3, count * 3);
+ return result;
+}
+// compile a changed body under a fresh name, check its signature, then redirect
+// the original entry so existing calls and function values use the new code.
+int replace_function(int addr, word* body_tokens) {
+ int s = 0, open, close, namepos, olderr = nerr, oldpanic = panic, oldabort = chkabort;
+ int errors, generated_id, new_symbol, count, i = 0, out = 0, cap, diag, seq, digits = 0, published;
+ char[40] generated; char[24] reversed; word meta, source, full, imports; char* module;
+ while (s < nsym && !(skind[s] == K_FN && sval[s] == addr && fnmeta[s])) s++;
+ if (s >= nsym || !body_tokens || body_tokens[0] < 0) return 0;
+ meta = fnmeta[s]; source = meta[0]; open = meta[4]; close = meta[5]; namepos = meta[3]; module = meta[1]; imports = meta[2];
+ count = source[0] - (close - open - 1) + body_tokens[0];
+ full = rt.new(1 + count * 3); full[0] = count;
+ i = 0; while (i <= open) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; }
+ i = 0; while (i < body_tokens[0]) { rt.copy_w(full + 1 + out * 3, body_tokens + 1 + i * 3, 3); i++; out++; }
+ i = close; while (i < source[0]) { rt.copy_w(full + 1 + out * 3, source + 1 + i * 3, 3); i++; out++; }
+ seq = fnpatchseq; fnpatchseq++;
+ rt.copy_w(generated, "__run_patch_", 12); generated[12] = 0;
+ if (!seq) { reversed[digits] = '0'; digits++; }
+ else { while (seq > 0) { reversed[digits] = '0' + seq % 10; digits++; seq = seq / 10; } }
+ i = 0; while (i < digits) { generated[12 + i] = reversed[digits - i - 1]; i++; }
+ generated[12 + digits] = 0; generated_id = intern(generated); full[2 + namepos * 3] = generated_id;
+ cap = 4096; diag = rt.new(cap); chk_begin(diag, cap);
+ compile_list_context(full, 1, module, imports);
+ errors = nerr || chkabort || panic;
+ chk_end(); nerr = olderr; panic = oldpanic; chkabort = oldabort;
+ if (module) new_symbol = lookup_raw(methname(module, generated_id)); else new_symbol = lookup_raw(generated_id);
+ if (!errors && new_symbol >= 0 && skind[new_symbol] == K_FN && (ssig[new_symbol] == ssig[s]) && (stype[new_symbol] == stype[s])) {
+ setlit(addr, sval[new_symbol]); *(addr + 1 + hdelta) = O_JMP + O_JMP;
+ // subsequent transformations start from the body that was published.
+ if (fnmeta[new_symbol]) fnmeta[s] = fnmeta[new_symbol];
+ published = 1;
+ } else published = 0;
+ rt.free(diag); rt.free(full); return published;
+}
// compile a token list into a function and return its address.
int compile(int q) { return compile_list(q, 0); }
// compile a token list and run its top-level code.
@@ -7640,13 +9435,14 @@ int load(int q) { compile_list(q, 1); return 0; }
// compile a token list and run it, returning its result.
int eval(int q) { int f = compile_list(q, 0); return f(); }
// compile the source string s and run it, returning its result.
-int evals(int s) {
+int evals(char* s) {
word q = tokens(s); int r = eval(q);
- free(d*q);
+ rt.free(d*q);
return r;
}
-// ---- symbol blob ----
+//: ---- symbol blob ----
+
// an image ends with its own symbol table so the genome knows its names:
// kind, value, signature, name chars, 0 ... then a 0 kind.
// emit the names defined since symbol index from (functions, globals,
@@ -7655,7 +9451,7 @@ int blob_emit(int from) {
int i = from, table, words = 1; word p; int b, k;
while (i < nsym) {
if (skind[i] == K_DOC) { b = sval[i]; words = words + 2 + (b[0] >> 3); }
- if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) words = words + 4 + len_w(sname[i]);
+ if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) words = words + 4 + rt.len_w(sname[i]);
i++;
}
reserve_code(words); i = from;
@@ -7693,19 +9489,19 @@ int blob_emit(int from) {
// cut is by name, the functions after chk_mark, and everything from the
// server's marker const (LSP_SERVER, the first thing src/lsp/server.zc declares)
int blob_load(word p, int hide) {
- int s, n, past = 0, after = 0, skip, mkn = intern("chk_mark"), srvn = intern("LSP_SERVER");
+ int s, n, past = 0, after = 0, skip, mkn = intern("run.chk_mark"), srvn = intern("lsp.LSP_SERVER");
while (*p) {
n = intern(p + 3);
if (hide && n == srvn) after = 1;
- skip = hide && (after || (past && p[0] == K_FN));
+ skip = 0;
if (hide && n == mkn) past = 1;
if (!skip) {
s = addsym(n, p[0], p[1]);
ssig[s] = p[2];
sflag[s] = F_DEF | F_SIG;
- if (p[0] == K_ENH) { if (p[1] >= nenh) nenh = p[1] + 1; install(p[2] >> 8); }
+ if (p[0] == K_ENH) { if (p[1] >= nenh) nenh = p[1] + 1; rt.install(p[2] >> 8); }
}
- p = p + 3 + len_w(p + 3) + 1;
+ p = p + 3 + rt.len_w(p + 3) + 1;
}
return 0;
}
@@ -7720,7 +9516,7 @@ int build_image_grow(int org, int initial) {
image_buffer = chk_resize(0, 0, initial); image_active = 1; image_org = org; image_capacity = initial;
ubase = nsym; vislo = nsym; nenh = 0;
here = org; hdelta = image_buffer - org; hlimit = org + initial;
- lit(FTOP); lit(ZFP); op(O_ST); fnref(fwdfn(s_start)); op(O_JMP); emit(0); emit(0);
+ lit(rt.FTOP); lit(rt.ZFP); op(O_ST); fnref(fwdfn(s_start)); op(O_JMP); emit(0); emit(0);
unit_stdin();
n = blob_emit(ubase);
image_buffer[5] = n;
@@ -7734,28 +9530,29 @@ int build_image_grow(int org, int initial) {
// retain the explicit destination interface for callers that already own storage.
// use build_image_grow and image_words when the output must grow independently.
int build_image(int org, int dst) {
- int n = build_image_grow(org, 4096), words = syscall(9, 0, 0, 0);
+ int n = build_image_grow(org, 4096), words = rt.syscall(9, 0, 0, 0);
if (dst < 0 || n > words - dst) fatal("image destination too small");
- copy_w(dst, image_words, n);
+ rt.copy_w(dst, image_words, n);
return n;
}
-// ---- setup ----
+//: ---- setup ----
+
// the collector's roots for the compiler's half of the memory map: the program
// area (globals live between functions), the genome's globals, the input source
// frames, the live frame stack and the live data stack. rt.zc declares this
// and calls it from gc().
int gc_roots() {
- int end = ptop; if (end > CODE_END) end = CODE_END;
- markrange(0, end);
- if (!hdelta && here > ptop && ptop < CODE_END) { end = here; if (end > CODE_END) end = CODE_END; markrange(ptop, end); } // globals of a unit still compiling
- markrange(GORG, GORG[6]);
- markrange(R_IH, R_POOLEND);
- markrange(sval, sval + nsym);
- markrange(R_TY, R_TYEND);
- markrange(R_SRC, R_SRC + R_SRC_cap);
- gc_frames();
- markrange(dsp(), data_top);
+ int end = ptop; if (end > rt.CODE_END) end = rt.CODE_END;
+ rt.markrange(0, end);
+ if (!hdelta && here > ptop && ptop < rt.CODE_END) { end = here; if (end > rt.CODE_END) end = rt.CODE_END; rt.markrange(ptop, end); } // globals of a unit still compiling
+ rt.markrange(rt.GORG, rt.GORG[6]);
+ rt.markrange(R_IH, R_POOLEND);
+ rt.markrange(sval, sval + nsym);
+ rt.markrange(R_TY, R_TYEND);
+ rt.markrange(R_SRC, R_SRC + R_SRC_cap);
+ rt.gc_frames();
+ rt.markrange(rt.dsp(), rt.data_top);
return 0;
}
// start-up in two parts. zc_init clears the intern table and interns the
@@ -7764,11 +9561,13 @@ int gc_roots() {
// the vm's own one-letter mnemonics (see seqasm), so "<=" is "~ < 1 ^": swap,
// less-than, flip the bit.
int zc_init() {
+ receiver_methods = 0; closure_expected = 0;
+ effect_floor = 0; effect_symbols = 0; effect_edges = 0; effect_requirements = 0; effect_current = 0;
pooltop = R_POOL;
poolend = R_POOLEND; poolblocks = 0; namecount = 0;
- fill_w(namehash, 0, hashcap);
+ rt.fill_w(namehash, 0, hashcap);
nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0;
- lsp = 0; ctxsp = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; fill_w(lkeys, 0, lkeycap); lkeyn = 0;
+ lsp = 0; ctxsp = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0;
s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}");
s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";");
s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--");
@@ -7787,20 +9586,20 @@ int zc_init() {
s_fn = intern("fn"); s_var = intern("let"); s_const = intern("const"); s_else = intern("else");
s_as = intern("as"); s_unchecked = intern("unchecked"); s_immediate = intern("immediate");
s_m = intern("m"); s_d = intern("d"); s_u = intern("u"); s_static = intern("static");
- s_start = intern("start");
+ s_start = intern("run.start");
s_main = intern("main");
- s_free = intern("free");
- s_recycle = intern("recycle");
+ s_free = intern("rt.free");
+ s_recycle = intern("rt.recycle");
s_do = intern("do");
s_while = intern("while");
s_enum = intern("enum");
s_include = intern("include");
- s_memdebug = intern("memdebug"); s_scrub = intern("scrub"); s_alloc = intern("alloc_line");
- s_check = intern("checkptr"); s_memset = intern("mem_set");
- s_own = intern("own"); s_qnew = intern("qnew"); s_fovf = intern("frame_overflow");
- s_frame_floor = intern("frame_floor"); s_frame_top = intern("frame_top"); s_frame_node = intern("frame_node");
- s_frame_busy = intern("frame_busy"); s_frame_guard = intern("frame_guard");
- s_frame_grow = intern("frame_grow"); s_frame_leave = intern("frame_leave");
+ s_memdebug = intern("memdebug"); s_scrub = intern("rt.scrub"); s_alloc = intern("rt.alloc_line");
+ s_check = intern("rt.checkptr"); s_memset = intern("rt.mem_set");
+ s_own = intern("rt.own"); s_qnew = intern("run.qnew"); s_fovf = intern("rt.frame_overflow");
+ s_frame_floor = intern("rt.frame_floor"); s_frame_top = intern("rt.frame_top"); s_frame_node = intern("rt.frame_node");
+ s_frame_busy = intern("rt.frame_busy"); s_frame_guard = intern("rt.frame_guard");
+ s_frame_grow = intern("rt.frame_grow"); s_frame_leave = intern("rt.frame_leave");
s_enhance = intern("enhance"); s_micro = intern("micro"); s_vm = intern("vm");
return 0;
}
@@ -7838,8 +9637,13 @@ int zc_tables() {
add_kw("for", kw_for, F_STMT);
add_kw("do", kw_do, F_STMT);
add_kw("enum", kw_enum, F_STMT | F_TOP);
- add_kw("include", kw_include, F_TOP);
+ add_kw("include", kw_include, F_TOP | F_STMT);
+ add_kw("run.method", kw_method, F_TOP | F_STMT);
+ add_kw("run.share_value", kw_share_value, F_EXPR);
+ add_kw("run.syntax", kw_syntax, F_TOP | F_STMT);
+ add_kw("run.statement_hook", kw_statement_hook, F_TOP | F_STMT);
add_kw("memdebug", kw_memdebug, F_STMT | F_TOP);
+ add_kw("hints", kw_hints, F_STMT | F_TOP);
add_kw("switch", kw_switch, F_STMT);
add_kw("break", kw_break, F_STMT);
add_kw("continue", kw_continue, F_STMT);
@@ -7859,33 +9663,33 @@ int zc_tables() {
add_kw("resize", kw_resize, F_EXPR);
return 0;
}
-// the default map's type records sit in the last 16384 words of the heap.
-// the type list is the first words of that range, so it cannot be kept aside
-// as a buddy block: types_init would overwrite the header and the allocator
-// would hand the range out again. cut the heap off at R_TY until the unit
-// has been compiled, then put the tail back for the program.
+//: the default map's type records sit in the last 16384 words of the heap.
+//: the type list is the first words of that range, so it cannot be kept aside
+//: as a buddy block: types_init would overwrite the header and the allocator
+//: would hand the range out again. cut the heap off at R_TY until the unit
+//: has been compiled, then put the tail back for the program.
int heldk;
// the size class of the type area, for the memory map.
int type_class() {
int k = 0, n = R_TYEND - R_TY;
- while (k < 31 && pow2[k] < n) k++;
- if (pow2[k] != n) return 0 - 1;
+ while (k < 31 && rt.pow2[k] < n) k++;
+ if (rt.pow2[k] != n) return 0 - 1;
return k;
}
// keep the type area out of the collector's reach while it is in use.
int shield_types() {
int k;
heldk = 0 - 1;
- if (R_TY <= HEAP_LO || R_TY >= HEAP_HI || R_TYEND > HEAP_HI) return 0;
+ if (R_TY <= rt.HEAP_LO || R_TY >= rt.HEAP_HI || R_TYEND > rt.HEAP_HI) return 0;
k = type_class();
if (k < 0) return 0;
- if (R_TY + pow2[k] != R_TYEND) return 0;
- if (heap_reset(R_TY)) heldk = k;
+ if (R_TY + rt.pow2[k] != R_TYEND) return 0;
+ if (rt.heap_reset(R_TY)) heldk = k;
return 0;
}
// let the collector see the type area again.
int unshield_types() {
- if (heldk >= 0) { heap_uncut(R_TY, heldk); heldk = 0 - 1; }
+ if (heldk >= 0) { rt.heap_uncut(R_TY, heldk); heldk = 0 - 1; }
return 0;
}
// the genome's entry point, run when the image boots: set up the heap and
@@ -7894,35 +9698,36 @@ int unshield_types() {
// call main().
int start() {
int s, f;
- heap_init(); chk_heap = 1;
+ rt.heap_init(); chk_heap = 1;
shield_types();
zc_init();
- blob_load(GORG[5], 0);
+ blob_load(rt.GORG[5], 0);
zc_tables();
types_init();
- here = 0; hdelta = 0; hlimit = CODE_END; ptop = 0;
+ here = 0; hdelta = 0; hlimit = rt.CODE_END; ptop = 0;
ubase = nsym; vislo = 0;
// a genome that carries the language server runs it instead of compiling stdin
- s = lookup(intern("lsp_main"));
- if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); halt(); }
+ s = lookup(intern("lsp.lsp_main"));
+ if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); rt.halt(); }
unit_stdin();
if (here > ptop) ptop = here;
- scrub(); // compilation's stale words would keep quarantined blocks alive
- gc();
+ rt.scrub(); // compilation's stale words would keep quarantined blocks alive
+ rt.gc();
// the type area stays out of the allocator. main() may compile again
// (the self-host does) and a later new() would otherwise reuse it.
- if (mdbg_flags) mem_set(mdbg_flags);
+ if (mdbg_flags) rt.mem_set(mdbg_flags);
// a unit that carries src/lib/std.zc has a libinit to fill argc/argv/envc/envp
// with. it runs before main, the same way lsp_main runs instead of stdin.
- s = lookup(intern("libinit"));
+ s = lookup(intern("std.libinit"));
if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); }
s = lookup(s_main);
if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); }
- halt();
+ rt.halt();
return 0;
}
-// ---- check mode: the language server ----
+//: ---- check mode: the language server ----
+
// the server runs this compiler, unchanged, over a document it holds in
// memory. chk_unit compiles it as a whole unit, in a machine set up the way
// start() sets one up (the genome's own names, the default operators and
@@ -7935,9 +9740,11 @@ int start() {
int chk_mark() { return 0; }
// reset check mode for a new document.
int chk_reset() {
- code_blocks = 0; code_chunk = CODE_END;
+ statement_hooks = 0; included_modules = 0;
+ if_local = 0; if_edges = 0; if_value = 0;
+ rt.code_blocks = 0; code_chunk = rt.CODE_END;
isp = -1;
- docs_on = 0; dn = 0; dline = 0; docpending = 0; dsn = 0; dnm = 0;
+ docs_on = 1; dn = 0; dline = 0; docpending = 0; dsect = 0; dsn = 0; dnm = 0;
pq_n = 0;
pendot = 0;
qdone = 0;
@@ -7958,7 +9765,8 @@ int chk_reset() {
bxdepth = 0;
epichain = 0;
fframe = 0;
- xbor = 0 - 1; xaf = 0; argfact = 0;
+ xbor = 0 - 1; xaf = 0; argfact = 0; xvk = 0;
+ if (svkind) rt.fill_w(svkind, 0, symcap);
mdbg = 0; mdbg_flags = 0; lv = 0; lvk = 0; lvi = 0; kw_expr = 0; xcls = 0; fty = 0;
check_on = 1; tc_on = ifstrict; dry = 0; ntp = 0; tvauto = 0; ngbind = 0; xgive = 0; xconst = 0;
o3on = 0; o3nxt = 0; no3pend = 0; no3sl = 0; no3free = 0; o3nreg = 0; o3na = 0; o3fn0 = 0;
@@ -7974,20 +9782,22 @@ int chk_reset() {
// check one document in check mode and fill the records it made.
int chk_unit(int src, int buf, int cap, int sets, int lim) {
chk_reset();
+ module_name = 0; module_imports = 0;
chk_begin(buf, cap);
lsets = sets;
- if (sets) fill_w(sets, 0, 4 * LSW);
- if (!chk_heap) { heap_init(); chk_heap = 1; } else gc(); // the caller's blocks are reachable from its globals, the rest goes
- rsn = 0; rsbase = src;
- if (!rsrec) { rscap = RS_CAP; rsrec = new(RS_CAP * RS_W); rstmp = new(RS_CAP * RS_W); }
+ if (sets) rt.fill_w(sets, 0, 4 * LSW);
+ if (!chk_heap) { rt.heap_init(); chk_heap = 1; } else rt.gc(); // the caller's blocks are reachable from its globals, the rest goes
+ rsn = 0; evn = 0; hl_n = 0; literal_doc_n = 0; if (literal_docs) rt.fill_w(literal_docs, 0, literal_doc_cap); curinc = 0; std_done = 0; icon_done = 0; incdepth = 0; dgdrop = 0; probe_has = 0; probe_n = 0; probe_mem = 0; probe_static = 0; nameoff = 0 - 1; rsbase = src;
+ if (!rsrec) { rscap = RS_CAP; rsrec = rt.new(RS_CAP * RS_W); rstmp = rt.new(RS_CAP * RS_W); }
ifs = 0; ifx = 0; nifx = 0; nift = 0; nif = 0; ifcur = 0; ifkk = 0; ifgk = 0; ifcall = 0 - 1;
if (ifon) {
ifrcap = NIF; ifxcap = IFN; iftcap = IFT;
- ifs = new(symcap); ifr = chk_resize(0, 0, ifrcap); ifx = chk_resize(0, 0, ifxcap * 4); ift = chk_resize(0, 0, iftcap);
- fill_w(ifs, 0, symcap);
+ ifs = rt.new(symcap); ifr = chk_resize(0, 0, ifrcap); ifx = chk_resize(0, 0, ifxcap * 4); ift = chk_resize(0, 0, iftcap);
+ if_local = chk_resize(0, 0, loccap); if_edges = 0; if_value = 0;
+ rt.fill_w(ifs, 0, symcap);
}
zc_init();
- blob_load(GORG[5], 1);
+ blob_load(rt.GORG[5], 1);
zc_tables();
types_init();
here = 0; hdelta = 0; hlimit = lim; ptop = 0;
@@ -8025,3 +9835,31 @@ int hl_end() {
pq_n = 0;
return 0;
}
+// ---- embedded std library (generated by src/tools/embed.sh from src/lib/std.zc; do not edit) ----
+bstr std_part(int i) {
+ if (i == 0) return b"namespace std;\ninclude rt, run;\n\n// small helpers for strings, files, the clock and byte buffers. not part of the\n// genome as declarations; its source is embedded. include it with: include std;\n// syscall numbers (SYS_OPEN and the rest) live in the runtime.\n// a character string has type char*, one character per word and a 0 at the end.\n// the Bytes group takes packed byte strings (b\"...\"), whose\n// type is bstr.\n// the helpers are grouped in structs, and each is a static function, so it is\n// called as std.Group.name(args): std.Text.itoa(n, buf), std.Host.now(),\n// std.Buf.get(b, 0).\n// the constructors arena(n) and bnew(n) and the argv helpers are plain\n// functions, because an owned result has to carry its .u tag.\n\n//: ---- the command line and the environment ----\n\n//: argc and argv, envc and envp, as passed to main by c. a zc string is\n//: one character per word, so an array of them is char*[?]. libinit() fills\n//: these and the runtime calls it before main, if the program has one (an\n//: image built by build_image, or a check-mode document, does not).\n\nint argc;\nchar*[?] argv;\nint envc;\nchar*[?] envp;\n\n// one entry of a vector syscall, as a fresh string, or 0 past the end.\n// op: the syscall that fetches an entry (SYS_ARG or SYS_ENV)\n// i: which entry\n// returns: the entry, owned by the caller; 0 when there is no entry i\nchar*.u argstr(int op, int i) {\n\tint n = rt.syscall(op, i, 0, 0);\n\tchar* s;\n\tif (n < 0) return 0;\n\ts = rt.new(n + 1);\n\trt.syscall(op, i, s, n + 1);\n\treturn s;\n}\n\n// every entry of a vector syscall, in order.\n// cntop: the syscall that counts the entries (SYS_ARGC or SYS_ENVC)\n// stop: the syscall that fetches one (SYS_ARG or SYS_ENV)\n// returns: a new array of the strings, owned by the caller\nchar*[?].u argsvec(int cntop, int stop) {\n\tint n = rt.syscall(cntop, 0, 0, 0), i = 0;\n\tchar*[?] a = rt.new(char*[?], 0);\n\twhile (i < n) { push(a, argstr(stop, i)); i++; }\n\treturn a;\n}\n\n// reads the command line and the environment into argc, argv, envc and envp.\n// it stays a plain function: the compiler looks it up by name.\n// returns: 0\nint libinit() {\n\targv = argsvec(rt.SYS_ARGC, rt.SYS_ARG);\n\tenvp = argsvec(rt.SYS_ENVC, rt.SYS_ENV);\n\targc = len(argv); envc = len(envp);\n\treturn 0;\n}\n\n//: ---- capturing output ----\n\n// capture.start() redirects everything the program writes into a string and\n// returns it. writes go to the buffer instead of the terminal until end()\n// stops it, so the buffer is a string once end() has run:\n// let buf = capture.start();\n// put_s(\"hello world!\"); // nothing on the terminal\n// capture.end(buf);\n// put_s(buf); // hello world\n// one capture at a time, and the buffer belongs to whoever holds it.\n\nstruct capture {\n\t// redirects program output into a collector-managed character buffer.\n\t// returns the buffer; call end(buffer) before reading its terminator.\n\t// returns: the buffer used for this capture\n\tstatic char* start() {\n\t\tint cap = 256;\n\t\trt.outmode = 2; rt.outn =";
+ if (i == 1) return b" 0; rt.outcap = cap;\n\t\trt.outbuf = rt.new(cap);\n\t\treturn rt.outbuf;\n\t}\n\t// stops output capture, terminates the buffer, and returns the collected text.\n\t// buffer: the buffer returned by start\n\t// returns: the same buffer, now terminated\n\tstatic char* end(char* buffer) {\n\t\trt.outbuf[rt.outn] = 0;\n\t\trt.outmode = 0; rt.outbuf = 0; rt.outn = 0; rt.outcap = 0;\n\t\treturn buffer;\n\t}\n}\n\n//: ---- character strings ----\n\nstruct Text {\n\t// the number written in decimal at the start of s. a leading '-' makes it\n\t// negative and the first character that is not a digit ends it.\n\t// s: the string to read\n\t// returns: the number, 0 when s starts with no digit\n\tstatic int atoi(char* s) {\n\t\tint n = 0, i = 0, sign = 1;\n\t\tif (s[0] == '-') { sign = 0 - 1; i = 1; }\n\t\twhile (s[i] >= '0' && s[i] <= '9') { n = n * 10 + s[i] - '0'; i++; }\n\t\treturn n * sign;\n\t}\n\n\t// writes n in decimal into dst, with a 0 after it.\n\t// n: the number\n\t// dst: where the text goes; 24 words is always enough\n\t// returns: how many characters were written, not counting the 0\n\tstatic int itoa(int n, char* dst) {\n\t\tint b[24], i = 0, j = 0, neg = 0;\n\t\tif (!n) { dst[0] = '0'; dst[1] = 0; return 1; }\n\t\tif (n < 0) { neg = 1; n = 0 - n; }\n\t\tif (n < 0) { rt.copy_w(dst, \"-9223372036854775808\", 21); return 20; }\n\t\twhile (n) { b[i] = '0' + n % 10; i++; n = n / 10; }\n\t\tif (neg) { dst[0] = '-'; j = 1; }\n\t\twhile (i) { i--; dst[j] = b[i]; j++; }\n\t\tdst[j] = 0;\n\t\treturn j;\n\t}\n\n\t// joins two strings.\n\t// dst: where the result goes, with room for both and a 0; not a or b\n\t// a: the first string\n\t// b: the second string\n\t// returns: the length of the result\n\tstatic int cat(char* dst, char* a, char* b) {\n\t\tint i = 0, j = 0;\n\t\twhile (a[i]) { dst[j] = a[i]; i++; j++; }\n\t\ti = 0;\n\t\twhile (b[i]) { dst[j] = b[i]; i++; j++; }\n\t\tdst[j] = 0;\n\t\treturn j;\n\t}\n\n\t// does s begin with pfx?\n\t// s: the string to test\n\t// pfx: the beginning to look for\n\t// returns: 1 if it does (always, for an empty pfx), 0 if not\n\tstatic int starts(char* s, char* pfx) {\n\t\tint i = 0;\n\t\twhile (pfx[i]) { if (s[i] != pfx[i]) return 0; i++; }\n\t\treturn 1;\n\t}\n\n\t// does s end with suffix? an empty suffix always matches.\n\t// s: the string to test\n\t// suffix: the ending to look for\n\t// returns: 1 when it matches, 0 otherwise\n\tstatic int ends(char* s, char* suffix) {\n\t\tint n = 0, m = 0, i = 0;\n\t\twhile (s[n]) n++;\n\t\twhile (suffix[m]) m++;\n\t\tif (m > n) return 0;\n\t\twhile (i < m) { if (s[n - m + i] != suffix[i]) return 0; i++; }\n\t\treturn 1;\n\t}\n\n\t// how many characters come before the 0.\n\t// s: the string\n\t// returns: the length, not counting the 0\n\tstatic int len(char* s) {\n\t\tint n = 0;\n\t\twhile (s[n]) n++;\n\t\treturn n;\n\t}\n\n\t// are the two strings the same, character for character?\n\t// a, b: the strings to compare\n\t// returns: 1 if they are equal, 0 if not\n\tstatic int eq(char* a, char* b) {\n\t\tint i = 0;\n\t\twhile (a[i] && a[i] == b[i]) i++;\n\t\treturn a[i] == b[i];\n\t}\n\n\t// copies s into dst, with a 0 after it.\n\t// dst: where th";
+ if (i == 2) return b"e copy goes, with room for s and the 0\n\t// s: the string to copy\n\t// returns: the length of the copy\n\tstatic int copy(char* dst, char* s) {\n\t\tint i = 0;\n\t\twhile (s[i]) { dst[i] = s[i]; i++; }\n\t\tdst[i] = 0;\n\t\treturn i;\n\t}\n\n\t// where pat first appears in s.\n\t// s: the string to search\n\t// pat: the string to look for; an empty pat is found at 0\n\t// returns: the index of the first match, or -1 when there is none\n\tstatic int find(char* s, char* pat) {\n\t\tint i = 0, j;\n\t\tif (!pat[0]) return 0;\n\t\twhile (s[i]) {\n\t\t\tj = 0;\n\t\t\twhile (pat[j] && s[i + j] == pat[j]) j++;\n\t\t\tif (!pat[j]) return i;\n\t\t\ti++;\n\t\t}\n\t\treturn 0 - 1;\n\t}\n\n\t// changes the ASCII letters of s to lower case, in place.\n\t// s: the string to change\n\t// returns: the length of s\n\tstatic int lower(char* s) {\n\t\tint i = 0;\n\t\twhile (s[i]) {\n\t\t\tif (s[i] >= 'A' && s[i] <= 'Z') s[i] = s[i] + 32;\n\t\t\ti++;\n\t\t}\n\t\treturn i;\n\t}\n\n\t// changes the ASCII letters of s to upper case, in place.\n\t// s: the string to change\n\t// returns: the length of s\n\tstatic int upper(char* s) {\n\t\tint i = 0;\n\t\twhile (s[i]) {\n\t\t\tif (s[i] >= 'a' && s[i] <= 'z') s[i] = s[i] - 32;\n\t\t\ti++;\n\t\t}\n\t\treturn i;\n\t}\n\n\t// removes the spaces, tabs and line breaks from both ends of s, in place.\n\t// s: the string to change\n\t// returns: the length of what is left\n\tstatic int trim(char* s) {\n\t\tint n = 0, a = 0, b, i = 0, c;\n\t\twhile (s[n]) n++;\n\t\twhile (a < n && (s[a] == ' ' || s[a] == '\\t' || s[a] == '\\n' || s[a] == '\\r')) a++;\n\t\tb = n;\n\t\twhile (b > a) {\n\t\t\tc = s[b - 1];\n\t\t\tif (!(c == ' ' || c == '\\t' || c == '\\n' || c == '\\r')) break;\n\t\t\tb--;\n\t\t}\n\t\tn = b - a;\n\t\twhile (i < n) { s[i] = s[i + a]; i++; }\n\t\ts[n] = 0;\n\t\treturn n;\n\t}\n}\n\n//: ---- numbers ----\n\n// plain integer helpers. none of them can fail: an out of range result wraps\n// the same way the arithmetic does.\n\nstruct Num {\n\t// the absolute value of n, using wrapping word arithmetic. the minimum\n\t// word remains negative because its positive value cannot fit in a word.\n\t// n: the value\n\t// returns: its absolute value, or the minimum word\n\tstatic int abs(int n) {\n\t\tif (n < 0) return 0 - n;\n\t\treturn n;\n\t}\n\n\t// the smaller of a and b.\n\t// a, b: the values to compare\n\t// returns: the smaller value (a when they are equal)\n\tstatic int min(int a, int b) {\n\t\tif (a < b) return a;\n\t\treturn b;\n\t}\n\n\t// the larger of a and b.\n\t// a, b: the values to compare\n\t// returns: the larger value (a when they are equal)\n\tstatic int max(int a, int b) {\n\t\tif (a > b) return a;\n\t\treturn b;\n\t}\n\n\t// v, kept between lo and hi.\n\t// v: the value\n\t// lo, hi: the bounds, lo first; lo must not exceed hi\n\t// returns: v clamped to the bounds\n\tstatic int clamp(int v, int lo, int hi) {\n\t\tif (v < lo) return lo;\n\t\tif (v > hi) return hi;\n\t\treturn v;\n\t}\n\n\t// the greatest common divisor of a and b, computed with word arithmetic.\n\t// a, b: the values\n\t// returns: their greatest common divisor\n\tstatic int gcd(int a, int b) {\n\t\tif (a < 0) a = 0 - a;\n\t\tif (b < 0) b = 0 - b;\n\t\tint t;\n\t\twhile (b) { t ";
+ if (i == 3) return b"= a % b; a = b; b = t; }\n\t\treturn a;\n\t}\n\n\t// b raised to the power e. e below 0 gives 1; the result wraps as a word.\n\t// b: the base\n\t// e: the exponent\n\t// returns: b to the power e, or 1 when e is negative\n\tstatic int pow(int b, int e) {\n\t\tint r = 1;\n\t\twhile (e > 0) { r = r * b; e--; }\n\t\treturn r;\n\t}\n\n\t// the whole square root of n, rounded down. n below 0 gives 0.\n\t// n: the value\n\t// returns: the greatest integer whose square is at most n, or 0 for n <= 0\n\tstatic int isqrt(int n) {\n\t\tint lo = 0, hi = 3037000499, mid;\n\t\tif (n <= 0) return 0;\n\t\twhile (lo < hi) {\n\t\t\tmid = lo + (hi - lo + 1) / 2;\n\t\t\tif (mid <= n / mid) lo = mid; else hi = mid - 1;\n\t\t}\n\t\treturn lo;\n\t}\n\n\t// -1, 0 or 1 according to the sign of n.\n\t// n: the value\n\t// returns: -1 when n is negative, 0 when it is zero, and 1 when it is positive\n\tstatic int sign(int n) {\n\t\tif (n < 0) return 0 - 1;\n\t\tif (n > 0) return 1;\n\t\treturn 0;\n\t}\n}\n\n//: ---- the host ----\n\n//: the argument block for Host.raw: the number, then its six arguments.\nint rawblk[7];\n\nstruct Host {\n\t// calls any linux system call on the host. this is the escape hatch below\n\t// the named helpers: the number is the kernel's, so the meaning of each\n\t// argument depends on the call. a pointer argument is an address in the\n\t// program's memory, and only the host reads it, so a string or buffer has\n\t// to be passed as its address.\n\t// it runs the call on the real machine, not inside the vm, so a program\n\t// that uses it can do whatever the process can do. only use it where that\n\t// is wanted.\n\t// nr: the syscall number (x86-64 and arm64 numbers differ, so check the host)\n\t// a..f: the six arguments, unused ones 0\n\t// returns: the kernel's result, -errno on failure, or -1 on unsupported hosts\n\tstatic int raw(int nr, int a, int b, int c, int d, int e, int f) {\n\t\trawblk[0] = nr; rawblk[1] = a; rawblk[2] = b; rawblk[3] = c;\n\t\trawblk[4] = d; rawblk[5] = e; rawblk[6] = f;\n\t\treturn rt.syscall(rt.SYS_RAW, rawblk, 0, 0);\n\t}\n\n\t// the value of an environment variable.\n\t// name: the variable's name\n\t// dst: where the value goes, a zero-terminated character string\n\t// n: how many words dst has\n\t// returns: the length of the value, -1 if the variable is not set\n\tstatic int env(char* name, char* dst, int n) { return rt.syscall(rt.SYS_GETENV, name, dst, n); }\n\n\t// the clock.\n\t// returns: the time in seconds since 1970\n\tstatic int now() { return rt.syscall(rt.SYS_TIME, 0, 0, 0); }\n\n\t// writes a string to a file, replacing what was there.\n\t// path: the file's name\n\t// s: the text to write\n\t// returns: how many bytes were written, -1 if the file could not be opened\n\tstatic int writefile(char* path, char* s) {\n\t\tint fd = rt.syscall(rt.SYS_OPEN, path, 1, 0), n;\n\t\tif (fd < 0) return -1;\n\t\tn = rt.syscall(rt.SYS_WRITE, fd, s, rt.len_w(s));\n\t\trt.syscall(rt.SYS_CLOSE, fd, 0, 0);\n\t\treturn n;\n\t}\n}\n\n//: ---- byte strings ----\n\n// the same helpers for packed byte strings. character-string helpers above are unchanged.\n// a";
+ if (i == 4) return b" packed byte string has type bstr; len_s reads its stored byte length.\n\nstruct Bytes {\n\t// reads a decimal number at the start of s; a leading '-' makes it\n\t// negative, and the first non-digit ends the number.\n\t// s: the byte string\n\t// returns: the number, or 0 when s starts with no digit\n\tstatic int atoi(bstr s) { return rt.s_to_n(s); }\n\n\t// like Text.itoa, but the text is made fresh.\n\t// n: the number\n\t// returns: a new byte string\n\tstatic bstr itoa(int n) { return rt.n_to_s(n); }\n\n\t// like Text.starts, for byte strings.\n\t// s: the byte string to test\n\t// pfx: the beginning to look for\n\t// returns: 1 if s begins with pfx, 0 if not\n\tstatic int starts(bstr s, bstr pfx) {\n\t\tint i = 0, n = rt.len_s(s), m = rt.len_s(pfx);\n\t\tif (m > n) return 0;\n\t\twhile (i < m) { if (rt.at_s(s, i) != rt.at_s(pfx, i)) return 0; i++; }\n\t\treturn 1;\n\t}\n\n\t// the number of bytes in a packed byte string.\n\t// s: the byte string\n\t// returns: its stored length\n\tstatic int len(bstr s) { return rt.len_s(s); }\n\n\t// are two packed byte strings equal, byte for byte.\n\t// a, b: the byte strings to compare\n\t// returns: 1 if equal, 0 otherwise\n\tstatic int eq(bstr a, bstr b) {\n\t\tint i = 0;\n\t\tif (rt.len_s(a) != rt.len_s(b)) return 0;\n\t\twhile (i < rt.len_s(a)) { if (rt.at_s(a, i) != rt.at_s(b, i)) return 0; i++; }\n\t\treturn 1;\n\t}\n\n\t// where pat first appears in s.\n\t// s: the byte string to search\n\t// pat: the byte string to find; an empty pat is found at 0\n\t// returns: its byte index, or -1 when absent\n\tstatic int find(bstr s, bstr pat) {\n\t\tint i = 0, j, n = rt.len_s(s), m = rt.len_s(pat);\n\t\tif (!m) return 0;\n\t\twhile (i + m <= n) {\n\t\t\tj = 0;\n\t\t\twhile (j < m && rt.at_s(s, i + j) == rt.at_s(pat, j)) j++;\n\t\t\tif (j == m) return i;\n\t\t\ti++;\n\t\t}\n\t\treturn 0 - 1;\n\t}\n\n\t// does s end with suffix? an empty suffix always matches.\n\t// s: the byte string to test\n\t// suffix: the ending to look for\n\t// returns: 1 when it matches, 0 otherwise\n\tstatic int ends(bstr s, bstr suffix) {\n\t\tint n = rt.len_s(s), m = rt.len_s(suffix), i = 0;\n\t\tif (m > n) return 0;\n\t\twhile (i < m) { if (rt.at_s(s, n - m + i) != rt.at_s(suffix, i)) return 0; i++; }\n\t\treturn 1;\n\t}\n\n\t// like Host.env, for byte strings.\n\t// name: the variable's name, a byte string\n\t// dst: the packed byte string whose payload receives the value\n\t// n: the maximum number of bytes to write\n\t// returns: the length of the value, -1 if the variable is not set\n\tstatic int env(bstr name, bstr dst, int n) { return rt.bgetenv(name, dst, n); }\n\n\t// like Host.writefile, for a byte string.\n\t// path: the file's name, a byte string\n\t// s: the bytes to write\n\t// returns: how many bytes were written, -1 if the file could not be opened\n\tstatic int writefile(bstr path, bstr s) {\n\t\tint fd = rt.bopen(path, 1), n;\n\t\tif (fd < 0) return -1;\n\t\tn = rt.bwrite(fd, s, rt.len_s(s));\n\t\trt.syscall(rt.SYS_CLOSE, fd, 0, 0);\n\t\treturn n;\n\t}\n}\n\n//: ---- arenas ----\n\n// a region that is freed all at once.\n// n: its capacity in words; value";
+ if (i == 5) return b"s below 1 allocate one word\n// returns: the arena, owned by the caller\nint .u arena(int n) { return rt.arena_new(n); }\n\nstruct Arena {\n\t// takes n zeroed words from an arena.\n\t// a: the arena\n\t// n: how many words\n\t// returns: the first word, or 0 when n words do not fit\n\tstatic int alloc(word a, int n) { return rt.arena_alloc(a, n); }\n\n\t// gives every block of an arena back at once. pointers returned by alloc\n\t// before the reset are no longer valid.\n\t// a: the arena\n\t// returns: 0\n\tstatic int reset(word a) { return rt.arena_reset(a); }\n}\n\n//: ---- growable byte buffers ----\n\n// a byte buffer stores its length and capacity in two words, followed by the\n// bytes, packed.\n// n: the capacity in bytes; a negative value is treated as 0\n// returns: an empty buffer with that capacity, owned by the caller\nword .u bnew(int n) {\n\tint words;\n\tword p;\n\tif (n < 0) n = 0;\n\twords = (n + 7) >> 3;\n\tp = rt.new(2 + words);\n\tp[0] = 0;\n\tp[1] = n;\n\treturn u*p;\n}\n\nstruct Buf {\n\t// how many bytes a buffer holds.\n\t// b: the buffer\n\t// returns: its length\n\tstatic int length(word b) { return b[0]; }\n\n\t// how many bytes a buffer can hold.\n\t// b: the buffer\n\t// returns: its capacity\n\tstatic int capacity(word b) { return b[1]; }\n\n\t// one byte of a buffer.\n\t// b: the buffer\n\t// i: the position\n\t// returns: the byte, 0 when i is outside the buffer\n\tstatic int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return rt.ld8(rt.addr_b(b + 2) + i); }\n\n\t// sets one byte of a buffer. setting the byte at the current length makes the\n\t// buffer one longer.\n\t// b: the buffer\n\t// i: the position, below the capacity\n\t// v: the value; only its low 8 bits are kept\n\t// returns: the byte stored, 0 when i is outside the capacity\n\tstatic int set(word b, int i, int v) {\n\t\tif (i < 0 || i >= b[1]) return 0;\n\t\trt.st8(rt.addr_b(b + 2) + i, v & 255);\n\t\tif (i >= b[0]) b[0] = i + 1;\n\t\treturn v & 255;\n\t}\n}\n\n//: array algorithms borrow their input and visit elements in index order.\n//: map and filter return independent handles. reference elements are shared\n//: with the collector, so freeing the input does not invalidate the result.\n\n// the first index equal to value, or -1 when no element matches.\nint array_find<T>(fixed T[?].m source, T value) {\n\tint length = len(source);\n\tfor (int i = 0; i < length; i++) if (source[i] == value) return i;\n\treturn -1;\n}\n// true when an element equals value.\nint array_contains<T>(fixed T[?].m source, T value) { return array_find(source, value) >= 0; }\n// the first index whose predicate is true, or -1 when none matches.\nint array_find_if<T>(fixed T[?].m source, int(T).m predicate) {\n\tint length = len(source);\n\tfor (int i = 0; i < length; i++) if (predicate(source[i])) return i;\n\treturn -1;\n}\n// true when any element satisfies predicate; false for an empty array.\nint array_any<T>(fixed T[?].m source, int(T).m predicate) { return array_find_if(source, predicate) >= 0; }\n// true when every element satisfies predicate, including an ";
+ if (i == 6) return b"empty array.\nint array_all<T>(fixed T[?].m source, int(T).m predicate) {\n\tint length = len(source);\n\tfor (int i = 0; i < length; i++) if (!predicate(source[i])) return 0;\n\treturn 1;\n}\n// the number of elements satisfying predicate.\nint array_count<T>(fixed T[?].m source, int(T).m predicate) {\n\tint length = len(source);\n\tint count = 0;\n\tfor (int i = 0; i < length; i++) if (predicate(source[i])) count++;\n\treturn count;\n}\n// apply transform once to each element and return the transformed array.\n// the result element type follows the callback's return type.\nU[?].u array_map<T,U>(fixed T[?].m source, U(T).m transform) {\n\tint length = len(source);\n\tU[?] result = rt.new(U[?]);\n\tfor (int i = 0; i < length; i++) push(result, run.share_value(transform(source[i])));\n\treturn result;\n}\n// copy elements satisfying predicate into a new array, preserving order.\nT[?].u array_filter<T>(fixed T[?].m source, int(T).m predicate) {\n\tint length = len(source);\n\tT[?] result = rt.new(T[?]);\n\tfor (int i = 0; i < length; i++) if (predicate(source[i])) push(result, run.share_value(source[i]));\n\treturn result;\n}\n// fold elements from left to right, starting with initial; an empty array\n// returns initial without calling combine.\nA array_reduce<T,A>(fixed T[?].m source, A initial, A(A,T).m combine) {\n\tint length = len(source);\n\tfor (int i = 0; i < length; i++) initial = combine(initial, source[i]);\n\treturn initial;\n}\n\nrun.method(\"find\", array_find);\nrun.method(\"contains\", array_contains);\nrun.method(\"find_if\", array_find_if);\nrun.method(\"any\", array_any);\nrun.method(\"all\", array_all);\nrun.method(\"count\", array_count);\nrun.method(\"map\", array_map);\nrun.method(\"filter\", array_filter);\nrun.method(\"reduce\", array_reduce);\n";
+ return 0;
+}
+// ---- end embedded std library ----
+// ---- embedded icon library (generated by src/tools/embed.sh from src/lib/icon.zc; do not edit) ----
+bstr icon_part(int i) {
+ if (i == 0) return b"namespace icon;\ninclude rt, run;\n\n// structured runtime transformations of compiler token lists.\n// token storage follows run.tokens: count, then kind, value and source line.\n// edits create independent lists; publication recompiles a function only after\n// delimiter validation. captures match complete balanced token ranges.\n\nint icon_error;\n\nint icon_eq(word* a, int ai, word* b, int bi, int n) {\n\tint i = 0;\n\twhile (i < n) {\n\t\tif (a[1 + (ai + i) * 3] != b[1 + (bi + i) * 3] || a[2 + (ai + i) * 3] != b[2 + (bi + i) * 3]) return 0;\n\t\ti++;\n\t}\n\treturn 1;\n}\nint icon_value(word* q, int i) { if (i < 0 || i >= q[0]) return 0; return q[2 + i * 3]; }\nint icon_kind(word* q, int i) { if (i < 0 || i >= q[0]) return 0; return q[1 + i * 3]; }\nint icon_is(word* q, int i, char* s) {\n\tint kind = run.T_OP; if (s[0] >= 'a' && s[0] <= 'z') kind = run.T_ID;\n\treturn icon_kind(q, i) == kind && icon_value(q, i) == run.intern(s);\n}\nint icon_open(int v) { return v == run.intern(\"(\") || v == run.intern(\"[\") || v == run.intern(\"{\"); }\nint icon_close(int v) { return v == run.intern(\")\") || v == run.intern(\"]\") || v == run.intern(\"}\"); }\nint icon_pair(int a, int b) {\n\treturn (a == run.intern(\"(\") && b == run.intern(\")\")) || (a == run.intern(\"[\") && b == run.intern(\"]\")) || (a == run.intern(\"{\") && b == run.intern(\"}\"));\n}\nint icon_declaration(word* q, int start, int binding) {\n\tint first = icon_value(q, start), i = start;\n\tif (icon_kind(q, start) != run.T_ID || (first != run.intern(\"let\") && first != run.intern(\"const\") && !run.istypename(first))) return 0;\n\twhile (i < binding) { if (icon_is(q, i, \"(\") || icon_is(q, i, \"=\")) return 0; i++; }\n\treturn binding > start;\n}\n// locate a simple declaration at a statement boundary, including nested blocks.\n// result records [type start, binding position, semicolon, statement start].\nint icon_locate_declaration(word* q, int id, word* result) {\n\tint start = 0, i, depth, v, binding, comma, type_start;\n\twhile (start < q[0]) {\n\t\tif (start && !icon_is(q, start - 1, \";\") && !icon_is(q, start - 1, \"{\") && !icon_is(q, start - 1, \"}\")) { start++; continue; }\n\t\ttype_start = start; if (icon_is(q, type_start, \"const\")) type_start++;\n\t\tif (icon_kind(q, type_start) != run.T_ID || (!icon_is(q, type_start, \"let\") && !run.istypename(icon_value(q, type_start)))) { start++; continue; }\n\t\ti = type_start; depth = 0; binding = -1; comma = 0;\n\t\twhile (i < q[0]) {\n\t\t\tv = icon_value(q, i);\n\t\t\tif (!depth && v == id && icon_kind(q, i) == run.T_ID && binding < 0 && icon_declaration(q, type_start, i) && (icon_is(q, i + 1, \"=\") || icon_is(q, i + 1, \";\") || icon_is(q, i + 1, \",\") || icon_is(q, i + 1, \"(\"))) binding = i;\n\t\t\tif (icon_kind(q, i) == run.T_OP) {\n\t\t\t\tif (icon_open(v)) depth++;\n\t\t\t\telse if (icon_close(v)) { if (!depth) break; depth--; }\n\t\t\t\tif (!depth && v == run.intern(\",\")) comma = 1;\n\t\t\t\tif (!depth && v == run.intern(\";\")) {\n\t\t\t\t\tif (binding >= 0) {\n\t\t\t\t\t\tif (comma || icon_is(q, binding + 1, \"(\")) return 0;\n\t\t\t\t\t\tresult[0] = type_start; result[1] = bindin";
+ if (i == 1) return b"g; result[2] = i; result[3] = start; return 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t\tstart++;\n\t}\n\treturn 0;\n}\nint icon_balanced(word* q, int a, int b) {\n\tword* stack = rt.new(b - a + 1); int n = 0, i = a, v, valid = 1;\n\twhile (i < b) {\n\t\tv = icon_value(q, i);\n\t\tif (icon_kind(q, i) == run.T_OP) {\n\t\t\tif (icon_open(v)) { stack[n] = v; n++; }\n\t\t\telse if (icon_close(v)) { if (!n || !icon_pair(stack[n - 1], v)) { valid = 0; break; } n--; }\n\t\t}\n\t\ti++;\n\t}\n\tif (n) valid = 0;\n\trt.free(stack);\n\treturn valid;\n}\nword* .u icon_slice(word* q, int a, int b) {\n\tword* r = rt.new(1 + (b - a) * 3); int i = 0; r[0] = b - a;\n\twhile (i < (b - a) * 3) { r[1 + i] = q[1 + a * 3 + i]; i++; }\n\treturn r;\n}\nword* .u icon_splice(word* q, int a, int b, word* insert) {\n\tint n = q[0] - (b - a) + insert[0], i = 0, j = 0;\n\tword* r = rt.new(1 + n * 3); r[0] = n;\n\twhile (i < a * 3) { r[1 + j] = q[1 + i]; i++; j++; }\n\ti = 0; while (i < insert[0] * 3) { r[1 + j] = insert[1 + i]; i++; j++; }\n\ti = b * 3; while (i < q[0] * 3) { r[1 + j] = q[1 + i]; i++; j++; }\n\treturn r;\n}\n// capture classes use integer literals, identifiers, expressions, statements\n// or blocks. a named capture records its exact token range for substitution.\nint icon_class(int name) {\n\tif (name == run.intern(\"int\")) return 1;\n\tif (name == run.intern(\"name\")) return 2;\n\tif (name == run.intern(\"expr\")) return 3;\n\tif (name == run.intern(\"stmt\")) return 4;\n\tif (name == run.intern(\"block\")) return 5;\n\treturn 0;\n}\nint icon_pattern(word* q) {\n\tint i = 0;\n\tif (!q || q[0] <= 0 || !icon_balanced(q, 0, q[0])) return 0;\n\twhile (i < q[0]) {\n\t\tif (icon_is(q, i, \"$\")) {\n\t\t\tif (icon_kind(q, i + 1) != run.T_ID || !icon_class(icon_value(q, i + 1))) return 0;\n\t\t\ti += 2;\n\t\t\tif (icon_is(q, i, \":\")) { if (icon_kind(q, i + 1) != run.T_ID || icon_class(icon_value(q, i + 1))) return 0; i += 2; }\n\t\t} else i++;\n\t}\n\treturn 1;\n}\nint icon_precedence(int name) {\n\tif (name == run.intern(\"||\")) return 1;\n\tif (name == run.intern(\"&&\")) return 2;\n\tif (name == run.intern(\"|\")) return 3;\n\tif (name == run.intern(\"^\")) return 4;\n\tif (name == run.intern(\"&\")) return 5;\n\tif (name == run.intern(\"==\") || name == run.intern(\"!=\")) return 6;\n\tif (name == run.intern(\"<\") || name == run.intern(\"<=\") || name == run.intern(\">\") || name == run.intern(\">=\")) return 7;\n\tif (name == run.intern(\"<<\") || name == run.intern(\">>\")) return 8;\n\tif (name == run.intern(\"+\") || name == run.intern(\"-\")) return 9;\n\tif (name == run.intern(\"*\") || name == run.intern(\"/\") || name == run.intern(\"%\")) return 10;\n\treturn 0;\n}\n// recognize expression structure before matching a variable-length capture.\n// this excludes statement keywords and incomplete operator sequences.\nint icon_expression(word* q, int start, int end, int minimum) {\n\tint i = start, v, kind, priority, next;\n\tif (i >= end) return -1;\n\tv = icon_value(q, i); kind = icon_kind(q, i);\n\tif (kind == run.T_OP && (v == run.intern(\"!\") || v == run.intern(\"~\") || v == run.intern(\"+\") || v == run.intern(\"-\") || v == run.intern(\"*";
+ if (i == 2) return b"\") || v == run.intern(\"&\"))) {\n\t\ti = icon_expression(q, i + 1, end, 11); if (i < 0) return -1;\n\t} else if (icon_is(q, i, \"(\")) {\n\t\ti = icon_expression(q, i + 1, end, 1); if (i < 0 || !icon_is(q, i, \")\")) return -1; i++;\n\t} else if (kind == run.T_NUM || kind == run.T_FLT || kind == run.T_STR || kind == run.T_BSTR || kind == run.T_ID) {\n\t\tif (kind == run.T_ID && (v == run.intern(\"return\") || v == run.intern(\"let\") || v == run.intern(\"if\") || v == run.intern(\"while\") || v == run.intern(\"for\") || v == run.intern(\"else\") || v == run.intern(\"break\") || v == run.intern(\"continue\") || v == run.intern(\"do\") || v == run.intern(\"switch\") || v == run.intern(\"const\"))) return -1;\n\t\ti++;\n\t} else return -1;\n\twhile (i < end) {\n\t\tif (icon_is(q, i, \"(\") || icon_is(q, i, \"[\")) {\n\t\t\tv = icon_value(q, i); i++;\n\t\t\tif (v != run.intern(\"(\") || !icon_is(q, i, \")\")) {\n\t\t\t\twhile (1) {\n\t\t\t\t\ti = icon_expression(q, i, end, 1); if (i < 0) return -1;\n\t\t\t\t\tif (v == run.intern(\"(\") && icon_is(q, i, \",\")) { i++; continue; } break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (i >= end || icon_kind(q, i) != run.T_OP || !icon_pair(v, icon_value(q, i))) return -1; i++; continue;\n\t\t}\n\t\tif (icon_is(q, i, \".\")) { if (i + 1 >= end || icon_kind(q, i + 1) != run.T_ID) return -1; i += 2; continue; }\n\t\tif (icon_is(q, i, \"++\") || icon_is(q, i, \"--\")) { i++; continue; }\n\t\tif (minimum == 1 && icon_is(q, i, \"?\")) {\n\t\t\ti = icon_expression(q, i + 1, end, 1); if (i < 0 || !icon_is(q, i, \":\")) return -1;\n\t\t\ti = icon_expression(q, i + 1, end, 1); if (i < 0) return -1; continue;\n\t\t}\n\t\tpriority = 0; if (icon_kind(q, i) == run.T_OP) priority = icon_precedence(icon_value(q, i)); if (!priority || priority < minimum) break;\n\t\tnext = icon_expression(q, i + 1, end, priority + 1); if (next < 0) return -1; i = next;\n\t}\n\treturn i;\n}\nint icon_range(word* q, int a, int b, int kind) {\n\tif (a < 0 || a >= b || b > q[0]) return 0;\n\tif (kind == 1) return b == a + 1 && icon_kind(q, a) == run.T_NUM;\n\tif (kind == 2) return b == a + 1 && icon_kind(q, a) == run.T_ID;\n\tif (kind == 3) return icon_expression(q, a, b, 1) == b;\n\tif (kind == 5) return icon_is(q, a, \"{\") && icon_mate(q, a) == b - 1;\n\tif (kind == 4) return icon_statement_end(q, a) == b;\n\treturn icon_expression(q, a, b, 1) == b;\n}\n// recursively match a pattern with backtracking at balanced capture boundaries.\n// captures contain count followed by [name, start, end, class] records.\nint icon_match(word* q, int at, word* p, int pi, word* caps) {\n\tint kind, name, next, end, old, i;\n\tif (pi == p[0]) return at + 1;\n\tif (at >= q[0]) return 0;\n\tif (!icon_is(p, pi, \"$\")) {\n\t\tif (!icon_eq(q, at, p, pi, 1)) return 0;\n\t\treturn icon_match(q, at + 1, p, pi + 1, caps);\n\t}\n\tif (pi + 1 >= p[0]) return 0;\n\tname = icon_value(p, pi + 1); kind = icon_class(name); next = pi + 2;\n\tif (!kind) return 0;\n\tif (icon_is(p, next, \":\")) { name = icon_value(p, next + 1); next += 2; if (!name) return 0; }\n\telse name = 0;\n\told = caps[0]; end = at + 1;\n\tif (kind == 4) end = icon_statement_end(q, at);\n\tif (kind == 5) { if (!ic";
+ if (i == 3) return b"on_is(q, at, \"{\")) return 0; end = icon_mate(q, at) + 1; }\n\tif (end <= at) return 0;\n\twhile (end <= q[0]) {\n\t\tif (icon_range(q, at, end, kind)) {\n\t\t\ti = 0;\n\t\t\twhile (i < old) {\n\t\t\t\tif (name && caps[1 + i * 4] == name) {\n\t\t\t\t\tif (end - at != caps[3 + i * 4] - caps[2 + i * 4] || !icon_eq(q, at, q, caps[2 + i * 4], end - at)) break;\n\t\t\t\t}\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tif (i == old) {\n\t\t\t\tcaps[0] = old + 1; caps[1 + old * 4] = name; caps[2 + old * 4] = at; caps[3 + old * 4] = end; caps[4 + old * 4] = kind;\n\t\t\t\ti = icon_match(q, end, p, next, caps);\n\t\t\t\tif (i) return i;\n\t\t\t\tcaps[0] = old;\n\t\t\t}\n\t\t}\n\t\tif (kind != 3) break;\n\t\tend++;\n\t}\n\treturn 0;\n}\nint icon_capture(word* caps, int name, int ordinal) {\n\tint c = 0, kind = icon_class(name), k = 0;\n\twhile (c < caps[0]) {\n\t\tif (!kind) { if (caps[1 + c * 4] == name) return c + 1; }\n\t\telse if (caps[4 + c * 4] == kind) { if (k == ordinal) return c + 1; k++; }\n\t\tc++;\n\t}\n\treturn 0;\n}\nword* .u icon_expand(word* q, word* replacement, word* caps) {\n\tint n = 0, i = 0, j = 0, c, name, kind, k, seen[6], a, b;\n\tk = 0; while (k < 6) { seen[k] = 0; k++; }\n\twhile (i < replacement[0]) {\n\t\tif (icon_is(replacement, i, \"$\")) {\n\t\t\tif (icon_kind(replacement, i + 1) != run.T_ID) { icon_error = 3; return 0; }\n\t\t\tname = icon_value(replacement, i + 1); kind = icon_class(name); c = icon_capture(caps, name, seen[kind]);\n\t\t\tif (!c) { icon_error = 3; return 0; } c--;\n\t\t\tn += caps[3 + c * 4] - caps[2 + c * 4]; if (kind) seen[kind]++; i += 2;\n\t\t} else { n++; i++; }\n\t}\n\tword* r = rt.new(1 + n * 3); i = 0;\n\tk = 0; while (k < 6) { seen[k] = 0; k++; }\n\twhile (i < replacement[0]) {\n\t\tif (icon_is(replacement, i, \"$\")) {\n\t\t\tname = icon_value(replacement, i + 1); kind = icon_class(name); c = icon_capture(caps, name, seen[kind]) - 1;\n\t\t\tif (kind) seen[kind]++;\n\t\t\ta = caps[2 + c * 4] * 3; b = caps[3 + c * 4] * 3;\n\t\t\twhile (a < b) { r[1 + j] = q[1 + a]; a++; j++; }\n\t\t\ti += 2;\n\t\t} else {\n\t\t\tk = 0; while (k < 3) { r[1 + j] = replacement[1 + i * 3 + k]; j++; k++; } i++;\n\t\t}\n\t}\n\tr[0] = j / 3;\n\tif (!icon_balanced(r, 0, r[0])) { rt.free(r); icon_error = 1; return 0; }\n\treturn r;\n}\n\n// resumable frames retain their own token list and explicit binding maps.\n// [tokens, position, state, result, error, locals, globals, blocks, steps].\n\n// locate a binding in a frame map by its interned name.\nint icon_binding(word map, int name) {\n\twhile (map) { if (map[0] == name) return map; map = map[2]; }\n\treturn 0;\n}\nint icon_read(word binding) { if (binding[3]) return *binding[3]; return binding[1]; }\nint icon_store(word*.m frame, int column, int name, int value) {\n\tword entry = icon_binding(frame[column], name);\n\tif (!entry) {\n\t\tword* node = rt.new(4); node[0] = name; node[1] = value; node[2] = frame[column]; node[3] = 0; frame[column] = node; return value;\n\t}\n\tentry[1] = value; if (entry[3]) *entry[3] = value; return value;\n}\nint icon_declare(word*.m frame, int name, int value) {\n\tword* node = rt.new(4); node[0] = name; node[1] = value; node[2] = frame[5]; node[3] = 0; frame[5] = no";
+ if (i == 4) return b"de; return value;\n}\nint icon_fail(word frame) { frame[2] = 2; frame[4] = 5; icon_error = 5; return 0; }\nint icon_end(word* q, int start) {\n\tint depth = 0, i = start, v;\n\twhile (i < q[0]) {\n\t\tv = icon_value(q, i);\n\t\tif (icon_kind(q, i) == run.T_OP) {\n\t\t\tif (icon_open(v)) depth++;\n\t\t\telse if (icon_close(v)) { if (!depth) return i; depth--; }\n\t\t\tif (!depth && v == run.intern(\";\")) return i;\n\t\t}\n\t\ti++;\n\t}\n\treturn i;\n}\nint icon_mate(word* q, int start) {\n\tint depth = 1, i = start + 1, v;\n\twhile (i < q[0]) {\n\t\tv = icon_value(q, i);\n\t\tif (icon_kind(q, i) == run.T_OP) {\n\t\t\tif (icon_open(v)) depth++; else if (icon_close(v)) depth--;\n\t\t\tif (!depth) return i;\n\t\t}\n\t\ti++;\n\t}\n\treturn -1;\n}\n// find one statement boundary; control-flow captures require braced bodies.\nint icon_statement_end(word* q, int start) {\n\tint close, end;\n\tif (icon_is(q, start, \"{\")) { close = icon_mate(q, start); if (close < 0) return -1; return close + 1; }\n\tif (icon_is(q, start, \"if\") || icon_is(q, start, \"while\")) {\n\t\tif (!icon_is(q, start + 1, \"(\")) return -1;\n\t\tclose = icon_mate(q, start + 1);\n\t\tif (close < 0 || !icon_is(q, close + 1, \"{\")) return -1;\n\t\tend = icon_mate(q, close + 1); if (end < 0) return -1; end++;\n\t\tif (icon_is(q, start, \"if\") && icon_is(q, end, \"else\")) {\n\t\t\tif (!icon_is(q, end + 1, \"{\")) return -1;\n\t\t\tclose = icon_mate(q, end + 1); if (close < 0) return -1; return close + 1;\n\t\t}\n\t\treturn end;\n\t}\n\tend = icon_end(q, start);\n\tif (!icon_is(q, end, \";\")) return -1;\n\treturn end + 1;\n}\n// construct a compiler return body in one allocation, retaining source lines.\nword* .u icon_eval_tokens(word* q, int start, int end) {\n\tint count = end - start;\n\tword* expr = rt.new(7 + count * 3); expr[0] = count + 2;\n\texpr[1] = run.T_ID; expr[2] = run.intern(\"return\"); expr[3] = 1;\n\tif (count) rt.copy_w(expr + 4, q + 1 + start * 3, count * 3);\n\texpr[4 + count * 3] = run.T_OP; expr[5 + count * 3] = run.intern(\";\"); expr[6 + count * 3] = 1;\n\treturn expr;\n}\n// scalar frame substitution cannot preserve lvalue operations or implicit\n// program-global reads. reject these before compiling or executing a range.\nint icon_frame_expression(word frame, int start, int end) {\n\tword* q = frame[0]; int i = start, s; word binding;\n\tif (!icon_range(q, start, end, 3)) return 0;\n\twhile (i < end) {\n\t\tif (icon_is(q, i, \"++\") || icon_is(q, i, \"--\")) return 0;\n\t\tif (icon_kind(q, i) == run.T_ID && !icon_is(q, i - 1, \".\")) {\n\t\t\tbinding = icon_binding(frame[5], icon_value(q, i));\n\t\t\tif (!binding) binding = icon_binding(frame[6], icon_value(q, i));\n\t\t\tif (!binding) {\n\t\t\t\ts = run.lookups(icon_value(q, i));\n\t\t\t\tif (s < 0 || (run.skind[s] != run.K_FN && run.skind[s] != run.K_CONST)) return 0;\n\t\t\t}\n\t\t}\n\t\ti++;\n\t}\n\treturn 1;\n}\nint icon_frame_eval(word frame, int start, int end) {\n\tword* expr = icon_eval_tokens(frame[0], start, end);\n\tint i = 1; word binding;\n\twhile (i < expr[0] - 1) {\n\t\tif (icon_kind(expr, i) == run.T_ID && !icon_is(expr, i - 1, \".\")) {\n\t\t\tbinding = icon_binding(frame[5], icon_value(expr, i));\n\t\t\tif (!bi";
+ if (i == 5) return b"nding) binding = icon_binding(frame[6], icon_value(expr, i));\n\t\t\tif (binding) { expr[1 + i * 3] = run.T_NUM; expr[2 + i * 3] = icon_read(binding); }\n\t\t}\n\t\ti++;\n\t}\n\tint result = run.eval(expr); rt.free(expr); return result;\n}\nint icon_block(word*.m frame, int open, int resume) {\n\tint close = icon_mate(frame[0], open);\n\tif (close < 0) return icon_fail(frame);\n\tword block = rt.new(4); block[0] = close; block[1] = resume; block[2] = frame[7]; block[3] = frame[5]; frame[7] = block; frame[1] = open + 1; return 1;\n}\n\nstruct icon {\n\t// the last transformation status: 0 success, 1 invalid delimiters,\n\t// 2 invalid range or declaration, 3 unresolved replacement capture,\n\t// 4 unavailable function metadata, 5 unsupported stepping syntax,\n\t// 6 rejected function publication.\n\tstatic int error() { return icon_error; }\n\t// check token delimiters without compiling or executing the list.\n\tstatic int validate(word* q) { icon_error = 0; if (!q || q[0] < 0 || !icon_balanced(q, 0, q[0])) { icon_error = 1; return 0; } return 1; }\n\t// return an independent token list with [start, end) replaced by insert.\n\tstatic word* .u splice(word* q, int start, int end, word* insert) {\n\t\ticon_error = 0;\n\t\tif (!q || !insert || q[0] < 0 || insert[0] < 0 || start < 0 || end < start || end > q[0]) { icon_error = 2; return 0; }\n\t\tword* r = icon_splice(q, start, end, insert);\n\t\tif (!icon_balanced(r, 0, r[0])) { rt.free(r); icon_error = 1; return 0; }\n\t\treturn r;\n\t}\n\t// replace each nonoverlapping pattern match. inserted tokens are not\n\t// searched again. the input and templates remain unchanged.\n\tstatic word* .u rewrite(word* q, word* pattern, word* replacement) {\n\t\ticon_error = 0;\n\t\tif (!q || q[0] < 0 || !replacement || replacement[0] < 0 || !icon_pattern(pattern) || !icon_balanced(q, 0, q[0])) { icon_error = 1; return 0; }\n\t\tword* result = icon_slice(q, 0, q[0]); word* caps = rt.new(1 + pattern[0] * 4); word* insert, next;\n\t\tint at = 0, end;\n\t\twhile (at < result[0]) {\n\t\t\tcaps[0] = 0; end = icon_match(result, at, pattern, 0, caps);\n\t\t\tif (!end) { at++; continue; }\n\t\t\tinsert = icon_expand(result, replacement, caps);\n\t\t\tif (!insert) { rt.free(result); rt.free(caps); return 0; }\n\t\t\tnext = icon_splice(result, at, end - 1, insert); at += insert[0];\n\t\t\trt.free(insert); rt.free(result); result = next;\n\t\t}\n\t\trt.free(caps); return result;\n\t}\n\t// transform retained function tokens and publish the compiled replacement.\n\t// references to the original entry continue to use the published version.\n\tstatic int find_replace(word function, word* pattern, word* replacement) {\n\t\ticon_error = 0;\n\t\tif (!replacement || replacement[0] < 0 || !icon_pattern(pattern)) { icon_error = 1; return 0; }\n\t\tword* q = run.function_tokens(function);\n\t\tif (!q) { icon_error = 4; return 0; }\n\t\tword* caps = rt.new(1 + pattern[0] * 4); int i = 0, end, count = 0;\n\t\twhile (i < q[0]) { caps[0] = 0; end = icon_match(q, i, pattern, 0, caps); if (end) { count++; i = end - 1; } else i++; }\n\t\trt.free(caps);\n\t\tif (!count) { rt.free(q); ";
+ if (i == 6) return b"return 0; }\n\t\tword* changed = icon.rewrite(q, pattern, replacement); rt.free(q);\n\t\tif (!changed) return 0;\n\t\tint published = run.replace_function(function, changed); rt.free(changed);\n\t\tif (!published) { icon_error = 6; return 0; }\n\t\treturn count;\n\t}\n\t// remove a complete declaration statement containing the requested name.\n\t// declarations containing multiple bindings are rejected to preserve them.\n\tstatic word* .u undeclare(word* q, char* name) {\n\t\ticon_error = 0;\n\t\tint declaration[4];\n\t\tif (!q || q[0] < 0 || !name || !icon_locate_declaration(q, run.intern(name), declaration)) { icon_error = 2; return 0; }\n\t\tword* empty = rt.new(1); empty[0] = 0;\n\t\tword* result = icon.splice(q, declaration[3], declaration[2] + 1, empty); rt.free(empty); return result;\n\t}\n\t// change the type of the first simple declaration of name.\n\tstatic word* .u change_type(word* q, char* name, word* type) {\n\t\ticon_error = 0;\n\t\tint declaration[4];\n\t\tif (!q || q[0] < 0 || !name || !icon_locate_declaration(q, run.intern(name), declaration)) { icon_error = 2; return 0; }\n\t\treturn icon.splice(q, declaration[0], declaration[1], type);\n\t}\n\t// insert a scoped statement block at the given token boundary.\n\tstatic word* .u insert_block(word* q, int at, word* body) {\n\t\ticon_error = 0;\n\t\tif (!q || !body || body[0] < 0 || at < 0 || at > q[0]) { icon_error = 2; return 0; }\n\t\tword* block = rt.new(7 + body[0] * 3); int i = 0; block[0] = body[0] + 2;\n\t\tblock[1] = run.T_OP; block[2] = run.intern(\"{\"); block[3] = 1;\n\t\twhile (i < body[0] * 3) { block[4 + i] = body[1 + i]; i++; }\n\t\tblock[4 + i] = run.T_OP; block[5 + i] = run.intern(\"}\"); block[6 + i] = 1;\n\t\tword* result = icon.splice(q, at, at, block); rt.free(block); return result;\n\t}\n\t// evaluate a selected expression using the compiler rather than a text\n\t// evaluator. the range must contain one complete expression.\n\tstatic int calculate(word* q, int start, int end) {\n\t\ticon_error = 0;\n\t\tif (!q || start < 0 || end > q[0] || !icon_range(q, start, end, 3)) { icon_error = 2; return 0; }\n\t\tword* expr = icon_eval_tokens(q, start, end);\n\t\tint value = run.eval(expr); rt.free(expr); return value;\n\t}\n\t// start a resumable evaluation with independent tokens and binding storage.\n\t// stepping executes statements through the compiler; no native frame is\n\t// suspended or inspected. scalar bindings are visible between statements.\n\tstatic word* .u begin(word* q) {\n\t\tif (!icon.validate(q)) { icon_error = 1; return 0; }\n\t\tword* frame = rt.new(9); int i = 0; while (i < 9) { frame[i] = 0; i++; } frame[0] = icon_slice(q, 0, q[0]); return frame;\n\t}\n\t// create an explicit frame from a function's retained body. argument values\n\t// are supplied through set_local before stepping starts.\n\tstatic word* .u begin_function(int function) {\n\t\tword* q = run.function_tokens(function);\n\t\tif (!q) { icon_error = 4; return 0; }\n\t\tword* frame = icon.begin(q); rt.free(q); return frame;\n\t}\n\t// set an isolated global binding, or update storage explicitly bound to it.\n\tstatic int set_gl";
+ if (i == 7) return b"obal(word frame, char* name, int value) { icon_error = 0; return icon_store(frame, 6, run.intern(name), value); }\n\t// update the innermost local binding, creating a binding when absent.\n\tstatic int set_local(word frame, char* name, int value) { icon_error = 0; return icon_store(frame, 5, run.intern(name), value); }\n\t// bind one existing scalar global explicitly. reads observe its current\n\t// value, and subsequent frame assignments update its program storage.\n\tstatic int bind_global(word frame, char* name) {\n\t\tint s = run.lookups(name); icon_error = 0;\n\t\tif (s < 0 || run.skind[s] != run.K_GVAR) { icon_error = 2; return 0; }\n\t\tword type = run.stype[s];\n\t\tif (type && type != run.tint && type != run.tword) { icon_error = 2; return 0; }\n\t\ticon_store(frame, 6, run.intern(name), *run.sval[s]);\n\t\tword binding = icon_binding(frame[6], run.intern(name)); binding[3] = run.sval[s]; return 1;\n\t}\n\t// read the innermost local binding; a missing binding sets status 2.\n\tstatic int local(word frame, char* name) { icon_error = 0; word b = icon_binding(frame[5], run.intern(name)); if (!b) { icon_error = 2; return 0; } return icon_read(b); }\n\t// read an isolated or explicitly connected global binding.\n\tstatic int global(word frame, char* name) { icon_error = 0; word b = icon_binding(frame[6], run.intern(name)); if (!b) { icon_error = 2; return 0; } return icon_read(b); }\n\t// frame state: 0 resumable, 1 complete, 2 unsupported statement.\n\tstatic int state(word frame) { return frame[2]; }\n\t// the returned value after completion; zero before a return is executed.\n\tstatic int result(word frame) { return frame[3]; }\n\t// token index of the next statement or control-flow decision.\n\tstatic int position(word frame) { return frame[1]; }\n\t// number of executed statements and control-flow decisions.\n\tstatic int steps(word frame) { return frame[8]; }\n\t// number of retained local binding records, including active shadowed records.\n\tstatic int local_count(word frame) { word b = frame[5]; int n = 0; while (b) { n++; b = b[2]; } return n; }\n\t// interned name of a local record; zero when index is outside the map.\n\tstatic char* local_name(word frame, int index) { word b = frame[5]; while (b && index > 0) { index--; b = b[2]; } if (!b || index < 0) return 0; return b[0]; }\n\t// execute one statement or control-flow decision. returns 1 after progress,\n\t// 0 after completion or an unsupported statement. blocks, if and while\n\t// require braces; declarations contain one scalar binding.\n\tstatic int step(word frame) {\n\t\tif (!frame || frame[2]) return 0;\n\t\ticon_error = 0;\n\t\tword* q = frame[0]; int at = frame[1], end, name, value, column = 5, v, close, open, alternate;\n\t\tword block, binding;\n\t\twhile (frame[7] && at == frame[7][0]) {\n\t\t\tblock = frame[7];\n\t\t\twhile (frame[5] != block[3]) { binding = frame[5]; frame[5] = binding[2]; rt.free(binding); }\n\t\t\tat = block[1]; frame[7] = block[2]; rt.free(block); frame[1] = at;\n\t\t}\n\t\tif (at >= q[0]) { frame[2] = 1; return 0; }\n\t\tframe[8]++;\n\t\tif (icon_is(q";
+ if (i == 8) return b", at, \";\")) { frame[1] = at + 1; return 1; }\n\t\tif (icon_is(q, at, \"{\")) { close = icon_mate(q, at); return icon_block(frame, at, close + 1); }\n\t\tif (icon_is(q, at, \"if\") || icon_is(q, at, \"while\")) {\n\t\t\tif (!icon_is(q, at + 1, \"(\")) return icon_fail(frame);\n\t\t\tclose = icon_mate(q, at + 1); open = close + 1;\n\t\t\tif (close < 0 || !icon_is(q, open, \"{\")) return icon_fail(frame);\n\t\t\tend = icon_mate(q, open); if (end < 0) return icon_fail(frame);\n\t\t\talternate = end + 1;\n\t\t\tif (icon_is(q, at, \"if\") && icon_is(q, alternate, \"else\")) {\n\t\t\t\tif (!icon_is(q, alternate + 1, \"{\")) return icon_fail(frame);\n\t\t\t}\n\t\t\tif (!icon_frame_expression(frame, at + 2, close)) return icon_fail(frame);\n\t\t\tvalue = icon_frame_eval(frame, at + 2, close);\n\t\t\tif (icon_is(q, at, \"while\")) { if (value) return icon_block(frame, open, at); frame[1] = end + 1; return 1; }\n\t\t\tif (icon_is(q, alternate, \"else\")) {\n\t\t\t\tclose = icon_mate(q, alternate + 1);\n\t\t\t\tif (value) return icon_block(frame, open, close + 1);\n\t\t\t\treturn icon_block(frame, alternate + 1, close + 1);\n\t\t\t}\n\t\t\tif (value) return icon_block(frame, open, end + 1);\n\t\t\tframe[1] = end + 1; return 1;\n\t\t}\n\t\tend = icon_end(q, at);\n\t\tif (end >= q[0] || !icon_is(q, end, \";\")) return icon_fail(frame);\n\t\tif (icon_is(q, at, \"return\")) {\n\t\t\tframe[3] = 0;\n\t\t\tif (end > at + 1) { if (!icon_frame_expression(frame, at + 1, end)) return icon_fail(frame); frame[3] = icon_frame_eval(frame, at + 1, end); }\n\t\t\tframe[1] = end + 1; frame[2] = 1; return 1;\n\t\t}\n\t\tif (icon_is(q, at, \"let\") || icon_is(q, at, \"int\") || icon_is(q, at, \"word\")) {\n\t\t\tif (icon_kind(q, at + 1) != run.T_ID) return icon_fail(frame);\n\t\t\tname = icon_value(q, at + 1); value = 0;\n\t\t\tif (end > at + 2) { if (!icon_is(q, at + 2, \"=\") || !icon_frame_expression(frame, at + 3, end)) return icon_fail(frame); value = icon_frame_eval(frame, at + 3, end); }\n\t\t\ticon_declare(frame, name, value); frame[1] = end + 1; return 1;\n\t\t}\n\t\tname = icon_value(q, at); binding = icon_binding(frame[5], name);\n\t\tif (!binding) { binding = icon_binding(frame[6], name); column = 6; }\n\t\tif (binding && icon_kind(q, at) == run.T_ID) {\n\t\t\tv = icon_value(q, at + 1);\n\t\t\tif (end == at + 2 && (v == run.intern(\"++\") || v == run.intern(\"--\"))) {\n\t\t\t\tvalue = icon_read(binding) + 1; if (v == run.intern(\"--\")) value = icon_read(binding) - 1;\n\t\t\t} else {\n\t\t\t\tif (v != run.intern(\"=\") && v != run.intern(\"+=\") && v != run.intern(\"-=\") && v != run.intern(\"*=\") && v != run.intern(\"/=\")) return icon_fail(frame);\n\t\t\t\tif (!icon_frame_expression(frame, at + 2, end)) return icon_fail(frame);\n\t\t\t\tvalue = icon_frame_eval(frame, at + 2, end);\n\t\t\t\tif (v == run.intern(\"+=\")) value = icon_read(binding) + value;\n\t\t\t\tif (v == run.intern(\"-=\")) value = icon_read(binding) - value;\n\t\t\t\tif (v == run.intern(\"*=\")) value = icon_read(binding) * value;\n\t\t\t\tif (v == run.intern(\"/=\")) value = icon_read(binding) / value;\n\t\t\t}\n\t\t\ticon_store(frame, column, name, value); frame[1] = end + 1; return 1;\n\t\t}\n\t\treturn icon_fail(frame);\n\t}\n\t// execute at most budg";
+ if (i == 9) return b"et statements. returning 0 leaves the frame resumable;\n\t// callers can inspect bindings, edit them, then continue with another budget.\n\tstatic int resume(word frame, int budget) { while (budget > 0 && !frame[2]) { icon.step(frame); budget--; } return frame[2]; }\n\t// release a frame and all storage it owns.\n\tstatic int close(.d frame) {\n\t\tword b, next; int column = 5;\n\t\twhile (column <= 7) { b = frame[column]; while (b) { next = b[2]; rt.free(b); b = next; } column++; }\n\t\trt.free(frame[0]); rt.free(frame); return 0;\n\t}\n}\n\n// public entry points for the icon namespace.\n\n// status of the most recent transformation or binding operation; see docs/ICON.md.\nint error() { return icon.icon.error(); }\n// check delimiter structure only; does not compile or execute tokens.\nint validate(word* q) { return icon.icon.validate(q); }\n// return a new token list with [start, end) replaced; caller owns the result.\nword* .u splice(word* q, int start, int end, word* insert) { return icon.icon.splice(q, start, end, insert); }\n// return a new list with nonoverlapping matches replaced; inputs remain unchanged.\nword* .u rewrite(word* q, word* pattern, word* replacement) { return icon.icon.rewrite(q, pattern, replacement); }\n// publish a transformed function body; returns matches, or 0 on no match or failure.\nint find_replace(word function, word* pattern, word* replacement) { return icon.icon.find_replace(function, pattern, replacement); }\n// remove the first complete single-binding declaration named name; caller owns the result.\nword* .u undeclare(word* q, char* name) { return icon.icon.undeclare(q, name); }\n// replace the type prefix of the first simple declaration named name; caller owns the result.\nword* .u change_type(word* q, char* name, word* type) { return icon.icon.change_type(q, name, type); }\n// insert a scoped block containing body at token boundary at; caller owns the result.\nword* .u insert_block(word* q, int at, word* body) { return icon.icon.insert_block(q, at, body); }\n// compile and evaluate the expression in [start, end); expression calls retain their effects.\nint calculate(word* q, int start, int end) { return icon.icon.calculate(q, start, end); }\n// create a frame with copied tokens and isolated bindings; caller closes the frame.\nword* .u begin(word* q) { return icon.icon.begin(q); }\n// create a frame from retained function tokens; set arguments as locals before stepping.\nword* .u begin_function(int function) { return icon.icon.begin_function(function); }\n// set an isolated global binding, creating it when absent; returns the stored value.\nint set_global(word frame, char* name, int value) { return icon.icon.set_global(frame, name, value); }\n// set the innermost local binding, creating it when absent; returns the stored value.\nint set_local(word frame, char* name, int value) { return icon.icon.set_local(frame, name, value); }\n// connect an int or word program global to the frame; later frame writes update it.\nint bind_global(word frame, char* name) { ret";
+ if (i == 10) return b"urn icon.icon.bind_global(frame, name); }\n// read a local binding; missing names return 0 and set error status 2.\nint local(word frame, char* name) { return icon.icon.local(frame, name); }\n// read a frame global binding; missing names return 0 and set error status 2.\nint global(word frame, char* name) { return icon.icon.global(frame, name); }\n// return frame state: 0 resumable, 1 complete, or 2 unsupported statement.\nint state(word frame) { return icon.icon.state(frame); }\n// return the value produced by return, or 0 before a return is executed.\nint result(word frame) { return icon.icon.result(frame); }\n// return the token index of the next statement or control-flow decision.\nint position(word frame) { return icon.icon.position(frame); }\n// return the number of statements and control-flow decisions executed.\nint steps(word frame) { return icon.icon.steps(frame); }\n// count retained local records, including shadowed records in active blocks.\nint local_count(word frame) { return icon.icon.local_count(frame); }\n// return the zero-based local record name, or 0 when index is out of range.\nchar* local_name(word frame, int index) { return icon.icon.local_name(frame, index); }\n// execute scalar declarations and assignments, standalone ++/--, return, blocks,\n// or braced if/else/while; direct program globals need bind_global first.\n// unsupported syntax sets error status 5.\nint step(word frame) { return icon.icon.step(frame); }\n// execute at most budget steps and return the frame state; frame bindings are scalar int/word values.\nint resume(word frame, int budget) { return icon.icon.resume(frame, budget); }\n// release the frame and owned storage; consumes frame and returns 0.\nint close(.d frame) { return icon.icon.close(d*frame); }\n";
+ return 0;
+}
+// ---- end embedded icon library ----
diff --git a/src/core/core.c b/src/core/core.c
index 2f6032a..3a1d0ca 100644
--- a/src/core/core.c
+++ b/src/core/core.c
@@ -25,11 +25,22 @@
#endif
#else
#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <signal.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
#endif
#include <time.h>
+#if !defined(_WIN32) && !defined(__wasm__)
+#include <pthread.h>
+#include <stdatomic.h>
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
#if (defined(__linux__) || defined(__APPLE__)) && !defined(__wasm__) && !defined(ZY_NOFFI)
#define ZYFFI 1
#include <dlfcn.h>
@@ -62,15 +73,16 @@
#define JTYPE char
#endif
char Q(void);
+int input_pending(void);
void G(char);
-extern JTYPE J;
+extern VM_TLS JTYPE J;
typedef int64_t Cell; /* one vm word. memory stays word-addressed. */
typedef uint64_t UCell;
enum { DEFAULT_MEMORY_LIMIT = MW };
-static int memory_limit = DEFAULT_MEMORY_LIMIT;
-static int memory_words;
-static int memory_growth_limit;
+static VM_TLS int memory_limit = DEFAULT_MEMORY_LIMIT;
+static VM_TLS int memory_words;
+static VM_TLS int memory_growth_limit;
/* bounds baked into native blocks use the reservation, whose base never moves. */
#undef MW
#define MW memory_limit
@@ -80,7 +92,7 @@ static int memory_growth_limit;
#define PC_DATA_OVERFLOW (PC_HALT + 1)
#define PC_RETURN_OVERFLOW (PC_HALT + 2)
#define PC_FRAME_OVERFLOW (PC_HALT + 3)
-static Cell code_words;
+static VM_TLS Cell code_words;
static int code_reserve(Cell end);
static int code_range_live(Cell start, Cell count);
/* why the cpu stopped, when it wasn't a halt */
@@ -95,8 +107,8 @@ static int stopped(int p)
#define MAXB 65536
#define BLKW 1024 /* longest block, in vm words */
-static Cell *m;
-static Cell frame_floor_word = ZFP + 6, frame_top_word = ZFP + 7, frame_guard_word = ZFP + 8;
+static VM_TLS Cell *m;
+static VM_TLS Cell frame_floor_word = ZFP + 6, frame_top_word = ZFP + 7, frame_guard_word = ZFP + 8;
static Cell frame_bound(void) { return m[frame_floor_word] + m[frame_guard_word]; }
static int b_ok(Cell addr, int n) { return bop_ok(addr, n, MW); }
@@ -139,7 +151,7 @@ static Cell fop_parse_cell(Cell addr)
if ((UCell)addr >= (UCell)MW) return 0;
return (Cell)fop_parse_words(m + addr, (UCell)MW - (UCell)addr);
}
-static int trace;
+static VM_TLS int trace;
/* the command line and the environment, kept for ops 21 to 24. a program read
from stdin has no file name, so argv[0] is <eval>, as in zy(1). the rest of
@@ -148,10 +160,10 @@ static int trace;
#ifndef _WIN32
extern char **environ;
#endif
-static int zy_argc;
-static char **zy_argv;
-static int zy_envc;
-static char **zy_envp;
+static VM_TLS int zy_argc;
+static VM_TLS char **zy_argv;
+static VM_TLS int zy_envc;
+static VM_TLS char **zy_envp;
static void zy_args(int argc, char **argv)
{
@@ -211,7 +223,7 @@ static Cell vecstr(char **v, int count, Cell a, Cell b, Cell c, const char *lead
}
static Cell host_rand(void)
{
- static uint64_t s;
+ static VM_TLS uint64_t s;
if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
if (!s) s = 0x9E3779B97F4A7C15ull;
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
@@ -221,11 +233,29 @@ static Cell host_rand(void)
/* c abi: ops 14..18 of the gate. see the ffi section further down. cb_s and
cb_r are the vm stack pointers as of the gate store, so that a callback from
native code can run vm code on top of them. */
-static Cell cb_s, cb_r;
+static VM_TLS Cell cb_s, cb_r;
static Cell ffi_call(Cell blk);
static Cell ffi_find(Cell name);
static Cell ffi_cbmake(Cell entry, Cell sig);
static Cell ffi_load(Cell name);
+typedef struct VmContext VmContext;
+static VM_TLS VmContext *context_current, *context_children;
+static int context_cancelled(void);
+static void context_exit(int value);
+static Cell context_start(Cell entry, Cell channel);
+static Cell context_join(Cell token, int cancel);
+#if !defined(_WIN32) && !defined(__wasm__)
+static VM_TLS atomic_int context_default_stop;
+static atomic_int context_records_live, context_code_live, context_memory_live;
+size_t io_context_size(void);
+void io_context_snapshot(void *destination);
+void io_context_restore(const void *source);
+#else
+static VM_TLS int context_default_stop;
+#endif
+static int context_descriptor(Cell handle);
+static int context_channel_close(Cell handle);
+#define CONTEXT_HANDLE_LO 0x40000000
static Cell hostcall(Cell op, Cell a, Cell b, Cell c)
{
char path[1024], tmp[512];
@@ -234,15 +264,36 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c)
/* a build for checking code the user has not run yet (the language server): compile-time
code may read what it includes, but it may not write files, exit, load or call C code,
or make raw system calls */
- if (op == 4 || op == 7 || op == 12 || (op >= 14 && op <= 17) || op == 40) return -1;
+ if (op == 4 || op == 7 || op == 12 || (op >= 14 && op <= 17) || op == 40 || (op >= 43 && op <= 49) || (op >= 51 && op <= 54)) return -1;
if ((op == 1 || op == 10) && b != 0) return -1;
#endif
switch (op) {
+ case 51: return context_start(a, b);
+ case 52: return context_join(a, (int)b);
+ case 53:
+#if !defined(_WIN32) && !defined(__wasm__)
+ return getpid();
+#else
+ return -1;
+#endif
+ case 55:
+#if !defined(_WIN32) && !defined(__wasm__)
+ return a == 0 ? atomic_load(&context_records_live) : a == 1 ? atomic_load(&context_code_live) : a == 2 ? atomic_load(&context_memory_live) : -1;
+#else
+ return -1;
+#endif
+ case 54:
+#if !defined(_WIN32) && !defined(__wasm__)
+ return (Cell)(uintptr_t)pthread_self();
+#else
+ return -1;
+#endif
case 1:
if (zcstr(a, path, 1024) < 0) return -1;
if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
return open(path, O_RDONLY | O_BINARY);
case 2:
+ if (a >= CONTEXT_HANDLE_LO) return context_channel_close(a);
return close((int)a);
case 3:
if (c < 0 || !inmem(b)) return -1;
@@ -288,6 +339,7 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c)
case 6:
return (Cell)time(0);
case 7:
+ if (context_current) { context_exit((int)a); return 0; }
exit((int)a);
case 8:
return host_rand();
@@ -313,6 +365,101 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c)
if (a == 7 || a == 8) return stack_expand(a == 8, b, &cb_s, &cb_r);
if (a == 9) return code_reserve(b);
return -1;
+ case 43: { /* clone an isolated execution context with a bidirectional channel */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int fd[2]; pid_t child;
+ if (!inmem(a) || socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ fflush(NULL); child = fork();
+ if (child < 0) { close(fd[0]); close(fd[1]); return -1; }
+ if (!child) {
+ close(fd[0]); m[a] = fd[1];
+#if defined(__APPLE__) && defined(ZYJIT)
+ /* fork resets the thread's jit permission state; resume executable code. */
+ pthread_jit_write_protect_np(1);
+#endif
+ return 0;
+ }
+ close(fd[1]); m[a] = fd[0]; return child;
+#else
+ return -1;
+#endif
+ }
+ case 44: case 45: { /* write/read complete word buffers on a context channel */
+ a = context_descriptor(a); if (a < 0) return -1;
+#if !defined(_WIN32) && !defined(__wasm__)
+ size_t done = 0, length; ssize_t got; unsigned char *buffer;
+ if (b < 0 || c < 0 || c > MW - b) return -1;
+ buffer = (unsigned char *)(m + b); length = (size_t)c * sizeof(Cell);
+ while (done < length) {
+ if (op == 44) {
+#ifdef __APPLE__
+ int enabled = 1; setsockopt((int)a, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof enabled);
+ #ifdef MSG_NOSIGNAL
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#else
+ got = send((int)a, buffer + done, length - done, 0);
+#endif
+#else
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#endif
+ } else got = read((int)a, buffer + done, length - done);
+ if (got < 0 && errno == EINTR) continue;
+ if (got <= 0) return -1;
+ done += (size_t)got;
+ }
+ return c;
+#else
+ return -1;
+#endif
+ }
+ case 46: { /* channel readiness, with a millisecond timeout */
+ a = context_descriptor(a); if (a < 0) return -1;
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct pollfd fd; int ready; fd.fd = (int)a; fd.events = POLLIN; fd.revents = 0;
+ do { ready = poll(&fd, 1, (int)b); } while (ready < 0 && errno == EINTR);
+ return ready > 0 ? !!(fd.revents & (POLLIN | POLLHUP)) : ready;
+#else
+ return -1;
+#endif
+ }
+ case 47: case 49: { /* join a child context, optionally terminating it first */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int status; pid_t joined;
+ if (a <= 0) return -1;
+ if (op == 49) kill((pid_t)a, SIGTERM);
+ do { joined = waitpid((pid_t)a, &status, 0); } while (joined < 0 && errno == EINTR);
+ return joined < 0 ? -1 : WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+#else
+ return -1;
+#endif
+ }
+ case 50: { /* monotonic milliseconds for elapsed-time measurements */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return -1;
+ return (Cell)now.tv_sec * 1000 + now.tv_nsec / 1000000;
+#else
+ return -1;
+#endif
+ }
+ case 48: { /* sleep for a bounded millisecond duration */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec duration;
+ if (a < 0 || a > 86400000) return -1;
+ duration.tv_sec = (time_t)(a / 1000); duration.tv_nsec = (long)(a % 1000) * 1000000;
+ while (nanosleep(&duration, &duration) < 0) if (errno != EINTR) return -1;
+ return 0;
+#else
+ return -1;
+#endif
+ }
+ case 42: /* poll: 1 when input can be read without waiting */
+ return input_pending();
+ case 41: { /* shortest float text into a word buffer */
+ char buf[400]; int n = fop_format_mode((uint64_t)a, 0, 17, buf), i;
+ if (c <= n || b < 0 || b > MW || c > MW - b) return -1;
+ for (i = 0; i < n; i++) b_commit(b + i, (unsigned char)buf[i]);
+ b_commit(b + n, 0); return n;
+ }
case 10:
if (bop_cstr(m, a, path, 1024, MW) < 0) return -1;
if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
@@ -468,11 +615,11 @@ typedef struct {
Uop *u;
signed char *reg; /* physical register per value, for the jit; 0 if it didn't fit */
} Enh;
-static Enh enh[NENH];
-static int need_flush;
+static VM_TLS Enh enh[NENH];
+static VM_TLS int need_flush;
#define MAXUOP 4096
-static Uop ub[MAXUOP]; static int un, unv, uerr; /* the program being built */
+static VM_TLS Uop ub[MAXUOP]; static VM_TLS int un, unv, uerr; /* the program being built */
static int unew(int op, int a, int b, int c, Cell imm)
{
Uop *u;
@@ -684,8 +831,8 @@ static void umagic(UCell d, UCell *mul, int *add, int *sh) /* hacker's delight,
*mul = q2 + 1; *sh = p - 64;
}
-static Uop ob[MAXUOP]; static int on, onv;
-static int *omap, *isk; static Cell *kv; /* old value -> new value; constness of new values */
+static VM_TLS Uop ob[MAXUOP]; static VM_TLS int on, onv;
+static VM_TLS int *omap, *isk; static VM_TLS Cell *kv; /* old value -> new value; constness of new values */
static int pure(int op) { return !is_memop(op); }
static int ispow2(UCell x) { return x && !(x & (x - 1)); }
static int lg2(UCell x) { int n = 0; while (x > 1) { x >>= 1; n++; } return n; }
@@ -784,12 +931,15 @@ static int oe(int op, int a, int b, int c, Cell imm)
}
return raw(op, a, b, c, 0);
}
+static void optimizer_storage(void) {
+ static VM_TLS int omap_[MAXUOP + 16], isk_[MAXUOP * 4]; static VM_TLS Cell kv_[MAXUOP * 4];
+ omap = omap_; isk = isk_; kv = kv_;
+}
/* runs the optimizer over ub[0..un) with nin inputs; leaves the result in e */
static int optimize(Enh *e, int nin, int *out, int nout)
{
int i, *live, n = 0;
- static int omap_[MAXUOP + 16], isk_[MAXUOP * 4]; static Cell kv_[MAXUOP * 4];
- omap = omap_; isk = isk_; kv = kv_;
+ optimizer_storage();
on = 0; onv = nin;
for (i = 0; i < nin; i++) { omap[i] = i; isk[i] = 0; }
for (i = 0; i < un && !uerr; i++) {
@@ -962,12 +1112,12 @@ typedef struct {
unsigned gen; /* bumped every time the region's code is dropped: a copy made from an older one is stale */
} Rg;
#define MAXRG 4096
-static Rg *rgs[MAXRG];
-static int nrg;
-static int fn_pc[MAXRG];
-static Rg *fn_rg_[MAXRG];
-static Rg *fn_cache[1024];
-static int nfn;
+static VM_TLS Rg *rgs[MAXRG];
+static VM_TLS int nrg;
+static VM_TLS int fn_pc[MAXRG];
+static VM_TLS Rg *fn_rg_[MAXRG];
+static VM_TLS Rg *fn_cache[1024];
+static VM_TLS int nfn;
/* ---- bookkeeping for translated code, shared by the backends ----
a translation covers a range of vm words [a,b). three things have to stay
@@ -983,13 +1133,13 @@ static int nfn;
#define NPG ((code_words >> PGSH) + 1)
#define MAXPGN (1 << 19)
#define MAXDEP (1 << 18)
-static struct { int a, b; } ix_blk[MAXB];
-static int ix_n;
-static int *pg_head, pg_free, pg_top, pg_live; /* node numbers are stored plus one: 0 is none */
-static struct { int blk, next; } pg_node[MAXPGN];
-static int dep_head[NENH + 1], dep_n; /* slot NENH: depends on every install */
-static struct { int blk, next; } dep_node[MAXDEP];
-static int drop_epoch; /* changes whenever any translation dies */
+static VM_TLS struct { int a, b; } ix_blk[MAXB];
+static VM_TLS int ix_n;
+static VM_TLS int *pg_head, pg_free, pg_top, pg_live; /* node numbers are stored plus one: 0 is none */
+static VM_TLS struct { int blk, next; } pg_node[MAXPGN];
+static VM_TLS int dep_head[NENH + 1], dep_n; /* slot NENH: depends on every install */
+static VM_TLS struct { int blk, next; } dep_node[MAXDEP];
+static VM_TLS int drop_epoch; /* changes whenever any translation dies */
static void be_drop(int j); /* the backend forgets block j's code and coverage */
static void runmark(Rg *R); /* the backend stops watching the region's words */
static void rclear(Rg *R);
@@ -1048,9 +1198,9 @@ static void slot_changed(int slot)
for (j = 0; j < nrg; j++)
if (rgs[j]->lifted && (rgs[j]->dep[slot >> 3] & (1 << (slot & 7)))) { rclear(rgs[j]); drop_epoch++; }
}
-static Cell halt_at = -1; /* a nested call stopped the machine: operand or return overflow */
-static unsigned short *o3ent; /* region + 1 at an entry point */
-static unsigned char *o3ei; /* which of its entries */
+static VM_TLS Cell halt_at = -1; /* a nested call stopped the machine: operand or return overflow */
+static VM_TLS unsigned short *o3ent; /* region + 1 at an entry point */
+static VM_TLS unsigned char *o3ei; /* which of its entries */
static int o3_ready(Rg *C);
static int o3_exec_region(Rg *C, int entry, Cell *ps, Cell *pr);
static int vm_until(int pc, int stop_eq, int stop_ge, Rg *re, Cell *ps, Cell *pr);
@@ -1189,10 +1339,10 @@ typedef double (*FfiD)(FFI_IT, FFI_DT, FFI_ST, ...);
typedef float (*FfiF)(FFI_IT, FFI_DT, FFI_ST, ...);
#define FFI_SYMS 256
#define FFI_LIBS 16
-static struct { char name[96]; void *p; } ffi_sym[FFI_SYMS];
-static int ffi_nsym;
-static void *ffi_lib[FFI_LIBS];
-static int ffi_nlib, ffi_tried_libm;
+static VM_TLS struct { char name[96]; void *p; } ffi_sym[FFI_SYMS];
+static VM_TLS int ffi_nsym;
+static VM_TLS void *ffi_lib[FFI_LIBS];
+static VM_TLS int ffi_nlib, ffi_tried_libm;
static void *ffi_lookup(const char *name)
{
int i;
@@ -1388,7 +1538,7 @@ static int ffi_varargs(void *f)
#if defined(__APPLE__) && defined(__aarch64__)
static const char *names[] = { "printf", "fprintf", "sprintf", "snprintf" };
static const int fixed[] = { 1, 2, 2, 3 };
- static void *entry[4]; static int ready;
+ static VM_TLS void *entry[4]; static VM_TLS int ready;
int i;
if (!ready) { for (i = 0; i < 4; i++) entry[i] = dlsym(RTLD_DEFAULT, names[i]); ready = 1; }
for (i = 0; i < 4; i++) if (f == entry[i]) return fixed[i];
@@ -1549,7 +1699,7 @@ static long ncall_host(long sp, long rp)
expects them, pushes a return address that stops the nested run, runs the
vm function and pops its result. */
#define FFI_CBS 16
-static struct { Cell entry, sig; int used; } ffi_cb[3][FFI_CBS];
+static VM_TLS struct { Cell entry, sig; int used; } ffi_cb[3][FFI_CBS];
static Cell ffi_enter(int cls, int k, Cell *iv, double *dv, Cell *sv)
{
Cell s = cb_s, r = cb_r, sig = ffi_cb[cls][k].sig, res = 0, sent = 1, w;
@@ -1595,7 +1745,7 @@ FFI_ALL(FFI_SLOT)
#define FFI_PICK(K) (void *)ffi_i##K,
#define FFI_PICKD(K) (void *)ffi_d##K,
#define FFI_PICKF(K) (void *)ffi_f##K,
-static void *ffi_fn[3][FFI_CBS] = { { FFI_ALL(FFI_PICK) }, { FFI_ALL(FFI_PICKD) }, { FFI_ALL(FFI_PICKF) } };
+static VM_TLS void *ffi_fn[3][FFI_CBS] = { { FFI_ALL(FFI_PICK) }, { FFI_ALL(FFI_PICKD) }, { FFI_ALL(FFI_PICKF) } };
static Cell ffi_cbmake(Cell entry, Cell sig)
{
int cls = (sig & 15) == 2 ? 1 : (sig & 15) == 5 ? 2 : 0, k;
@@ -1611,8 +1761,8 @@ static Cell ffi_cbmake(Cell entry, Cell sig)
/* a native call the o3 lifter has resolved: the micro-ops narg and ncall carry
an index into this table. imm of narg is the argument's position */
typedef struct { void *f; Cell sig; int n; char name[48]; } Ncd;
-static Ncd ncd[256];
-static int nncd;
+static VM_TLS Ncd ncd[256];
+static VM_TLS int nncd;
static int ncd_get(Cell blk)
{
void *f;
@@ -1712,15 +1862,15 @@ static void rclear(Rg *R)
static void rdrop_all(void) { int j; for (j = 0; j < nrg; j++) if (rgs[j]->lifted) rclear(rgs[j]); }
/* lifting one block */
-static Rg *R_;
-static unsigned char *lead_;
-static int *bidx_, *wl_, nw_;
-static int lv_[RMAXV], lw_[RMAXV];
+static VM_TLS Rg *R_;
+static VM_TLS unsigned char *lead_;
+static VM_TLS int *bidx_, *wl_, nw_;
+static VM_TLS int lv_[RMAXV], lw_[RMAXV];
#define SB 1100
-static int skv[2 * SB], stp, slo, shi;
-static unsigned char skk[2 * SB];
-static int afp[MAXUOP * 4 + 16]; /* value -> 1 + its offset from fp, if it is fp + k */
-static int emap[MAXUOP * 4 + 16];
+static VM_TLS int skv[2 * SB], stp, slo, shi;
+static VM_TLS unsigned char skk[2 * SB];
+static VM_TLS int afp[MAXUOP * 4 + 16]; /* value -> 1 + its offset from fp, if it is fp + k */
+static VM_TLS int emap[MAXUOP * 4 + 16];
static int rvar(int v)
{
@@ -2201,7 +2351,7 @@ enum { C_UNSEEN, C_CONST, C_VAR, C_COPY };
/* C_COPY: the variable holds the same value as variable v (both at the same point) */
static int copy_ok(const Rg *R, int j, int k) { return j > 0 && j < RMAXV && k > 0 && k < R->nv && !R->fv[j] && !R->fv[k]; }
typedef struct { unsigned char st[RMAXV]; Cell v[RMAXV]; } CIn;
-static int g_isk[MAXUOP * 4]; static Cell g_kv[MAXUOP * 4]; static int g_omap[MAXUOP + 16];
+static VM_TLS int g_isk[MAXUOP * 4]; static VM_TLS Cell g_kv[MAXUOP * 4]; static VM_TLS int g_omap[MAXUOP + 16];
static int cjoin(CIn *d, const CIn *s)
{
int j, ch = 0;
@@ -2523,7 +2673,7 @@ static int opcost(int op)
#define GOP_PHIM (U_N + 5)
#define GNMAX (1 << 17)
typedef struct { int op, a, b, c, next; Cell imm; } GN;
-static GN *gn_; static int gn_n, *gh_, gn_over;
+static VM_TLS GN *gn_; static VM_TLS int gn_n, *gh_, gn_over;
static int gn_init(void)
{
if (!gn_) { gn_ = malloc(GNMAX * sizeof(GN)); gh_ = malloc(GNMAX * sizeof(int)); }
@@ -3143,11 +3293,11 @@ static Rg *rat(int p)
}
#ifdef ZYJIT
-static unsigned char *cov; /* how many live translations cover the word (255 sticks) */
-static void **tab; /* vm address -> translation */
+static VM_TLS unsigned char *cov; /* how many live translations cover the word (255 sticks) */
+static VM_TLS void **tab; /* vm address -> translation */
static void be_drop(int j) { tab[ix_blk[j].a] = 0; cdec(cov, ix_blk[j].a, ix_blk[j].b); if (trace) fprintf(stderr, "jit: dropped %d..%d\n", ix_blk[j].a, ix_blk[j].b - 1); }
-static unsigned *code, *cp, *x_enter, *x_exit, *x_disp, *x_first;
-static long S[14]; /* memory, stacks, hooks, limits, and the target saved on overflow */
+static VM_TLS unsigned *code, *cp, *x_enter, *x_exit, *x_disp, *x_first;
+static VM_TLS long S[15]; /* memory, stacks, hooks, limits, and the target saved on overflow */
static void o3_invoke_jit(Cell *ps, Cell *pr, Cell target, Cell retpc)
{
o3_invoke(ps, pr, target, retpc);
@@ -3240,7 +3390,11 @@ static void build_stubs(void)
li(W0, PC_DATA_OVERFLOW); b_to(x_exit); /* data stack overflow */
ovr = cp;
li(W0, PC_RETURN_OVERFLOW); b_to(x_exit); /* return stack overflow */
- x_disp = cp; /* w0 = target: check the stacks, chain through tab[] or fall back to c */
+ x_disp = cp; /* w0 = target: check cancellation and stacks before chaining */
+ if (context_current) {
+ ldrx_u(X9, ST, 112); E(0x88dffc00u | (X9 << 5) | X10);
+ unsigned *ready = cp; E(0x3400000a); li(W0, PC_HALT); b_to(x_exit); cb_fix(ready, cp);
+ }
str_u(0, ST, 104);
E(0xeb18029f); /* cmp x20, x24 */
E(0x54000003 | ((ovd - cp) & 0x7ffff) << 5); /* b.lo data overflow */
@@ -3573,7 +3727,7 @@ static void emit_uop(int op, int d, int a, int b, int c, int tmp)
/* an enhancement, inlined: inputs from the data stack into x0..x15, the
optimized micro-ops, outputs back. constants are made where they're used,
or folded into the instruction (a fixed address, a small addend). */
-static int ekc[MAXUOP * 4 + 16]; static Cell ekv[MAXUOP * 4 + 16];
+static VM_TLS int ekc[MAXUOP * 4 + 16]; static VM_TLS Cell ekv[MAXUOP * 4 + 16];
static void emit_enh_e(Enh *e, int s)
{
signed char *r = e->reg;
@@ -3618,7 +3772,7 @@ static int spec_enh(int s, Cell *ks, int nk, Enh *te)
{
Enh *e = &enh[s];
int nin = e->nin - nk, i, j, out[8], na;
- static int mp[MAXUOP * 4 + 16];
+ static VM_TLS int mp[MAXUOP * 4 + 16];
if (e->nv > MAXUOP * 4) return -1;
un = 0; unv = nin; uerr = 0;
for (j = 0; j < nin; j++) mp[j] = j;
@@ -3643,16 +3797,16 @@ static int spec_enh(int s, Cell *ks, int nk, Enh *te)
d8..d15 when the value is a float, so a chain of float ops does not move
through an integer register. constants have no register: they are made in
a scratch (x16/x17 or d16/d17) where they're used. */
-static signed char rreg[MAXUOP * 4 + 16];
-static unsigned char rk[MAXUOP * 4 + 16], rhome[MAXUOP * 4 + 16], rfp[MAXUOP * 4 + 16];
-static Cell rkv[MAXUOP * 4 + 16]; static int rlast[MAXUOP * 4 + 16];
-static unsigned **rfa; static int *rfb, rnf; /* branches to blocks, fixed up at the end */
+static VM_TLS signed char rreg[MAXUOP * 4 + 16];
+static VM_TLS unsigned char rk[MAXUOP * 4 + 16], rhome[MAXUOP * 4 + 16], rfp[MAXUOP * 4 + 16];
+static VM_TLS Cell rkv[MAXUOP * 4 + 16]; static VM_TLS int rlast[MAXUOP * 4 + 16];
+static VM_TLS unsigned **rfa; static VM_TLS int *rfb, rnf; /* branches to blocks, fixed up at the end */
#define LINKN 1024
-static void *linkstk[LINKN];
-static Cell jit_depth;
+static VM_TLS void *linkstk[LINKN];
+static VM_TLS Cell jit_depth;
typedef struct { void *stub; int fslots; int argc; } FnNat;
-static FnNat *fn_nat;
+static VM_TLS FnNat *fn_nat;
static void rforget(Rg *R)
{
int e;
@@ -3941,6 +4095,10 @@ static int remit(Rg *R, int bi)
int i, j, d, a, b, c;
unsigned *p;
if (ralloc(R, B)) return -1;
+ if (context_current) {
+ ldrx_u(X9, ST, 112); E(0x88dffc00u | (X9 << 5) | X10);
+ unsigned *ready = cp; E(0x3400000a); li(W0, PC_HALT); b_to(x_exit); cb_fix(ready, cp);
+ }
for (i = 0; i < B->n; i++) {
Uop *u = &B->u[i];
switch (u->op) {
@@ -4235,7 +4393,7 @@ static int vm_until(int pc, int stop_eq, int stop_ge, Rg *re, Cell *ps, Cell *pr
for (i = 0; i < nent; i++) { saved_ent[i] = tab[re->ent[i]]; tab[re->ent[i]] = 0; }
}
S[1] = *ps; S[2] = *pr;
- while ((unsigned)pc < (unsigned)code_words || pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW) {
+ while (!context_cancelled() && ((unsigned)pc < (unsigned)code_words || pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW)) {
void *c;
if (pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW) {
Cell sp = S[1], rp = S[2]; int ret = pc == PC_RETURN_OVERFLOW;
@@ -4313,35 +4471,35 @@ static int o3_exec_region(Rg *C, int entry, Cell *ps, Cell *pr)
#include <llvm-c/Target.h>
#include <llvm-c/Transforms/PassBuilder.h>
#include <llvm-c/TargetMachine.h>
-static int HOT = 5000; /* runs before a block gets compiled: ZYHOT */
-static const char *lpasses = "default<O2>"; /* ZYPASSES */
+static VM_TLS int HOT = 5000; /* runs before a block gets compiled: ZYHOT */
+static VM_TLS const char *lpasses = "default<O2>"; /* ZYPASSES */
#define LMAXB 65536
typedef Cell (*Blk)(Cell *mem, Cell *regs);
-static Blk *lt; /* address -> compiled block */
-static unsigned short *hot;
-static unsigned char *lcov; /* how many compiled blocks cover the word (255 sticks) */
-static void *lrt[MAXB]; /* each block's resource tracker: removing it frees the block's code */
-static int llvm_ok, nblocks;
-static LLVMOrcLLJITRef jit;
-static LLVMTargetMachineRef tm_; /* the host cpu, so the pass pipeline's cost models know the target */
-static LLVMOrcThreadSafeContextRef tsc;
-static LLVMContextRef ctx;
-static LLVMTypeRef i8t, i32t, i64t, ptrt, i1t, f64t;
-static LLVMBuilderRef bd;
-static LLVMValueRef rvv[RMAXV];
-static LLVMValueRef fn_, vm_, vregs, vs, vr; /* function, memory, regs, s and r slots */
-static LLVMTypeRef l_dret; /* what a register-passing function returns: the next address and s */
-static int l_dm; /* the function being built is the register-passing one */
-static Cell d_r; /* r, left here by a register-passing function that exits abnormally */
-static LLVMModuleRef mod_;
+static VM_TLS Blk *lt; /* address -> compiled block */
+static VM_TLS unsigned short *hot;
+static VM_TLS unsigned char *lcov; /* how many compiled blocks cover the word (255 sticks) */
+static VM_TLS void *lrt[MAXB]; /* each block's resource tracker: removing it frees the block's code */
+static VM_TLS int llvm_ok, nblocks;
+static VM_TLS LLVMOrcLLJITRef jit;
+static VM_TLS LLVMTargetMachineRef tm_; /* the host cpu, so the pass pipeline's cost models know the target */
+static VM_TLS LLVMOrcThreadSafeContextRef tsc;
+static VM_TLS LLVMContextRef ctx;
+static VM_TLS LLVMTypeRef i8t, i32t, i64t, ptrt, i1t, f64t;
+static VM_TLS LLVMBuilderRef bd;
+static VM_TLS LLVMValueRef rvv[RMAXV];
+static VM_TLS LLVMValueRef fn_, vm_, vregs, vs, vr; /* function, memory, regs, s and r slots */
+static VM_TLS LLVMTypeRef l_dret; /* what a register-passing function returns: the next address and s */
+static VM_TLS int l_dm; /* the function being built is the register-passing one */
+static VM_TLS Cell d_r; /* r, left here by a register-passing function that exits abnormally */
+static VM_TLS LLVMModuleRef mod_;
-static unsigned char *rcov; /* how many lifted o3 regions cover the word */
+static VM_TLS unsigned char *rcov; /* how many lifted o3 regions cover the word */
static int rdrop(int a);
/* code that is dropped is freed when no compiled code is running: a block can
drop itself (it stores into its own words), and a callback can nest */
-static int ccode;
-static LLVMOrcResourceTrackerRef pend_rt[1024];
-static int npend_rt;
+static VM_TLS int ccode;
+static VM_TLS LLVMOrcResourceTrackerRef pend_rt[1024];
+static VM_TLS int npend_rt;
static void rt_sweep(void)
{
LLVMErrorRef err;
@@ -4641,7 +4799,7 @@ static void llvm_init(void)
pair is two. no noalias: the host helpers it calls read and write that memory. */
static void lfunc_attrs(LLVMValueRef f)
{
- static unsigned kn, ka, kd, knu;
+ static VM_TLS unsigned kn, ka, kd, knu;
LLVMValueRef p0 = LLVMGetParam(f, 0);
(void)p0;
if (!kn) {
@@ -4881,12 +5039,12 @@ static Blk lcompile(int p)
/* ---- o3 regions without the arm64 jit: compiled by llvm, or run here ---- */
#ifndef ZYLLVM
-static unsigned char *rcov; /* 1 on the words of a lifted region */
+static VM_TLS unsigned char *rcov; /* 1 on the words of a lifted region */
#endif
-static Cell rvals[MAXUOP * 4 + 16];
+static VM_TLS Cell rvals[MAXUOP * 4 + 16];
#define RPOOL 40
-static Cell rpool[RPOOL][MAXUOP * 4 + 16];
-static int rpool_n;
+static VM_TLS Cell rpool[RPOOL][MAXUOP * 4 + 16];
+static VM_TLS int rpool_n;
static int rdrop(int a) /* a store landed on region code: drop those regions */
{
int j, hit = 0;
@@ -4913,6 +5071,7 @@ static int rexec(Rg *R, int e, int *ps, int *pr)
var[0] = fp = mread(ZFP);
for (j = 1; j < R->nv; j++) var[j] = mread(fp + R->slot[j]);
for (;;) {
+ if (context_cancelled()) { t = PC_HALT; break; }
RB *B = &R->b[b];
for (i = 0; i < B->n; i++) {
Uop *u = &B->u[i];
@@ -4986,7 +5145,7 @@ static void rcompile(Rg *R, int eager) { (void)R; (void)eager; }
mem2reg makes them ssa across blocks), each block's micro-ops as ir, then
the O3 pipeline. f(mem, regs, entry) returns the next vm address. */
typedef Cell (*RFn)(Cell *mem, Cell *regs, int ent);
-static int nregions;
+static VM_TLS int nregions;
static void rforget(Rg *R) { rt_queue(R->rt); R->rt = 0; R->nat = 0; R->dnat = 0; }
static void rmark(Rg *R) { R->marked = 1; cinc(rcov, R->lo, R->hi); cinc(lcov, R->lo, R->hi); }
static void runmark(Rg *R) { cdec(rcov, R->lo, R->hi); cdec(lcov, R->lo, R->hi); }
@@ -5149,7 +5308,7 @@ static LLVMValueRef lncall(int di, LLVMValueRef *ai, LLVMValueRef *af, LLVMValue
return res;
}
#endif
-static LLVMValueRef l_dfn; /* the register-passing function being built or declared for this region */
+static VM_TLS LLVMValueRef l_dfn; /* the register-passing function being built or declared for this region */
static int has_ret(Rg *R)
{
int b;
@@ -5163,12 +5322,12 @@ static int has_ret(Rg *R)
#define INL_MAX 8 /* copies in one module */
#define INL_BLOCKS 24 /* largest callee copied */
typedef struct { Rg *C; unsigned gen; LLVMValueRef fn; } InlEnt;
-static InlEnt inl_tab[INL_MAX];
-static int ninl, inl_depth;
+static VM_TLS InlEnt inl_tab[INL_MAX];
+static VM_TLS int ninl, inl_depth;
static void lbody(Rg *R, int direct, LLVMValueRef fnv);
static LLVMValueRef inline_fn(Rg *C, unsigned *gen)
{
- static int off = -1, inl_n;
+ static VM_TLS int off = -1, inl_n;
LLVMValueRef sfn, sdfn, svm, svregs, svs, svr, nf, srv[RMAXV];
LLVMBasicBlockRef at;
LLVMTypeRef dpt[6];
@@ -5504,7 +5663,7 @@ static void *lregion(Rg *R)
lfunc_attrs(f1);
dir = R->fn >= 0 && R->argc <= 3 && R->fslots >= 0 && !has_ret(R);
if (dir) {
- static unsigned knu;
+ static VM_TLS unsigned knu;
LLVMTypeRef rt2[2];
rt2[0] = i64t; rt2[1] = i64t;
l_dret = LLVMStructTypeInContext(ctx, rt2, 2, 0);
@@ -5540,9 +5699,9 @@ static void rcompile(Rg *R, int eager)
}
#endif
#define LD(x) (inmem(x) ? m[x] : 0)
-static int stop_eq = -1, stop_ge = -1, stop_return_depth = -1, run_nest;
-static Rg *stop_rg;
-static Cell run_s, run_r;
+static VM_TLS int stop_eq = -1, stop_ge = -1, stop_return_depth = -1, run_nest;
+static VM_TLS Rg *stop_rg;
+static VM_TLS Cell run_s, run_r;
static int run(int p)
{
int s = run_nest ? (int)run_s : DS0, r = run_nest ? (int)run_r : RS0, xfer = 1;
@@ -5550,9 +5709,10 @@ static int run(int p)
unsigned long long steps = 0;
#ifdef ZYLLVM
Cell regs[2];
- if (!getenv("ZYNOLLVM")) llvm_init();
+ /* worker contexts interpret regions; llvm ownership stays in its compiling context. */
+ if (!context_current && !getenv("ZYNOLLVM")) llvm_init();
#endif
- while ((unsigned)p < (unsigned)code_words) {
+ while ((unsigned)p < (unsigned)code_words && !context_cancelled()) {
if (run_nest) {
if (stop_eq >= 0 && p == stop_eq && r - (stack_control ? stack_get(SC_RETURN_LOW) : RS0) == stop_return_depth) break;
if (stop_ge >= 0 && p >= stop_ge) break;
@@ -5731,8 +5891,8 @@ static void b_commit(Cell w, Cell v)
}
#ifdef ZYBIGSTACK
-static ucontext_t uc_main, uc_vm;
-static int vm_rc;
+static VM_TLS ucontext_t uc_main, uc_vm;
+static VM_TLS int vm_rc;
static void vm_entry(void) { vm_rc = run(GORG); }
#endif
static int memory_option(const char *name, int fallback, int minimum)
@@ -5798,8 +5958,226 @@ static int code_range_live(Cell start, Cell count)
return 0;
}
+/* each worker owns a host thread, vm memory and translation state. */
+#if !defined(_WIN32) && !defined(__wasm__)
+typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel;
+static VM_TLS ContextChannel *context_channels;
+static atomic_llong context_id_sequence;
+struct VmContext {
+ ContextChannel parent_channel, worker_channel;
+ pthread_t thread;
+ struct VmContext *next;
+ atomic_int stop;
+ Cell token, entry, sp, rp, stack, words, limit, growth, code_size;
+ Cell frame_floor, frame_top, frame_guard;
+ Cell *memory;
+ int exit_code, requested_exit;
+ void *input_snapshot;
+ int argc, envc;
+ char **argv, **envp;
+#ifdef ZYFFI
+ unsigned char callback_snapshot[sizeof ffi_cb], symbol_snapshot[sizeof ffi_sym], library_snapshot[sizeof ffi_lib];
+ int ffi_symbols, ffi_libraries, ffi_libm;
+#endif
+ Enh enhancements[NENH];
+ Rg *regions[MAXRG];
+ int region_count;
+ unsigned short *entries;
+ unsigned char *entry_indices;
+};
+static int context_descriptor(Cell handle) {
+ ContextChannel *channel;
+ if (handle < CONTEXT_HANDLE_LO) return handle >= 0 && handle <= 0x7fffffff ? (int)handle : -1;
+ for (channel = context_channels; channel; channel = channel->next) if (channel->handle == handle) return channel->fd;
+ return -1;
+}
+static int context_channel_close(Cell handle) {
+ ContextChannel **link = &context_channels, *channel;
+ while (*link && (*link)->handle != handle) link = &(*link)->next;
+ if (!*link) return -1; channel = *link; *link = channel->next;
+ if (channel->fd < 0) return -1;
+ int result = close(channel->fd); channel->fd = -1; return result;
+}
+static void context_snapshot_free(VmContext *context) {
+ int i;
+ for (i = 0; i < NENH; i++) { free(context->enhancements[i].u); free(context->enhancements[i].reg); }
+ for (i = 0; i < context->region_count; i++) free(context->regions[i]);
+ free(context->input_snapshot); free(context->entries); free(context->entry_indices); if (context->memory) { free(context->memory); atomic_fetch_sub(&context_memory_live, 1); } context->memory = 0;
+}
+static int context_cancelled(void) { return context_current && atomic_load_explicit(&context_current->stop, memory_order_acquire); }
+static void context_exit(int value) { context_current->exit_code = value; context_current->requested_exit = 1; atomic_store_explicit(&context_current->stop, 1, memory_order_release); }
+static void context_engine_free(void) {
+ int i;
+ for (i = 0; i < nrg; i++) { rclear(rgs[i]); free(rgs[i]); }
+ for (i = 0; i < NENH; i++) { free(enh[i].u); free(enh[i].reg); }
+ free(pg_head); free(o3ent); free(o3ei); free(gn_); free(gh_);
+#ifdef ZYJIT
+ free(tab); free(fn_nat); free(cov); if (code && code != MAP_FAILED && munmap(code, CODEW * 4) == 0) atomic_fetch_sub(&context_code_live, 1);
+#else
+ free(rcov);
+#ifdef ZYLLVM
+ free(lcov); free(lt); free(hot);
+#endif
+#endif
+ free(m); m = 0; atomic_fetch_sub(&context_memory_live, 1);
+}
+static void *context_worker(void *opaque) {
+ VmContext *context = opaque;
+ Cell sp = context->sp, rp = context->rp, entry = context->entry;
+ int i, back;
+ optimizer_storage();
+ context_current = context; io_context_restore(context->input_snapshot); free(context->input_snapshot); context->input_snapshot = 0; trace = getenv("ZYTRACE") != 0;
+ context->worker_channel.next = context_channels; context_channels = &context->worker_channel;
+ m = context->memory; context->memory = 0;
+ memory_limit = (int)context->limit; memory_words = (int)context->words; memory_growth_limit = (int)context->growth;
+ stack_control = context->stack; frame_floor_word = context->frame_floor; frame_top_word = context->frame_top; frame_guard_word = context->frame_guard;
+ zy_argc = context->argc; zy_argv = context->argv; zy_envc = context->envc; zy_envp = context->envp;
+#ifdef ZYFFI
+ memcpy(ffi_cb, context->callback_snapshot, sizeof ffi_cb); memcpy(ffi_sym, context->symbol_snapshot, sizeof ffi_sym); memcpy(ffi_lib, context->library_snapshot, sizeof ffi_lib);
+ ffi_nsym = context->ffi_symbols; ffi_nlib = context->ffi_libraries; ffi_tried_libm = context->ffi_libm;
+#endif
+#ifdef ZYJIT
+ cov = calloc((size_t)MW, 1);
+#else
+ rcov = calloc((size_t)MW, 1);
+#ifdef ZYLLVM
+ lcov = calloc((size_t)MW, 1);
+#endif
+#endif
+#ifdef ZYJIT
+ if (!cov) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; }
+#else
+ if (!rcov
+#ifdef ZYLLVM
+ || !lcov
+#endif
+ ) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; }
+#endif
+ if (code_reserve(context->code_size) < 0) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; }
+ memcpy(enh, context->enhancements, sizeof enh); memset(context->enhancements, 0, sizeof context->enhancements);
+ for (i = 0; i < context->region_count; i++) { rgs[i] = context->regions[i]; context->regions[i] = 0; }
+ nrg = context->region_count; context->region_count = 0;
+ memcpy(o3ent, context->entries, (size_t)code_words * sizeof *o3ent); memcpy(o3ei, context->entry_indices, (size_t)code_words);
+ free(context->entries); context->entries = 0; free(context->entry_indices); context->entry_indices = 0;
+#ifdef ZYJIT
+#ifdef __APPLE__
+ code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0);
+#else
+ code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+#endif
+ if (code == MAP_FAILED) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; }
+ atomic_fetch_add(&context_code_live, 1);
+ jitwrite(1); build_stubs(); jitwrite(0); syncicache(code, cp);
+ S[0] = (long)m; S[1] = sp; S[2] = rp; S[3] = (long)tab; S[4] = (long)cov;
+ S[5] = (long)Q; S[6] = (long)G; S[7] = (long)&J; S[8] = (long)hook;
+ S[9] = (long)inst_hook; S[10] = (long)xrun; S[11] = (long)(m + stack_data_limit()); S[12] = (long)(m + stack_return_limit());
+ S[14] = (long)&context->stop; cov[SYSGATE] = 1;
+#endif
+ if (entry >= HEAP_LO && inmem(entry) && m[entry - 3] == (entry ^ 0x2B7E1516)) { m[--sp] = entry + 3; entry = m[entry]; }
+ m[rp++] = PC_HALT;
+ back = vm_until((int)entry, PC_HALT, -1, 0, &sp, &rp);
+ if (!context->requested_exit) context->exit_code = context_cancelled() ? 0 : stopped(back) ? -1 : inmem(sp) ? (int)m[sp] : 0;
+ context_channel_close(context->worker_channel.handle);
+ while (context_children) context_join(context_children->token, 1);
+ context_engine_free(); context_current = 0; return 0;
+}
+static Cell context_start(Cell entry, Cell channel) {
+ VmContext *context; int fd[2], i; pthread_attr_t attributes; Cell saved;
+ if (entry < 0 || (!inmem(entry)) || !inmem(channel)) return -1;
+ if (entry >= HEAP_LO && (!inmem(entry - 3) || m[entry - 3] != (entry ^ 0x2B7E1516))) return -1;
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ context = calloc(1, sizeof *context); if (!context) { close(fd[0]); close(fd[1]); return -1; }
+ context->entry = entry; context->words = memory_words; context->limit = memory_limit; context->growth = memory_growth_limit; context->code_size = code_words;
+ context->sp = cb_s; context->rp = cb_r; context->stack = stack_control; context->frame_floor = frame_floor_word; context->frame_top = frame_top_word; context->frame_guard = frame_guard_word;
+ context->argc = zy_argc; context->argv = zy_argv; context->envc = zy_envc; context->envp = zy_envp;
+ context->token = atomic_fetch_add(&context_id_sequence, 1) + 1;
+ context->parent_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_id_sequence, 1) + 1;
+ context->worker_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_id_sequence, 1) + 1;
+ context->parent_channel.fd = fd[0]; context->worker_channel.fd = fd[1]; atomic_init(&context->stop, 0);
+ context->input_snapshot = malloc(io_context_size());
+ if (!context->input_snapshot) goto failed; io_context_snapshot(context->input_snapshot);
+ context->memory = calloc((size_t)memory_limit, sizeof(Cell));
+ if (context->memory) atomic_fetch_add(&context_memory_live, 1);
+ context->entries = malloc((size_t)code_words * sizeof *o3ent); context->entry_indices = malloc((size_t)code_words);
+ if (!context->memory || !context->entries || !context->entry_indices) goto failed;
+ saved = m[channel]; m[channel] = context->worker_channel.handle; memcpy(context->memory, m, (size_t)memory_words * sizeof(Cell)); m[channel] = saved;
+ memcpy(context->entries, o3ent, (size_t)code_words * sizeof *o3ent); memcpy(context->entry_indices, o3ei, (size_t)code_words);
+ for (i = 0; i < NENH; i++) {
+ context->enhancements[i] = enh[i]; context->enhancements[i].u = 0; context->enhancements[i].reg = 0;
+ if (enh[i].u) { context->enhancements[i].u = malloc((size_t)(enh[i].n + 1) * sizeof(Uop)); if (!context->enhancements[i].u) goto failed; memcpy(context->enhancements[i].u, enh[i].u, (size_t)(enh[i].n + 1) * sizeof(Uop)); }
+ if (enh[i].reg) { context->enhancements[i].reg = malloc((size_t)enh[i].nv); if (!context->enhancements[i].reg) goto failed; memcpy(context->enhancements[i].reg, enh[i].reg, (size_t)enh[i].nv); }
+ }
+ for (i = 0; i < nrg; i++) {
+ Rg *region = malloc(sizeof *region); if (!region) goto failed; *region = *rgs[i];
+ region->b = 0; region->nb = 0; region->lifted = 0; region->marked = 0; region->nat = 0; region->dnat = 0; region->rt = 0; memset(region->stub, 0, sizeof region->stub);
+ context->regions[context->region_count++] = region;
+ }
+#ifdef ZYFFI
+ memcpy(context->callback_snapshot, ffi_cb, sizeof ffi_cb); memcpy(context->symbol_snapshot, ffi_sym, sizeof ffi_sym); memcpy(context->library_snapshot, ffi_lib, sizeof ffi_lib);
+ context->ffi_symbols = ffi_nsym; context->ffi_libraries = ffi_nlib; context->ffi_libm = ffi_tried_libm;
+#endif
+ pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 16u << 20);
+ i = pthread_create(&context->thread, &attributes, context_worker, context); pthread_attr_destroy(&attributes);
+ if (i) goto failed;
+ context->parent_channel.next = context_channels; context_channels = &context->parent_channel;
+ atomic_fetch_add(&context_records_live, 1);
+ m[channel] = context->parent_channel.handle; context->next = context_children; context_children = context; return context->token;
+failed:
+ context_snapshot_free(context); free(context); close(fd[0]); close(fd[1]); return -1;
+}
+static Cell context_join(Cell token, int cancel) {
+ VmContext **link = &context_children, *context; int result;
+ while (*link && (*link)->token != token) link = &(*link)->next;
+ if (!*link) return -1; context = *link;
+ if (cancel) { atomic_store_explicit(&context->stop, 1, memory_order_release); if (context->parent_channel.fd >= 0) shutdown(context->parent_channel.fd, SHUT_RDWR); }
+ if (pthread_join(context->thread, 0)) return -1;
+ if (context->parent_channel.fd >= 0) context_channel_close(context->parent_channel.handle); result = context->exit_code; *link = context->next;
+ context_snapshot_free(context); free(context); atomic_fetch_sub(&context_records_live, 1); return result;
+}
+#else
+static int context_descriptor(Cell handle) { return -1; }
+static int context_channel_close(Cell handle) { return -1; }
+static int context_cancelled(void) { return 0; }
+static void context_exit(int value) { }
+static Cell context_start(Cell entry, Cell channel) { return -1; }
+static Cell context_join(Cell token, int cancel) { return -1; }
+#endif
+
int main(int argc, char **argv)
{
+ if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
+#ifdef USE_LSP_GENOME
+ fputs("usage: zclsp [--format|--annotate|--types|--help|-h] < input\n"
+ "reads language-server protocol messages from stdin and writes responses to stdout.\n"
+ "uses content-length framing; plain source on stdin runs a diagnostic check.\n",
+ stdout);
+ fputs("source options:\n"
+ " --format print formatted source\n"
+ " --annotate print source with inferred types\n"
+ " --types list inferred type edits as line:column length text\n", stdout);
+#else
+ fputs("usage: zy [program arguments] < source.zc\n"
+ "compiles source from stdin and runs main; input after a backtick is program input.\n"
+ "program arguments are available through std.argv; stdin has the name <eval>.\n",
+ stdout);
+#endif
+ fputs("options:\n --help, -h show this help and exit\n"
+ "environment:\n"
+ " TINYVM_MEMORY_WORDS initial vm memory in words\n"
+ " TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n"
+ " ZYTRACE trace native translation and optimization\n", stdout);
+ return 0;
+ }
+ if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') {
+#ifdef USE_LSP_GENOME
+ if (strcmp(argv[1], "--format") && strcmp(argv[1], "--annotate") && strcmp(argv[1], "--types")) {
+#endif
+ fprintf(stderr, "tinyvm: unknown option %s; use --help for usage\n", argv[1]);
+ return 2;
+#ifdef USE_LSP_GENOME
+ }
+#endif
+ }
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
@@ -5848,7 +6226,7 @@ int main(int argc, char **argv)
syncicache(code, cp);
S[0] = (long)m; S[1] = DS0; S[2] = RS0; S[3] = (long)tab; S[4] = (long)cov;
S[5] = (long)Q; S[6] = (long)G; S[7] = (long)&J; S[8] = (long)hook;
- S[9] = (long)inst_hook; S[10] = (long)xrun;
+ S[9] = (long)inst_hook; S[10] = (long)xrun; S[14] = (long)&context_default_stop;
S[11] = (long)(m + DSLIM); S[12] = (long)(m + RSLIM);
cov[SYSGATE] = 1;
enter.u = x_enter;
diff --git a/src/core/core.zc b/src/core/core.zc
new file mode 100644
index 0000000..61ecd1c
--- /dev/null
+++ b/src/core/core.zc
@@ -0,0 +1,19 @@
+namespace core;
+include rt;
+
+// calls the host gate with an operation and three machine-word arguments.
+// returns the host result; negative values indicate a failed operation.
+int syscall(int operation, word a, word b, word c) {
+ return rt.syscall(operation, a, b, c);
+}
+
+// the current operand-stack pointer, expressed as a word address.
+word dsp() { return rt.dsp(); }
+// converts a vm word address to its byte address.
+word addr_b(word address) { return rt.addr_b(address); }
+// loads one unsigned byte from a byte address.
+int ld8(word address) { return rt.ld8(address); }
+// stores the low byte of value at a byte address.
+int st8(word address, int value) { return rt.st8(address, value); }
+// stops execution of the current vm program.
+int halt() { return rt.halt(); }
diff --git a/src/core/io_linux.c b/src/core/io_linux.c
index 8a024aa..aaf6a18 100644
--- a/src/core/io_linux.c
+++ b/src/core/io_linux.c
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <string.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
@@ -7,7 +8,12 @@
#include <poll.h>
#endif
-int J;
+#if !defined(_WIN32) && !defined(__wasm__)
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
+VM_TLS int J;
#ifdef _WIN32
static int pk(void){int c=getchar();ungetc(c,stdin);return c==EOF;}
@@ -19,8 +25,8 @@ char Q(void){int c=getchar();J=pk();return c;}
one would wait for a message that comes only after the answer. so J is 1 only
when the end is known, and 0 when more may come. the output is flushed before
any read that can wait, for the same reason. */
-static unsigned char ib[65536];
-static int ip, in, ieof;
+static VM_TLS unsigned char ib[65536];
+static VM_TLS int ip, in, ieof;
/* make a byte available. wait is 0 to look without waiting. returns 1 when one is ready */
static int fill(int wait)
{
@@ -46,6 +52,27 @@ char Q(void)
return (char)c;
}
#endif
+/* 1 when a byte can be read without waiting: buffered, or already on the pipe */
+int input_pending(void)
+{
+#ifdef _WIN32
+ return 0;
+#else
+ return fill(0);
+#endif
+}
+#ifndef _WIN32
+struct IoSnapshot { unsigned char bytes[65536]; int position, count, ended, flag; };
+size_t io_context_size(void) { return sizeof(struct IoSnapshot); }
+void io_context_snapshot(void *destination) {
+ struct IoSnapshot *snapshot = destination;
+ memcpy(snapshot->bytes, ib, sizeof ib); snapshot->position = ip; snapshot->count = in; snapshot->ended = ieof; snapshot->flag = J;
+}
+void io_context_restore(const void *source) {
+ const struct IoSnapshot *snapshot = source;
+ memcpy(ib, snapshot->bytes, sizeof ib); ip = snapshot->position; in = snapshot->count; ieof = snapshot->ended; J = snapshot->flag;
+}
+#endif
void G(char c){putchar(c);}
static void init_io(void) {
diff --git a/src/core/io_win.c b/src/core/io_win.c
index e197353..073022e 100644
--- a/src/core/io_win.c
+++ b/src/core/io_win.c
@@ -6,12 +6,14 @@
int J;
static int pk(void) { int c = _fgetc_nolock(stdin); if (c != EOF) _ungetc_nolock(c, stdin); return c == EOF; }
char Q(void) { int c = _fgetc_nolock(stdin); J = pk(); return (char)c; }
+int input_pending(void) { return 0; }
void G(char c) { _fputc_nolock(c, stdout); }
static void init_io(void) {
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
- J = pk();
+ /* the first read establishes eof; startup must not read stdin. */
+ J = 0;
}
#if defined(__GNUC__) || defined(__clang__)
diff --git a/src/core/memmap.h b/src/core/memmap.h
index b2ee787..cc768d4 100644
--- a/src/core/memmap.h
+++ b/src/core/memmap.h
@@ -1,29 +1,10 @@
-/* machine map, in words. the default has the code area, the compiler tables,
- the genome and the stacks first (PW words in all) and the heap last: it
- starts at PW and grows at run time, up to MW words of memory. MW is the
- host's memory size; the array behind it is untouched until the heap grows
- into it. a smaller board is a different build: src/tools/genmap writes a
- directory whose memmap.h and genome agree, and the C cores are compiled
- against that directory. do not edit the numbers by hand in one file only.
-
- include genome.h before this header when both are in the translation
- unit. the linked image's GORG then wins; it has to be the same value
- this header would have defined.
-
- PW is the first word past the initial stacks. emitted code can add heap
- segments, and native code-address tables grow with those reservations.
- MW bounds data addresses. a fixed map (HEAP_GROW 0) has MW == PW.
-
- DSLIM and RSLIM are the interpreter's stack checks. a straight-line
- block can push about 1024 words after the check, which is why the data
- limit sits 1100 words above DSLO. the genome's prologue checks the current frame segment and adds another
- segment when needed. */
+/* generated by src/tools/genmap for the default profile. do not edit. */
#ifndef MEMMAP_H
#define MEMMAP_H
-#define PW 0x1C5000
+#define PW 0x1D5000
#define CODE_END 0x80000
-#define HEAP_LO 0x1C5000
-#define HEAP_HI 0x225000
+#define HEAP_LO 0x1D5000
+#define HEAP_HI 0x235000
#define HEAP_GROW 1
#ifndef MW
#define MW 0x8000000
@@ -39,13 +20,13 @@
#else
#define GORG 0x114100
#endif
-#define FBOT 0x164100
-#define FTOP 0x1A4100
-#define RS0 0x1A4100
-#define MARK0 0x1B4100
+#define FBOT 0x174100
+#define FTOP 0x1B4100
+#define RS0 0x1B4100
+#define MARK0 0x1C4100
#define MARKN 0x8000
-#define DSLO 0x1BC100
-#define DSHI 0x1C4FF0
+#define DSLO 0x1CC100
+#define DSHI 0x1D4FF0
#define DS0 DSHI
#define SYSGATE (ZFP + 2)
#define DSLIM (DSLO + 1100)
diff --git a/src/core/stack.h b/src/core/stack.h
index 49f347c..f699b52 100644
--- a/src/core/stack.h
+++ b/src/core/stack.h
@@ -4,8 +4,11 @@
stack globals once the heap is ready. addresses are vm word indices. */
#ifndef TINYVM_STACK_H
#define TINYVM_STACK_H
+#ifndef VM_TLS
+#define VM_TLS
+#endif
-static Cell stack_control;
+static VM_TLS Cell stack_control;
enum {
SC_FRAME_FLOOR, SC_FRAME_TOP, SC_FRAME_GUARD,
SC_DATA_LOW, SC_DATA_TOP, SC_DATA_NODE,
diff --git a/src/js/extract_genome.js b/src/js/extract_genome.js
index 5cb2b38..89bebe3 100644
--- a/src/js/extract_genome.js
+++ b/src/js/extract_genome.js
@@ -1,5 +1,13 @@
const fs = require('fs');
const path = require('path');
+if (process.argv.length > 2) {
+ if (process.argv.length === 3 && ['--help', '-h'].includes(process.argv[2])) {
+ console.log('usage: node src/js/extract_genome.js [--help|-h]\nconverts src/core/genome.h and memmap.h into the javascript genome and layout files.');
+ process.exit(0);
+ }
+ console.error('extract_genome: unexpected arguments; use --help for usage');
+ process.exit(2);
+}
const srcPath = path.join(__dirname, '..', 'core', 'genome.h');
const outPath = path.join(__dirname, 'genome.json');
diff --git a/src/js/extract_lsp.js b/src/js/extract_lsp.js
index 6f53ebc..358d404 100644
--- a/src/js/extract_lsp.js
+++ b/src/js/extract_lsp.js
@@ -3,6 +3,14 @@
// the vm needs the layout, because the server's map is not the compiler's.
const fs = require('fs');
const path = require('path');
+if (process.argv.length > 2) {
+ if (process.argv.length === 3 && ['--help', '-h'].includes(process.argv[2])) {
+ console.log('usage: node src/js/extract_lsp.js [--help|-h]\nconverts build/lsp/genome.h and its memory map into src/js/lsp.json; build the lsp first.');
+ process.exit(0);
+ }
+ console.error('extract_lsp: unexpected arguments; use --help for usage');
+ process.exit(2);
+}
const root = path.join(__dirname, '..', '..');
const genome = fs.readFileSync(path.join(root, 'build', 'lsp', 'genome.h'), 'utf8');
diff --git a/src/js/tinyvm.ts b/src/js/tinyvm.ts
index ab1e4da..b6146fb 100644
--- a/src/js/tinyvm.ts
+++ b/src/js/tinyvm.ts
@@ -171,6 +171,9 @@ export class TinyVM {
private inputBuffer: Uint8Array = new Uint8Array(0);
private inputPos: number = 0;
+ /** true while a streaming run is suspended by a poll (syscall 42) rather than by a read: the host
+ should resume it after its event loop has run, whether or not input came. */
+ public polling: boolean = false;
private outputBuffer: number[] = [];
private onOutput?: (byte: number) => void;
private onMessage?: (msg: string) => void;
@@ -429,6 +432,14 @@ export class TinyVM {
}
return -1n;
}
+ case 42: // poll: input is waiting
+ return this.inputPos < this.inputBuffer.length ? 1n : 0n;
+ case 41: { // shortest float text into a word buffer
+ const text = formatFloat(a, 0n, 17n);
+ if (b < 0n || c <= BigInt(text.length) || b > BigInt(this.mw) || c > BigInt(this.mw) - b) return -1n;
+ for (let i = 0; i < text.length; i++) this.mem[Number(b) + i] = BigInt(text.charCodeAt(i));
+ this.mem[Number(b) + text.length] = 0n; return BigInt(text.length);
+ }
case 18: { // fill heap words without running one vm store per word
if (a < BigInt(this.L.HEAP_LO) || c <= 0n || a > BigInt(this.mw) || c > BigInt(this.mw) - a) return -1n;
this.mem.fill(b, Number(a), Number(a + c)); return c;
@@ -721,6 +732,14 @@ export class TinyVM {
break;
case 2: // st
if (Number(a) === this.L.SYSGATE) {
+ if (b === 42n && this.streaming) {
+ // poll: suspend once so the host can deliver what has arrived, then say if input waits
+ if (!this.polling) { this.polling = true; this.rp = p - 1; this.rs = s; this.rr = r; this.blocked = true; return BLOCKED; }
+ this.polling = false;
+ mem[Number(a)] = this.inputPos < this.inputBuffer.length ? 1n : 0n;
+ s += 2;
+ break;
+ }
this.rs = s + 2; this.rr = r;
const result = this.syscall(b, mem[Number(a) + 1], mem[Number(a) + 2], mem[Number(a) + 3]);
mem = this.mem; bytes = this.bytes; view = this.view; mw = this.mw; // a grow may have moved them
@@ -898,6 +917,20 @@ export class TinyVM {
// command line runner: node src/js/tinyvm.js <file.zc>, or the source on stdin
if (typeof require === 'function' && typeof module !== 'undefined' && require.main === module) {
+ const option = process.argv[2];
+ if (option === '--help' || option === '-h') {
+ process.stdout.write('usage: node src/js/tinyvm.js [file.zc]\n' +
+ 'compiles and runs the file, or reads source from stdin when no file is supplied.\n' +
+ 'input after a backtick is program input.\n' +
+ 'options:\n --help, -h show this help and exit\n' +
+ 'environment:\n TINYVM_MEMORY_WORDS initial vm memory in words\n' +
+ ' TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n');
+ process.exit(0);
+ }
+ if (option && option.startsWith('-')) {
+ process.stderr.write('tinyvm.js: unknown option ' + option + '; use --help for usage\n');
+ process.exit(2);
+ }
const genomePath = path.join(__dirname, 'genome.json');
if (!fs.existsSync(genomePath)) {
console.error('Error: genome.json not found. Run extract_genome.js first.');
diff --git a/src/lib/actor.zc b/src/lib/actor.zc
new file mode 100644
index 0000000..29fd600
--- /dev/null
+++ b/src/lib/actor.zc
@@ -0,0 +1,280 @@
+namespace actor;
+include rt, run;
+
+// stream entries are raw words: integer payloads never acquire heap ownership.
+unchecked word vector() { return rt.dnew(1, 0, 0); }
+unchecked int append(word vector, word value) {
+ rt.dgrow(vector, vector[0] + 1); vector[2][vector[0]] = value; vector[0]++; return 0;
+}
+unchecked int find(word refs, word value) {
+ int i = 0; while (i < refs[0]) { if (refs[2][i] == value) return i; i++; } return -1;
+}
+unchecked int size(word type) { return type[1]; }
+unchecked int packed(word type) { if (type[0] == 1 || type[0] == 12) return type[5]; return 0; }
+
+// encode pointer graphs with identity records; decoding creates fresh objects.
+// typed fields determine references, so an integer equal to a heap address
+// remains an integer. repeated references and cycles preserve identity.
+unchecked int encode(word data, word refs, word address, word type) {
+ int kind = type[0], i = 0, offset, bytes, n, id; word pointer, element;
+ if (kind == 8) {
+ while (i < type[3]) {
+ element = type[4][i * 3 + 1]; offset = type[4][i * 3 + 2];
+ if (offset < 0) { bytes = packed(element); n = 0; int k = 0; while (k < bytes) { n |= rt.ld8(rt.addr_b(address) - offset - 1 + k) << (k * 8); k++; } append(data, n); }
+ else encode(data, refs, address + offset, element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind == 6) {
+ element = type[4]; bytes = packed(element);
+ while (i < type[3]) {
+ if (bytes) { n = 0; int k = 0; while (k < bytes) { n |= rt.ld8(rt.addr_b(address) + i * bytes + k) << (k * 8); k++; } append(data, n); }
+ else encode(data, refs, address + i * size(element), element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind != 5 && kind != 11 && kind != 13 && kind != 7) { append(data, *address); return 0; }
+ pointer = *address;
+ if (!pointer) { append(data, 0); if (kind == 7) append(data, 0); return 0; }
+ if (kind == 7 && rt.callable_code(pointer)) { append(data, 0); append(data, pointer); return 0; }
+ id = find(refs, pointer);
+ if (id >= 0) { append(data, id + 1); return 0; }
+ id = refs[0]; append(refs, pointer); append(data, 0 - id - 1);
+ if (kind == 11) {
+ n = pointer[0]; append(data, n); element = type[4]; bytes = packed(element);
+ while (i < n) {
+ if (bytes) { int k = 0; int value = 0; while (k < bytes) { value |= rt.ld8(rt.addr_b(pointer[2]) + i * bytes + k) << (k * 8); k++; } append(data, value); }
+ else encode(data, refs, pointer[2] + i * size(element), element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind == 13) { n = rt.len_s(pointer); append(data, n); while (i < n) { append(data, rt.at_s(pointer, i)); i++; } return 0; }
+ if (kind == 7) {
+ element = pointer[-1]; n = pointer[-5] - rt.HDR;
+ append(data, n); append(data, element); encode(data, refs, pointer, element); return 0;
+ }
+ element = type[4]; bytes = packed(element);
+ if (rt.isobj(pointer)) n = pointer[-5] - rt.HDR;
+ else if (element[0] == 2) n = rt.len_w(pointer) + 1;
+ else n = size(element);
+ append(data, n);
+ if (bytes || size(element) <= 0) { while (i < n) { append(data, pointer[i]); i++; } }
+ else { while (i + size(element) <= n) { encode(data, refs, pointer + i, element); i += size(element); } }
+ return 0;
+}
+unchecked word take(word frame) { word result = frame[0][2][frame[1]]; frame[1]++; return result; }
+unchecked int decode(word frame, word refs, word address, word type) {
+ int kind = type[0], i = 0, offset, bytes, n, id; word pointer, element;
+ if (kind == 8) {
+ while (i < type[3]) {
+ element = type[4][i * 3 + 1]; offset = type[4][i * 3 + 2];
+ if (offset < 0) { bytes = packed(element); word value = take(frame); int k = 0; while (k < bytes) { rt.st8(rt.addr_b(address) - offset - 1 + k, value >> (k * 8)); k++; } }
+ else decode(frame, refs, address + offset, element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind == 6) {
+ element = type[4]; bytes = packed(element);
+ while (i < type[3]) {
+ if (bytes) { word value = take(frame); int k = 0; while (k < bytes) { rt.st8(rt.addr_b(address) + i * bytes + k, value >> (k * 8)); k++; } }
+ else decode(frame, refs, address + i * size(element), element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind != 5 && kind != 11 && kind != 13 && kind != 7) { *address = take(frame); return 0; }
+ id = take(frame);
+ if (!id) { if (kind == 7) *address = take(frame); else *address = 0; return 0; }
+ if (id > 0) { *address = refs[2][id - 1]; return 0; }
+ n = take(frame);
+ if (kind == 11) {
+ element = type[4]; bytes = packed(element);
+ if (bytes) pointer = rt.dnew(0 - bytes, n, type); else pointer = rt.dnew(size(element), n, type);
+ append(refs, pointer); *address = pointer;
+ while (i < n) {
+ if (bytes) { word value = take(frame); int k = 0; while (k < bytes) { rt.st8(rt.addr_b(pointer[2]) + i * bytes + k, value >> (k * 8)); k++; } }
+ else decode(frame, refs, pointer[2] + i * size(element), element);
+ i++;
+ }
+ return 0;
+ }
+ if (kind == 13) {
+ pointer = rt.new((n + 7) / 8 + 1); pointer[0] = n; append(refs, pointer); *address = pointer;
+ while (i < n) { rt.st8(rt.addr_b(pointer + 1) + i, take(frame)); i++; } return 0;
+ }
+ if (kind == 7) { element = take(frame); pointer = rt.new_t(n, element); append(refs, pointer); *address = pointer; decode(frame, refs, pointer, element); return 0; }
+ pointer = rt.new_t(n, type[4]); append(refs, pointer); *address = pointer;
+ element = type[4]; bytes = packed(element);
+ if (bytes || size(element) <= 0) { while (i < n) { pointer[i] = take(frame); i++; } }
+ else { while (i + size(element) <= n) { decode(frame, refs, pointer + i, element); i += size(element); } }
+ return 0;
+}
+unchecked int write_message(int fd, word address, word type) {
+ word data = vector(), refs = vector(); int count;
+ encode(data, refs, address, type); count = data[0];
+ int result = rt.syscall(44, fd, &count, 1);
+ if (result >= 0) result = rt.syscall(44, fd, data[2], count);
+ rt.free(refs); rt.free(data); return result;
+}
+unchecked int read_message(int fd, word address, word type) {
+ int count = 0; word data, refs, frame[2];
+ if (rt.syscall(45, fd, &count, 1) < 0 || count < 0 || count > 1048576) return -1;
+ data = rt.dnew(1, count, 0); refs = vector(); frame[0] = data; frame[1] = 0;
+ if (rt.syscall(45, fd, data[2], count) < 0) { rt.free(refs); rt.free(data); return -1; }
+ decode(frame, refs, address, type); rt.free(refs); rt.free(data); return 0;
+}
+
+int busy, worker;
+type Dispatcher = fn();
+Dispatcher[?] dispatchers;
+unchecked int dispatch() {
+ if (busy || worker || !dispatchers) return 0;
+ busy = 1; int i = 0;
+ while (i < len(dispatchers)) { if (dispatchers[i]) dispatchers[i](); i++; }
+ busy = 0; return 0;
+}
+
+// declared actors expose a typed handle method and receive their own endpoint.
+// lowering lives in this imported source; the compiler only dispatches callbacks.
+unchecked int declaration_parameters(word type) {
+ if (type[0] != run.TY_GEN) return 0;
+ rt.put_c('<'); int i = 0;
+ while (i < type[3]) { if (i) rt.put_c(','); rt.put_s(type[6][i]); i++; }
+ rt.put_c('>'); return 0;
+}
+unchecked int declaration_type(word type) { rt.put_s(type[2]); declaration_parameters(type); return 0; }
+unchecked int declaration_method(word type, char* method) {
+ rt.put_s("unchecked fn "); rt.put_s(type[2]); rt.put_c('.'); rt.put_s(method); declaration_parameters(type); rt.put_c('('); declaration_type(type); rt.put_s("* this"); return 0;
+}
+unchecked int parse_actor() {
+ int symbol = run.kw_struct(); word type = run.sval[symbol], signature, fields, oldfields;
+ int handler, count, mode, length, capacity; word output, previous_grow; char* buffer;
+ if (type[0] == run.TY_GEN && rt.eq_w(type[2], "actor.Actor")) return 0;
+ handler = run.methof(type, run.intern("handle"));
+ if (handler < 0) { run.serr("an actor declaration requires handle(input) with a typed result"); return 0; }
+ signature = run.stype[handler];
+ if (!signature || signature[3] != 2) { run.serr("an actor handle method requires one input parameter"); return 0; }
+ int generic = type[0] == run.TY_GEN, stride = 3 - generic;
+ count = type[3]; if (generic) count = type[5]; int field_index = 0;
+ while (field_index < count) { if (type[4][field_index * stride] == run.intern("__mailbox")) { run.serr("__mailbox is reserved for actor state"); return 0; } field_index++; }
+ oldfields = type[4]; fields = run.talloc((count + 1) * stride + 1);
+ rt.copy_w(fields, oldfields, count * stride); fields[count * stride] = run.intern("__mailbox");
+ fields[count * stride + 1] = run.tword;
+ if (generic) type[5]++; else { fields[count * stride + 2] = type[1]; type[3]++; type[1]++; }
+ type[4] = fields;
+ buffer = rt.new(16384); mode = rt.outmode; output = rt.outbuf; length = rt.outn; capacity = rt.outcap; previous_grow = rt.outgrow;
+ rt.outgrow = 0; rt.outmode = 2; rt.outbuf = buffer; rt.outn = 0; rt.outcap = 16383;
+ declaration_method(type, "spawn"); rt.put_s(") { if (this.__mailbox) return -1; actor.Actor<");
+ run.tprint(signature[8]); rt.put_s(","); run.tprint(signature[4]); rt.put_s(">* endpoint = rt.new(actor.Actor<");
+ run.tprint(signature[8]); rt.put_s(","); run.tprint(signature[4]); rt.put_s(">); this.__mailbox = endpoint; return endpoint.spawn(fn(");
+ run.tprint(signature[8]); rt.put_s(" message) { return this.handle(message); }); } ");
+ int index = 0;
+ while (index < 5) {
+ if (index == 0) declaration_method(type, "send"); else if (index == 1) declaration_method(type, "recv"); else if (index == 2) declaration_method(type, "await"); else if (index == 3) declaration_method(type, "close"); else declaration_method(type, "poll");
+ if (index == 0) { rt.put_s(","); run.tprint(signature[8]); rt.put_s(" message"); }
+ if (index == 1) { rt.put_s(",int("); run.tprint(signature[4]); rt.put_s(").d callback"); }
+ rt.put_s(") { actor.Actor<"); run.tprint(signature[8]); rt.put_s(","); run.tprint(signature[4]); rt.put_s(">* endpoint = this.__mailbox as actor.Actor<");
+ run.tprint(signature[8]); rt.put_s(","); run.tprint(signature[4]); rt.put_s(">*; if (!endpoint) return -1; return endpoint.");
+ if (index == 0) rt.put_s("send(message);"); else if (index == 1) rt.put_s("recv(callback);"); else if (index == 2) rt.put_s("await();"); else if (index == 3) rt.put_s("close();"); else rt.put_s("poll();");
+ rt.put_s(" } "); index++;
+ }
+ buffer[rt.outn] = 0; rt.outgrow = previous_grow; rt.outmode = mode; rt.outbuf = output; rt.outn = length; rt.outcap = capacity;
+ word tokens = run.tokens(buffer);
+ if (generic) {
+ int position = 0, variable;
+ while (position < tokens[0]) {
+ variable = 0;
+ while (variable < run.ntl) {
+ word descriptor = run.typelist[variable];
+ if (descriptor[0] == run.TY_VAR && descriptor[3] < type[3] && tokens[1 + position * 3] == run.T_ID && tokens[2 + position * 3] == descriptor[2]) tokens[2 + position * 3] = type[6][descriptor[3]];
+ variable++;
+ }
+ position++;
+ }
+ }
+ word lowered = rt.new(tokens[0] * 3 + 1); int read = 0, written = 0;
+ while (read < tokens[0]) {
+ if (read > 0 && tokens[2 + (read - 1) * 3] == run.s_fn && read + 2 < tokens[0] && tokens[2 + (read + 1) * 3] == run.s_dot) {
+ lowered[1 + written * 3] = run.T_ID; lowered[2 + written * 3] = run.methname(tokens[2 + read * 3], tokens[2 + (read + 2) * 3]); lowered[3 + written * 3] = tokens[3 + read * 3]; read += 3;
+ } else { rt.copy_w(lowered + 1 + written * 3, tokens + 1 + read * 3, 3); read++; }
+ written++;
+ }
+ lowered[0] = written; run.compile_list_context(lowered, 1, run.module_name, run.module_imports); rt.free(lowered); rt.free(tokens); rt.free(buffer); return 0;
+}
+run.syntax("actor", actor.parse_actor);
+
+actor Actor<I, O> {
+ O(I) handler;
+ int(O) receiver;
+ int fd, process, pending, closed, dispatcher_index;
+
+ // threads receive a private vm snapshot and an owned channel endpoint.
+ unchecked fn spawn(O(I).d callback) {
+ if (this.process || this.closed || !callback) return -1;
+ this.handler = callback;
+ worker = 1;
+ this.process = rt.syscall(51, fn() {
+ worker = 1;
+ while (1) {
+ I message; O result;
+ if (read_message(this.fd, &message, typeof(I)) < 0) break;
+ result = this.handler(message);
+ if (write_message(this.fd, &result, typeof(O)) < 0) break;
+ }
+ return 0;
+ } as word, &this.fd, 0);
+ if (this.process < 0) { worker = 0; return -1; }
+ worker = 0;
+ if (!dispatchers) dispatchers = rt.new(Dispatcher[?]);
+ this.dispatcher_index = len(dispatchers);
+ push(dispatchers, fn:word() { this.poll(); return 0; });
+ return 0;
+ }
+
+ // sends a snapshot; the worker executes it asynchronously on its own cpu.
+ unchecked fn send(I message) {
+ if (this.process <= 0 || this.closed) return -1;
+ if (write_message(this.fd, &message, typeof(I)) < 0) { this.close(); return -1; }
+ this.pending++; return 0;
+ }
+
+ // registration does not wait; statement callbacks deliver ready replies.
+ unchecked fn recv(int(O).d callback) {
+ if (this.closed || !callback) return -1;
+ this.receiver = callback; this.poll(); return 0;
+ }
+
+ unchecked fn poll() {
+ O result;
+ if (!this.receiver || this.closed || this.process <= 0) return 0;
+ int previous_busy = busy; busy = 1;
+ while (!this.closed && this.pending > 0 && rt.syscall(46, this.fd, 0, 0) > 0) {
+ if (read_message(this.fd, &result, typeof(O)) < 0) { busy = previous_busy; this.close(); return -1; }
+ this.pending--; this.receiver(result);
+ }
+ busy = previous_busy; return 0;
+ }
+
+ // waits until every submitted message has reached the registered receiver.
+ unchecked fn await() {
+ if (!this.receiver && this.pending) return -1;
+ while (this.pending > 0 && !this.closed) {
+ if (rt.syscall(46, this.fd, -1, 0) < 0 || this.poll() < 0) return -1;
+ }
+ return 0;
+ }
+
+ unchecked fn close() {
+ if (this.closed) return 0;
+ this.closed = 1;
+ if (this.process > 0 && dispatchers) dispatchers[this.dispatcher_index] = 0;
+ if (this.process > 0) rt.syscall(52, this.process, 1, 0);
+ this.handler = 0; this.receiver = 0; return 0;
+ }
+}
+
+run.statement_hook(actor.dispatch);
diff --git a/src/lib/icon.zc b/src/lib/icon.zc
new file mode 100644
index 0000000..646a832
--- /dev/null
+++ b/src/lib/icon.zc
@@ -0,0 +1,623 @@
+namespace icon;
+include rt, run;
+
+// structured runtime transformations of compiler token lists.
+// token storage follows run.tokens: count, then kind, value and source line.
+// edits create independent lists; publication recompiles a function only after
+// delimiter validation. captures match complete balanced token ranges.
+
+int icon_error;
+
+int icon_eq(word* a, int ai, word* b, int bi, int n) {
+ int i = 0;
+ while (i < n) {
+ if (a[1 + (ai + i) * 3] != b[1 + (bi + i) * 3] || a[2 + (ai + i) * 3] != b[2 + (bi + i) * 3]) return 0;
+ i++;
+ }
+ return 1;
+}
+int icon_value(word* q, int i) { if (i < 0 || i >= q[0]) return 0; return q[2 + i * 3]; }
+int icon_kind(word* q, int i) { if (i < 0 || i >= q[0]) return 0; return q[1 + i * 3]; }
+int icon_is(word* q, int i, char* s) {
+ int kind = run.T_OP; if (s[0] >= 'a' && s[0] <= 'z') kind = run.T_ID;
+ return icon_kind(q, i) == kind && icon_value(q, i) == run.intern(s);
+}
+int icon_open(int v) { return v == run.intern("(") || v == run.intern("[") || v == run.intern("{"); }
+int icon_close(int v) { return v == run.intern(")") || v == run.intern("]") || v == run.intern("}"); }
+int icon_pair(int a, int b) {
+ return (a == run.intern("(") && b == run.intern(")")) || (a == run.intern("[") && b == run.intern("]")) || (a == run.intern("{") && b == run.intern("}"));
+}
+int icon_declaration(word* q, int start, int binding) {
+ int first = icon_value(q, start), i = start;
+ if (icon_kind(q, start) != run.T_ID || (first != run.intern("let") && first != run.intern("const") && !run.istypename(first))) return 0;
+ while (i < binding) { if (icon_is(q, i, "(") || icon_is(q, i, "=")) return 0; i++; }
+ return binding > start;
+}
+// locate a simple declaration at a statement boundary, including nested blocks.
+// result records [type start, binding position, semicolon, statement start].
+int icon_locate_declaration(word* q, int id, word* result) {
+ int start = 0, i, depth, v, binding, comma, type_start;
+ while (start < q[0]) {
+ if (start && !icon_is(q, start - 1, ";") && !icon_is(q, start - 1, "{") && !icon_is(q, start - 1, "}")) { start++; continue; }
+ type_start = start; if (icon_is(q, type_start, "const")) type_start++;
+ if (icon_kind(q, type_start) != run.T_ID || (!icon_is(q, type_start, "let") && !run.istypename(icon_value(q, type_start)))) { start++; continue; }
+ i = type_start; depth = 0; binding = -1; comma = 0;
+ while (i < q[0]) {
+ v = icon_value(q, i);
+ if (!depth && v == id && icon_kind(q, i) == run.T_ID && binding < 0 && icon_declaration(q, type_start, i) && (icon_is(q, i + 1, "=") || icon_is(q, i + 1, ";") || icon_is(q, i + 1, ",") || icon_is(q, i + 1, "("))) binding = i;
+ if (icon_kind(q, i) == run.T_OP) {
+ if (icon_open(v)) depth++;
+ else if (icon_close(v)) { if (!depth) break; depth--; }
+ if (!depth && v == run.intern(",")) comma = 1;
+ if (!depth && v == run.intern(";")) {
+ if (binding >= 0) {
+ if (comma || icon_is(q, binding + 1, "(")) return 0;
+ result[0] = type_start; result[1] = binding; result[2] = i; result[3] = start; return 1;
+ }
+ break;
+ }
+ }
+ i++;
+ }
+ start++;
+ }
+ return 0;
+}
+int icon_balanced(word* q, int a, int b) {
+ word* stack = rt.new(b - a + 1); int n = 0, i = a, v, valid = 1;
+ while (i < b) {
+ v = icon_value(q, i);
+ if (icon_kind(q, i) == run.T_OP) {
+ if (icon_open(v)) { stack[n] = v; n++; }
+ else if (icon_close(v)) { if (!n || !icon_pair(stack[n - 1], v)) { valid = 0; break; } n--; }
+ }
+ i++;
+ }
+ if (n) valid = 0;
+ rt.free(stack);
+ return valid;
+}
+word* .u icon_slice(word* q, int a, int b) {
+ word* r = rt.new(1 + (b - a) * 3); int i = 0; r[0] = b - a;
+ while (i < (b - a) * 3) { r[1 + i] = q[1 + a * 3 + i]; i++; }
+ return r;
+}
+word* .u icon_splice(word* q, int a, int b, word* insert) {
+ int n = q[0] - (b - a) + insert[0], i = 0, j = 0;
+ word* r = rt.new(1 + n * 3); r[0] = n;
+ while (i < a * 3) { r[1 + j] = q[1 + i]; i++; j++; }
+ i = 0; while (i < insert[0] * 3) { r[1 + j] = insert[1 + i]; i++; j++; }
+ i = b * 3; while (i < q[0] * 3) { r[1 + j] = q[1 + i]; i++; j++; }
+ return r;
+}
+// capture classes use integer literals, identifiers, expressions, statements
+// or blocks. a named capture records its exact token range for substitution.
+int icon_class(int name) {
+ if (name == run.intern("int")) return 1;
+ if (name == run.intern("name")) return 2;
+ if (name == run.intern("expr")) return 3;
+ if (name == run.intern("stmt")) return 4;
+ if (name == run.intern("block")) return 5;
+ return 0;
+}
+int icon_pattern(word* q) {
+ int i = 0;
+ if (!q || q[0] <= 0 || !icon_balanced(q, 0, q[0])) return 0;
+ while (i < q[0]) {
+ if (icon_is(q, i, "$")) {
+ if (icon_kind(q, i + 1) != run.T_ID || !icon_class(icon_value(q, i + 1))) return 0;
+ i += 2;
+ if (icon_is(q, i, ":")) { if (icon_kind(q, i + 1) != run.T_ID || icon_class(icon_value(q, i + 1))) return 0; i += 2; }
+ } else i++;
+ }
+ return 1;
+}
+int icon_precedence(int name) {
+ if (name == run.intern("||")) return 1;
+ if (name == run.intern("&&")) return 2;
+ if (name == run.intern("|")) return 3;
+ if (name == run.intern("^")) return 4;
+ if (name == run.intern("&")) return 5;
+ if (name == run.intern("==") || name == run.intern("!=")) return 6;
+ if (name == run.intern("<") || name == run.intern("<=") || name == run.intern(">") || name == run.intern(">=")) return 7;
+ if (name == run.intern("<<") || name == run.intern(">>")) return 8;
+ if (name == run.intern("+") || name == run.intern("-")) return 9;
+ if (name == run.intern("*") || name == run.intern("/") || name == run.intern("%")) return 10;
+ return 0;
+}
+// recognize expression structure before matching a variable-length capture.
+// this excludes statement keywords and incomplete operator sequences.
+int icon_expression(word* q, int start, int end, int minimum) {
+ int i = start, v, kind, priority, next;
+ if (i >= end) return -1;
+ v = icon_value(q, i); kind = icon_kind(q, i);
+ if (kind == run.T_OP && (v == run.intern("!") || v == run.intern("~") || v == run.intern("+") || v == run.intern("-") || v == run.intern("*") || v == run.intern("&"))) {
+ i = icon_expression(q, i + 1, end, 11); if (i < 0) return -1;
+ } else if (icon_is(q, i, "(")) {
+ i = icon_expression(q, i + 1, end, 1); if (i < 0 || !icon_is(q, i, ")")) return -1; i++;
+ } else if (kind == run.T_NUM || kind == run.T_FLT || kind == run.T_STR || kind == run.T_BSTR || kind == run.T_ID) {
+ if (kind == run.T_ID && (v == run.intern("return") || v == run.intern("let") || v == run.intern("if") || v == run.intern("while") || v == run.intern("for") || v == run.intern("else") || v == run.intern("break") || v == run.intern("continue") || v == run.intern("do") || v == run.intern("switch") || v == run.intern("const"))) return -1;
+ i++;
+ } else return -1;
+ while (i < end) {
+ if (icon_is(q, i, "(") || icon_is(q, i, "[")) {
+ v = icon_value(q, i); i++;
+ if (v != run.intern("(") || !icon_is(q, i, ")")) {
+ while (1) {
+ i = icon_expression(q, i, end, 1); if (i < 0) return -1;
+ if (v == run.intern("(") && icon_is(q, i, ",")) { i++; continue; } break;
+ }
+ }
+ if (i >= end || icon_kind(q, i) != run.T_OP || !icon_pair(v, icon_value(q, i))) return -1; i++; continue;
+ }
+ if (icon_is(q, i, ".")) { if (i + 1 >= end || icon_kind(q, i + 1) != run.T_ID) return -1; i += 2; continue; }
+ if (icon_is(q, i, "++") || icon_is(q, i, "--")) { i++; continue; }
+ if (minimum == 1 && icon_is(q, i, "?")) {
+ i = icon_expression(q, i + 1, end, 1); if (i < 0 || !icon_is(q, i, ":")) return -1;
+ i = icon_expression(q, i + 1, end, 1); if (i < 0) return -1; continue;
+ }
+ priority = 0; if (icon_kind(q, i) == run.T_OP) priority = icon_precedence(icon_value(q, i)); if (!priority || priority < minimum) break;
+ next = icon_expression(q, i + 1, end, priority + 1); if (next < 0) return -1; i = next;
+ }
+ return i;
+}
+int icon_range(word* q, int a, int b, int kind) {
+ if (a < 0 || a >= b || b > q[0]) return 0;
+ if (kind == 1) return b == a + 1 && icon_kind(q, a) == run.T_NUM;
+ if (kind == 2) return b == a + 1 && icon_kind(q, a) == run.T_ID;
+ if (kind == 3) return icon_expression(q, a, b, 1) == b;
+ if (kind == 5) return icon_is(q, a, "{") && icon_mate(q, a) == b - 1;
+ if (kind == 4) return icon_statement_end(q, a) == b;
+ return icon_expression(q, a, b, 1) == b;
+}
+// recursively match a pattern with backtracking at balanced capture boundaries.
+// captures contain count followed by [name, start, end, class] records.
+int icon_match(word* q, int at, word* p, int pi, word* caps) {
+ int kind, name, next, end, old, i;
+ if (pi == p[0]) return at + 1;
+ if (at >= q[0]) return 0;
+ if (!icon_is(p, pi, "$")) {
+ if (!icon_eq(q, at, p, pi, 1)) return 0;
+ return icon_match(q, at + 1, p, pi + 1, caps);
+ }
+ if (pi + 1 >= p[0]) return 0;
+ name = icon_value(p, pi + 1); kind = icon_class(name); next = pi + 2;
+ if (!kind) return 0;
+ if (icon_is(p, next, ":")) { name = icon_value(p, next + 1); next += 2; if (!name) return 0; }
+ else name = 0;
+ old = caps[0]; end = at + 1;
+ if (kind == 4) end = icon_statement_end(q, at);
+ if (kind == 5) { if (!icon_is(q, at, "{")) return 0; end = icon_mate(q, at) + 1; }
+ if (end <= at) return 0;
+ while (end <= q[0]) {
+ if (icon_range(q, at, end, kind)) {
+ i = 0;
+ while (i < old) {
+ if (name && caps[1 + i * 4] == name) {
+ if (end - at != caps[3 + i * 4] - caps[2 + i * 4] || !icon_eq(q, at, q, caps[2 + i * 4], end - at)) break;
+ }
+ i++;
+ }
+ if (i == old) {
+ caps[0] = old + 1; caps[1 + old * 4] = name; caps[2 + old * 4] = at; caps[3 + old * 4] = end; caps[4 + old * 4] = kind;
+ i = icon_match(q, end, p, next, caps);
+ if (i) return i;
+ caps[0] = old;
+ }
+ }
+ if (kind != 3) break;
+ end++;
+ }
+ return 0;
+}
+int icon_capture(word* caps, int name, int ordinal) {
+ int c = 0, kind = icon_class(name), k = 0;
+ while (c < caps[0]) {
+ if (!kind) { if (caps[1 + c * 4] == name) return c + 1; }
+ else if (caps[4 + c * 4] == kind) { if (k == ordinal) return c + 1; k++; }
+ c++;
+ }
+ return 0;
+}
+word* .u icon_expand(word* q, word* replacement, word* caps) {
+ int n = 0, i = 0, j = 0, c, name, kind, k, seen[6], a, b;
+ k = 0; while (k < 6) { seen[k] = 0; k++; }
+ while (i < replacement[0]) {
+ if (icon_is(replacement, i, "$")) {
+ if (icon_kind(replacement, i + 1) != run.T_ID) { icon_error = 3; return 0; }
+ name = icon_value(replacement, i + 1); kind = icon_class(name); c = icon_capture(caps, name, seen[kind]);
+ if (!c) { icon_error = 3; return 0; } c--;
+ n += caps[3 + c * 4] - caps[2 + c * 4]; if (kind) seen[kind]++; i += 2;
+ } else { n++; i++; }
+ }
+ word* r = rt.new(1 + n * 3); i = 0;
+ k = 0; while (k < 6) { seen[k] = 0; k++; }
+ while (i < replacement[0]) {
+ if (icon_is(replacement, i, "$")) {
+ name = icon_value(replacement, i + 1); kind = icon_class(name); c = icon_capture(caps, name, seen[kind]) - 1;
+ if (kind) seen[kind]++;
+ a = caps[2 + c * 4] * 3; b = caps[3 + c * 4] * 3;
+ while (a < b) { r[1 + j] = q[1 + a]; a++; j++; }
+ i += 2;
+ } else {
+ k = 0; while (k < 3) { r[1 + j] = replacement[1 + i * 3 + k]; j++; k++; } i++;
+ }
+ }
+ r[0] = j / 3;
+ if (!icon_balanced(r, 0, r[0])) { rt.free(r); icon_error = 1; return 0; }
+ return r;
+}
+
+// resumable frames retain their own token list and explicit binding maps.
+// [tokens, position, state, result, error, locals, globals, blocks, steps].
+
+// locate a binding in a frame map by its interned name.
+int icon_binding(word map, int name) {
+ while (map) { if (map[0] == name) return map; map = map[2]; }
+ return 0;
+}
+int icon_read(word binding) { if (binding[3]) return *binding[3]; return binding[1]; }
+int icon_store(word*.m frame, int column, int name, int value) {
+ word entry = icon_binding(frame[column], name);
+ if (!entry) {
+ word* node = rt.new(4); node[0] = name; node[1] = value; node[2] = frame[column]; node[3] = 0; frame[column] = node; return value;
+ }
+ entry[1] = value; if (entry[3]) *entry[3] = value; return value;
+}
+int icon_declare(word*.m frame, int name, int value) {
+ word* node = rt.new(4); node[0] = name; node[1] = value; node[2] = frame[5]; node[3] = 0; frame[5] = node; return value;
+}
+int icon_fail(word frame) { frame[2] = 2; frame[4] = 5; icon_error = 5; return 0; }
+int icon_end(word* q, int start) {
+ int depth = 0, i = start, v;
+ while (i < q[0]) {
+ v = icon_value(q, i);
+ if (icon_kind(q, i) == run.T_OP) {
+ if (icon_open(v)) depth++;
+ else if (icon_close(v)) { if (!depth) return i; depth--; }
+ if (!depth && v == run.intern(";")) return i;
+ }
+ i++;
+ }
+ return i;
+}
+int icon_mate(word* q, int start) {
+ int depth = 1, i = start + 1, v;
+ while (i < q[0]) {
+ v = icon_value(q, i);
+ if (icon_kind(q, i) == run.T_OP) {
+ if (icon_open(v)) depth++; else if (icon_close(v)) depth--;
+ if (!depth) return i;
+ }
+ i++;
+ }
+ return -1;
+}
+// find one statement boundary; control-flow captures require braced bodies.
+int icon_statement_end(word* q, int start) {
+ int close, end;
+ if (icon_is(q, start, "{")) { close = icon_mate(q, start); if (close < 0) return -1; return close + 1; }
+ if (icon_is(q, start, "if") || icon_is(q, start, "while")) {
+ if (!icon_is(q, start + 1, "(")) return -1;
+ close = icon_mate(q, start + 1);
+ if (close < 0 || !icon_is(q, close + 1, "{")) return -1;
+ end = icon_mate(q, close + 1); if (end < 0) return -1; end++;
+ if (icon_is(q, start, "if") && icon_is(q, end, "else")) {
+ if (!icon_is(q, end + 1, "{")) return -1;
+ close = icon_mate(q, end + 1); if (close < 0) return -1; return close + 1;
+ }
+ return end;
+ }
+ end = icon_end(q, start);
+ if (!icon_is(q, end, ";")) return -1;
+ return end + 1;
+}
+// construct a compiler return body in one allocation, retaining source lines.
+word* .u icon_eval_tokens(word* q, int start, int end) {
+ int count = end - start;
+ word* expr = rt.new(7 + count * 3); expr[0] = count + 2;
+ expr[1] = run.T_ID; expr[2] = run.intern("return"); expr[3] = 1;
+ if (count) rt.copy_w(expr + 4, q + 1 + start * 3, count * 3);
+ expr[4 + count * 3] = run.T_OP; expr[5 + count * 3] = run.intern(";"); expr[6 + count * 3] = 1;
+ return expr;
+}
+// scalar frame substitution cannot preserve lvalue operations or implicit
+// program-global reads. reject these before compiling or executing a range.
+int icon_frame_expression(word frame, int start, int end) {
+ word* q = frame[0]; int i = start, s; word binding;
+ if (!icon_range(q, start, end, 3)) return 0;
+ while (i < end) {
+ if (icon_is(q, i, "++") || icon_is(q, i, "--")) return 0;
+ if (icon_kind(q, i) == run.T_ID && !icon_is(q, i - 1, ".")) {
+ binding = icon_binding(frame[5], icon_value(q, i));
+ if (!binding) binding = icon_binding(frame[6], icon_value(q, i));
+ if (!binding) {
+ s = run.lookups(icon_value(q, i));
+ if (s < 0 || (run.skind[s] != run.K_FN && run.skind[s] != run.K_CONST)) return 0;
+ }
+ }
+ i++;
+ }
+ return 1;
+}
+int icon_frame_eval(word frame, int start, int end) {
+ word* expr = icon_eval_tokens(frame[0], start, end);
+ int i = 1; word binding;
+ while (i < expr[0] - 1) {
+ if (icon_kind(expr, i) == run.T_ID && !icon_is(expr, i - 1, ".")) {
+ binding = icon_binding(frame[5], icon_value(expr, i));
+ if (!binding) binding = icon_binding(frame[6], icon_value(expr, i));
+ if (binding) { expr[1 + i * 3] = run.T_NUM; expr[2 + i * 3] = icon_read(binding); }
+ }
+ i++;
+ }
+ int result = run.eval(expr); rt.free(expr); return result;
+}
+int icon_block(word*.m frame, int open, int resume) {
+ int close = icon_mate(frame[0], open);
+ if (close < 0) return icon_fail(frame);
+ word block = rt.new(4); block[0] = close; block[1] = resume; block[2] = frame[7]; block[3] = frame[5]; frame[7] = block; frame[1] = open + 1; return 1;
+}
+
+struct icon {
+ // the last transformation status: 0 success, 1 invalid delimiters,
+ // 2 invalid range or declaration, 3 unresolved replacement capture,
+ // 4 unavailable function metadata, 5 unsupported stepping syntax,
+ // 6 rejected function publication.
+ static int error() { return icon_error; }
+ // check token delimiters without compiling or executing the list.
+ static int validate(word* q) { icon_error = 0; if (!q || q[0] < 0 || !icon_balanced(q, 0, q[0])) { icon_error = 1; return 0; } return 1; }
+ // return an independent token list with [start, end) replaced by insert.
+ static word* .u splice(word* q, int start, int end, word* insert) {
+ icon_error = 0;
+ if (!q || !insert || q[0] < 0 || insert[0] < 0 || start < 0 || end < start || end > q[0]) { icon_error = 2; return 0; }
+ word* r = icon_splice(q, start, end, insert);
+ if (!icon_balanced(r, 0, r[0])) { rt.free(r); icon_error = 1; return 0; }
+ return r;
+ }
+ // replace each nonoverlapping pattern match. inserted tokens are not
+ // searched again. the input and templates remain unchanged.
+ static word* .u rewrite(word* q, word* pattern, word* replacement) {
+ icon_error = 0;
+ if (!q || q[0] < 0 || !replacement || replacement[0] < 0 || !icon_pattern(pattern) || !icon_balanced(q, 0, q[0])) { icon_error = 1; return 0; }
+ word* result = icon_slice(q, 0, q[0]); word* caps = rt.new(1 + pattern[0] * 4); word* insert, next;
+ int at = 0, end;
+ while (at < result[0]) {
+ caps[0] = 0; end = icon_match(result, at, pattern, 0, caps);
+ if (!end) { at++; continue; }
+ insert = icon_expand(result, replacement, caps);
+ if (!insert) { rt.free(result); rt.free(caps); return 0; }
+ next = icon_splice(result, at, end - 1, insert); at += insert[0];
+ rt.free(insert); rt.free(result); result = next;
+ }
+ rt.free(caps); return result;
+ }
+ // transform retained function tokens and publish the compiled replacement.
+ // references to the original entry continue to use the published version.
+ static int find_replace(word function, word* pattern, word* replacement) {
+ icon_error = 0;
+ if (!replacement || replacement[0] < 0 || !icon_pattern(pattern)) { icon_error = 1; return 0; }
+ word* q = run.function_tokens(function);
+ if (!q) { icon_error = 4; return 0; }
+ word* caps = rt.new(1 + pattern[0] * 4); int i = 0, end, count = 0;
+ while (i < q[0]) { caps[0] = 0; end = icon_match(q, i, pattern, 0, caps); if (end) { count++; i = end - 1; } else i++; }
+ rt.free(caps);
+ if (!count) { rt.free(q); return 0; }
+ word* changed = icon.rewrite(q, pattern, replacement); rt.free(q);
+ if (!changed) return 0;
+ int published = run.replace_function(function, changed); rt.free(changed);
+ if (!published) { icon_error = 6; return 0; }
+ return count;
+ }
+ // remove a complete declaration statement containing the requested name.
+ // declarations containing multiple bindings are rejected to preserve them.
+ static word* .u undeclare(word* q, char* name) {
+ icon_error = 0;
+ int declaration[4];
+ if (!q || q[0] < 0 || !name || !icon_locate_declaration(q, run.intern(name), declaration)) { icon_error = 2; return 0; }
+ word* empty = rt.new(1); empty[0] = 0;
+ word* result = icon.splice(q, declaration[3], declaration[2] + 1, empty); rt.free(empty); return result;
+ }
+ // change the type of the first simple declaration of name.
+ static word* .u change_type(word* q, char* name, word* type) {
+ icon_error = 0;
+ int declaration[4];
+ if (!q || q[0] < 0 || !name || !icon_locate_declaration(q, run.intern(name), declaration)) { icon_error = 2; return 0; }
+ return icon.splice(q, declaration[0], declaration[1], type);
+ }
+ // insert a scoped statement block at the given token boundary.
+ static word* .u insert_block(word* q, int at, word* body) {
+ icon_error = 0;
+ if (!q || !body || body[0] < 0 || at < 0 || at > q[0]) { icon_error = 2; return 0; }
+ word* block = rt.new(7 + body[0] * 3); int i = 0; block[0] = body[0] + 2;
+ block[1] = run.T_OP; block[2] = run.intern("{"); block[3] = 1;
+ while (i < body[0] * 3) { block[4 + i] = body[1 + i]; i++; }
+ block[4 + i] = run.T_OP; block[5 + i] = run.intern("}"); block[6 + i] = 1;
+ word* result = icon.splice(q, at, at, block); rt.free(block); return result;
+ }
+ // evaluate a selected expression using the compiler rather than a text
+ // evaluator. the range must contain one complete expression.
+ static int calculate(word* q, int start, int end) {
+ icon_error = 0;
+ if (!q || start < 0 || end > q[0] || !icon_range(q, start, end, 3)) { icon_error = 2; return 0; }
+ word* expr = icon_eval_tokens(q, start, end);
+ int value = run.eval(expr); rt.free(expr); return value;
+ }
+ // start a resumable evaluation with independent tokens and binding storage.
+ // stepping executes statements through the compiler; no native frame is
+ // suspended or inspected. scalar bindings are visible between statements.
+ static word* .u begin(word* q) {
+ if (!icon.validate(q)) { icon_error = 1; return 0; }
+ word* frame = rt.new(9); int i = 0; while (i < 9) { frame[i] = 0; i++; } frame[0] = icon_slice(q, 0, q[0]); return frame;
+ }
+ // create an explicit frame from a function's retained body. argument values
+ // are supplied through set_local before stepping starts.
+ static word* .u begin_function(int function) {
+ word* q = run.function_tokens(function);
+ if (!q) { icon_error = 4; return 0; }
+ word* frame = icon.begin(q); rt.free(q); return frame;
+ }
+ // set an isolated global binding, or update storage explicitly bound to it.
+ static int set_global(word frame, char* name, int value) { icon_error = 0; return icon_store(frame, 6, run.intern(name), value); }
+ // update the innermost local binding, creating a binding when absent.
+ static int set_local(word frame, char* name, int value) { icon_error = 0; return icon_store(frame, 5, run.intern(name), value); }
+ // bind one existing scalar global explicitly. reads observe its current
+ // value, and subsequent frame assignments update its program storage.
+ static int bind_global(word frame, char* name) {
+ int s = run.lookups(name); icon_error = 0;
+ if (s < 0 || run.skind[s] != run.K_GVAR) { icon_error = 2; return 0; }
+ word type = run.stype[s];
+ if (type && type != run.tint && type != run.tword) { icon_error = 2; return 0; }
+ icon_store(frame, 6, run.intern(name), *run.sval[s]);
+ word binding = icon_binding(frame[6], run.intern(name)); binding[3] = run.sval[s]; return 1;
+ }
+ // read the innermost local binding; a missing binding sets status 2.
+ static int local(word frame, char* name) { icon_error = 0; word b = icon_binding(frame[5], run.intern(name)); if (!b) { icon_error = 2; return 0; } return icon_read(b); }
+ // read an isolated or explicitly connected global binding.
+ static int global(word frame, char* name) { icon_error = 0; word b = icon_binding(frame[6], run.intern(name)); if (!b) { icon_error = 2; return 0; } return icon_read(b); }
+ // frame state: 0 resumable, 1 complete, 2 unsupported statement.
+ static int state(word frame) { return frame[2]; }
+ // the returned value after completion; zero before a return is executed.
+ static int result(word frame) { return frame[3]; }
+ // token index of the next statement or control-flow decision.
+ static int position(word frame) { return frame[1]; }
+ // number of executed statements and control-flow decisions.
+ static int steps(word frame) { return frame[8]; }
+ // number of retained local binding records, including active shadowed records.
+ static int local_count(word frame) { word b = frame[5]; int n = 0; while (b) { n++; b = b[2]; } return n; }
+ // interned name of a local record; zero when index is outside the map.
+ static char* local_name(word frame, int index) { word b = frame[5]; while (b && index > 0) { index--; b = b[2]; } if (!b || index < 0) return 0; return b[0]; }
+ // execute one statement or control-flow decision. returns 1 after progress,
+ // 0 after completion or an unsupported statement. blocks, if and while
+ // require braces; declarations contain one scalar binding.
+ static int step(word frame) {
+ if (!frame || frame[2]) return 0;
+ icon_error = 0;
+ word* q = frame[0]; int at = frame[1], end, name, value, column = 5, v, close, open, alternate;
+ word block, binding;
+ while (frame[7] && at == frame[7][0]) {
+ block = frame[7];
+ while (frame[5] != block[3]) { binding = frame[5]; frame[5] = binding[2]; rt.free(binding); }
+ at = block[1]; frame[7] = block[2]; rt.free(block); frame[1] = at;
+ }
+ if (at >= q[0]) { frame[2] = 1; return 0; }
+ frame[8]++;
+ if (icon_is(q, at, ";")) { frame[1] = at + 1; return 1; }
+ if (icon_is(q, at, "{")) { close = icon_mate(q, at); return icon_block(frame, at, close + 1); }
+ if (icon_is(q, at, "if") || icon_is(q, at, "while")) {
+ if (!icon_is(q, at + 1, "(")) return icon_fail(frame);
+ close = icon_mate(q, at + 1); open = close + 1;
+ if (close < 0 || !icon_is(q, open, "{")) return icon_fail(frame);
+ end = icon_mate(q, open); if (end < 0) return icon_fail(frame);
+ alternate = end + 1;
+ if (icon_is(q, at, "if") && icon_is(q, alternate, "else")) {
+ if (!icon_is(q, alternate + 1, "{")) return icon_fail(frame);
+ }
+ if (!icon_frame_expression(frame, at + 2, close)) return icon_fail(frame);
+ value = icon_frame_eval(frame, at + 2, close);
+ if (icon_is(q, at, "while")) { if (value) return icon_block(frame, open, at); frame[1] = end + 1; return 1; }
+ if (icon_is(q, alternate, "else")) {
+ close = icon_mate(q, alternate + 1);
+ if (value) return icon_block(frame, open, close + 1);
+ return icon_block(frame, alternate + 1, close + 1);
+ }
+ if (value) return icon_block(frame, open, end + 1);
+ frame[1] = end + 1; return 1;
+ }
+ end = icon_end(q, at);
+ if (end >= q[0] || !icon_is(q, end, ";")) return icon_fail(frame);
+ if (icon_is(q, at, "return")) {
+ frame[3] = 0;
+ if (end > at + 1) { if (!icon_frame_expression(frame, at + 1, end)) return icon_fail(frame); frame[3] = icon_frame_eval(frame, at + 1, end); }
+ frame[1] = end + 1; frame[2] = 1; return 1;
+ }
+ if (icon_is(q, at, "let") || icon_is(q, at, "int") || icon_is(q, at, "word")) {
+ if (icon_kind(q, at + 1) != run.T_ID) return icon_fail(frame);
+ name = icon_value(q, at + 1); value = 0;
+ if (end > at + 2) { if (!icon_is(q, at + 2, "=") || !icon_frame_expression(frame, at + 3, end)) return icon_fail(frame); value = icon_frame_eval(frame, at + 3, end); }
+ icon_declare(frame, name, value); frame[1] = end + 1; return 1;
+ }
+ name = icon_value(q, at); binding = icon_binding(frame[5], name);
+ if (!binding) { binding = icon_binding(frame[6], name); column = 6; }
+ if (binding && icon_kind(q, at) == run.T_ID) {
+ v = icon_value(q, at + 1);
+ if (end == at + 2 && (v == run.intern("++") || v == run.intern("--"))) {
+ value = icon_read(binding) + 1; if (v == run.intern("--")) value = icon_read(binding) - 1;
+ } else {
+ if (v != run.intern("=") && v != run.intern("+=") && v != run.intern("-=") && v != run.intern("*=") && v != run.intern("/=")) return icon_fail(frame);
+ if (!icon_frame_expression(frame, at + 2, end)) return icon_fail(frame);
+ value = icon_frame_eval(frame, at + 2, end);
+ if (v == run.intern("+=")) value = icon_read(binding) + value;
+ if (v == run.intern("-=")) value = icon_read(binding) - value;
+ if (v == run.intern("*=")) value = icon_read(binding) * value;
+ if (v == run.intern("/=")) value = icon_read(binding) / value;
+ }
+ icon_store(frame, column, name, value); frame[1] = end + 1; return 1;
+ }
+ return icon_fail(frame);
+ }
+ // execute at most budget statements. returning 0 leaves the frame resumable;
+ // callers can inspect bindings, edit them, then continue with another budget.
+ static int resume(word frame, int budget) { while (budget > 0 && !frame[2]) { icon.step(frame); budget--; } return frame[2]; }
+ // release a frame and all storage it owns.
+ static int close(.d frame) {
+ word b, next; int column = 5;
+ while (column <= 7) { b = frame[column]; while (b) { next = b[2]; rt.free(b); b = next; } column++; }
+ rt.free(frame[0]); rt.free(frame); return 0;
+ }
+}
+
+// public entry points for the icon namespace.
+
+// status of the most recent transformation or binding operation; see docs/ICON.md.
+int error() { return icon.icon.error(); }
+// check delimiter structure only; does not compile or execute tokens.
+int validate(word* q) { return icon.icon.validate(q); }
+// return a new token list with [start, end) replaced; caller owns the result.
+word* .u splice(word* q, int start, int end, word* insert) { return icon.icon.splice(q, start, end, insert); }
+// return a new list with nonoverlapping matches replaced; inputs remain unchanged.
+word* .u rewrite(word* q, word* pattern, word* replacement) { return icon.icon.rewrite(q, pattern, replacement); }
+// publish a transformed function body; returns matches, or 0 on no match or failure.
+int find_replace(word function, word* pattern, word* replacement) { return icon.icon.find_replace(function, pattern, replacement); }
+// remove the first complete single-binding declaration named name; caller owns the result.
+word* .u undeclare(word* q, char* name) { return icon.icon.undeclare(q, name); }
+// replace the type prefix of the first simple declaration named name; caller owns the result.
+word* .u change_type(word* q, char* name, word* type) { return icon.icon.change_type(q, name, type); }
+// insert a scoped block containing body at token boundary at; caller owns the result.
+word* .u insert_block(word* q, int at, word* body) { return icon.icon.insert_block(q, at, body); }
+// compile and evaluate the expression in [start, end); expression calls retain their effects.
+int calculate(word* q, int start, int end) { return icon.icon.calculate(q, start, end); }
+// create a frame with copied tokens and isolated bindings; caller closes the frame.
+word* .u begin(word* q) { return icon.icon.begin(q); }
+// create a frame from retained function tokens; set arguments as locals before stepping.
+word* .u begin_function(int function) { return icon.icon.begin_function(function); }
+// set an isolated global binding, creating it when absent; returns the stored value.
+int set_global(word frame, char* name, int value) { return icon.icon.set_global(frame, name, value); }
+// set the innermost local binding, creating it when absent; returns the stored value.
+int set_local(word frame, char* name, int value) { return icon.icon.set_local(frame, name, value); }
+// connect an int or word program global to the frame; later frame writes update it.
+int bind_global(word frame, char* name) { return icon.icon.bind_global(frame, name); }
+// read a local binding; missing names return 0 and set error status 2.
+int local(word frame, char* name) { return icon.icon.local(frame, name); }
+// read a frame global binding; missing names return 0 and set error status 2.
+int global(word frame, char* name) { return icon.icon.global(frame, name); }
+// return frame state: 0 resumable, 1 complete, or 2 unsupported statement.
+int state(word frame) { return icon.icon.state(frame); }
+// return the value produced by return, or 0 before a return is executed.
+int result(word frame) { return icon.icon.result(frame); }
+// return the token index of the next statement or control-flow decision.
+int position(word frame) { return icon.icon.position(frame); }
+// return the number of statements and control-flow decisions executed.
+int steps(word frame) { return icon.icon.steps(frame); }
+// count retained local records, including shadowed records in active blocks.
+int local_count(word frame) { return icon.icon.local_count(frame); }
+// return the zero-based local record name, or 0 when index is out of range.
+char* local_name(word frame, int index) { return icon.icon.local_name(frame, index); }
+// execute scalar declarations and assignments, standalone ++/--, return, blocks,
+// or braced if/else/while; direct program globals need bind_global first.
+// unsupported syntax sets error status 5.
+int step(word frame) { return icon.icon.step(frame); }
+// execute at most budget steps and return the frame state; frame bindings are scalar int/word values.
+int resume(word frame, int budget) { return icon.icon.resume(frame, budget); }
+// release the frame and owned storage; consumes frame and returns 0.
+int close(.d frame) { return icon.icon.close(d*frame); }
diff --git a/src/lib/std.zc b/src/lib/std.zc
index 375f403..f232422 100644
--- a/src/lib/std.zc
+++ b/src/lib/std.zc
@@ -1,35 +1,40 @@
+namespace std;
+include rt, run;
+
// small helpers for strings, files, the clock and byte buffers. not part of the
-// genome; include it with: include "src/lib/std.zc";
+// genome as declarations; its source is embedded. include it with: include std;
// syscall numbers (SYS_OPEN and the rest) live in the runtime.
-// a string here is a word string, one character per word and a 0 at the end,
-// which is char*. the Bytes group takes packed byte strings (b"..."), whose
-// type is a plain word.
+// a character string has type char*, one character per word and a 0 at the end.
+// the Bytes group takes packed byte strings (b"..."), whose
+// type is bstr.
// the helpers are grouped in structs, and each is a static function, so it is
-// called as Group.name(args): Text.itoa(n, buf), Host.now(), Buf.get(b, 0).
+// called as std.Group.name(args): std.Text.itoa(n, buf), std.Host.now(),
+// std.Buf.get(b, 0).
// the constructors arena(n) and bnew(n) and the argv helpers are plain
// functions, because an owned result has to carry its .u tag.
-// ---- the command line and the environment ----
+//: ---- the command line and the environment ----
+
+//: argc and argv, envc and envp, as passed to main by c. a zc string is
+//: one character per word, so an array of them is char*[?]. libinit() fills
+//: these and the runtime calls it before main, if the program has one (an
+//: image built by build_image, or a check-mode document, does not).
-// argc and argv, envc and envp, the way c hands them to main. a zc string is
-// one character per word, so an array of them is []char*. libinit() fills
-// these and the runtime calls it before main, if the program has one (an
-// image built by build_image, or a check-mode document, does not).
int argc;
-[]char* argv;
+char*[?] argv;
int envc;
-[]char* envp;
+char*[?] envp;
// one entry of a vector syscall, as a fresh string, or 0 past the end.
// op: the syscall that fetches an entry (SYS_ARG or SYS_ENV)
// i: which entry
// returns: the entry, owned by the caller; 0 when there is no entry i
-unchecked char*.u argstr(int op, int i) {
- int n = syscall(op, i, 0, 0);
+char*.u argstr(int op, int i) {
+ int n = rt.syscall(op, i, 0, 0);
char* s;
if (n < 0) return 0;
- s = new(n + 1);
- syscall(op, i, s, n + 1);
+ s = rt.new(n + 1);
+ rt.syscall(op, i, s, n + 1);
return s;
}
@@ -37,9 +42,9 @@ unchecked char*.u argstr(int op, int i) {
// cntop: the syscall that counts the entries (SYS_ARGC or SYS_ENVC)
// stop: the syscall that fetches one (SYS_ARG or SYS_ENV)
// returns: a new array of the strings, owned by the caller
-unchecked []char*.u argsvec(int cntop, int stop) {
- int n = syscall(cntop, 0, 0, 0), i = 0;
- []char* a = new([]char*, 0);
+char*[?].u argsvec(int cntop, int stop) {
+ int n = rt.syscall(cntop, 0, 0, 0), i = 0;
+ char*[?] a = rt.new(char*[?], 0);
while (i < n) { push(a, argstr(stop, i)); i++; }
return a;
}
@@ -48,43 +53,44 @@ unchecked []char*.u argsvec(int cntop, int stop) {
// it stays a plain function: the compiler looks it up by name.
// returns: 0
int libinit() {
- argv = argsvec(SYS_ARGC, SYS_ARG);
- envp = argsvec(SYS_ENVC, SYS_ENV);
+ argv = argsvec(rt.SYS_ARGC, rt.SYS_ARG);
+ envp = argsvec(rt.SYS_ENVC, rt.SYS_ENV);
argc = len(argv); envc = len(envp);
return 0;
}
-// ---- capturing output ----
+//: ---- capturing output ----
// capture.start() redirects everything the program writes into a string and
-// hands it back. writes go to the buffer instead of the terminal until end()
+// returns it. writes go to the buffer instead of the terminal until end()
// stops it, so the buffer is a string once end() has run:
// let buf = capture.start();
// put_s("hello world!"); // nothing on the terminal
-// buf.end();
+// capture.end(buf);
// put_s(buf); // hello world
// one capture at a time, and the buffer belongs to whoever holds it.
+
struct capture {
- // starts a capture of the output.
- // returns: the buffer, a word on purpose: the method end() is looked up on
- // any receiver the compiler cannot type, and a typed one has none
- static word start() {
+ // redirects program output into a collector-managed character buffer.
+ // returns the buffer; call end(buffer) before reading its terminator.
+ // returns: the buffer used for this capture
+ static char* start() {
int cap = 256;
- outmode = 2; outn = 0; outcap = cap;
- outbuf = new(cap);
- return outbuf;
+ rt.outmode = 2; rt.outn = 0; rt.outcap = cap;
+ rt.outbuf = rt.new(cap);
+ return rt.outbuf;
}
-
- // stops the capture and terminates the buffer.
- // returns: the string it collected (this is the buffer)
- word end() {
- outbuf[outn] = 0;
- outmode = 0; outbuf = 0; outn = 0; outcap = 0;
- return this;
+ // stops output capture, terminates the buffer, and returns the collected text.
+ // buffer: the buffer returned by start
+ // returns: the same buffer, now terminated
+ static char* end(char* buffer) {
+ rt.outbuf[rt.outn] = 0;
+ rt.outmode = 0; rt.outbuf = 0; rt.outn = 0; rt.outcap = 0;
+ return buffer;
}
}
-// ---- word strings ----
+//: ---- character strings ----
struct Text {
// the number written in decimal at the start of s. a leading '-' makes it
@@ -106,7 +112,7 @@ struct Text {
int b[24], i = 0, j = 0, neg = 0;
if (!n) { dst[0] = '0'; dst[1] = 0; return 1; }
if (n < 0) { neg = 1; n = 0 - n; }
- if (n < 0) { copy_w(dst, "-9223372036854775808", 21); return 20; }
+ if (n < 0) { rt.copy_w(dst, "-9223372036854775808", 21); return 20; }
while (n) { b[i] = '0' + n % 10; i++; n = n / 10; }
if (neg) { dst[0] = '-'; j = 1; }
while (i) { i--; dst[j] = b[i]; j++; }
@@ -138,6 +144,19 @@ struct Text {
return 1;
}
+ // does s end with suffix? an empty suffix always matches.
+ // s: the string to test
+ // suffix: the ending to look for
+ // returns: 1 when it matches, 0 otherwise
+ static int ends(char* s, char* suffix) {
+ int n = 0, m = 0, i = 0;
+ while (s[n]) n++;
+ while (suffix[m]) m++;
+ if (m > n) return 0;
+ while (i < m) { if (s[n - m + i] != suffix[i]) return 0; i++; }
+ return 1;
+ }
+
// how many characters come before the 0.
// s: the string
// returns: the length, not counting the 0
@@ -227,23 +246,32 @@ struct Text {
}
}
-// ---- numbers ----
+//: ---- numbers ----
+
// plain integer helpers. none of them can fail: an out of range result wraps
// the same way the arithmetic does.
+
struct Num {
- // the absolute value of n.
+ // the absolute value of n, using wrapping word arithmetic. the minimum
+ // word remains negative because its positive value cannot fit in a word.
+ // n: the value
+ // returns: its absolute value, or the minimum word
static int abs(int n) {
if (n < 0) return 0 - n;
return n;
}
// the smaller of a and b.
+ // a, b: the values to compare
+ // returns: the smaller value (a when they are equal)
static int min(int a, int b) {
if (a < b) return a;
return b;
}
// the larger of a and b.
+ // a, b: the values to compare
+ // returns: the larger value (a when they are equal)
static int max(int a, int b) {
if (a > b) return a;
return b;
@@ -251,14 +279,17 @@ struct Num {
// v, kept between lo and hi.
// v: the value
- // lo, hi: the bounds, lo first
+ // lo, hi: the bounds, lo first; lo must not exceed hi
+ // returns: v clamped to the bounds
static int clamp(int v, int lo, int hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
- // the greatest common divisor of two numbers, always 0 or more.
+ // the greatest common divisor of a and b, computed with word arithmetic.
+ // a, b: the values
+ // returns: their greatest common divisor
static int gcd(int a, int b) {
if (a < 0) a = 0 - a;
if (b < 0) b = 0 - b;
@@ -267,7 +298,10 @@ struct Num {
return a;
}
- // b raised to the power e, for e of 0 or more. e below 0 gives 1.
+ // b raised to the power e. e below 0 gives 1; the result wraps as a word.
+ // b: the base
+ // e: the exponent
+ // returns: b to the power e, or 1 when e is negative
static int pow(int b, int e) {
int r = 1;
while (e > 0) { r = r * b; e--; }
@@ -275,6 +309,8 @@ struct Num {
}
// the whole square root of n, rounded down. n below 0 gives 0.
+ // n: the value
+ // returns: the greatest integer whose square is at most n, or 0 for n <= 0
static int isqrt(int n) {
int lo = 0, hi = 3037000499, mid;
if (n <= 0) return 0;
@@ -285,7 +321,9 @@ struct Num {
return lo;
}
- // -1, 0 or 1 by the sign of n.
+ // -1, 0 or 1 according to the sign of n.
+ // n: the value
+ // returns: -1 when n is negative, 0 when it is zero, and 1 when it is positive
static int sign(int n) {
if (n < 0) return 0 - 1;
if (n > 0) return 1;
@@ -293,9 +331,9 @@ struct Num {
}
}
-// ---- the host ----
+//: ---- the host ----
-// the argument block for Host.raw: the number, then its six arguments.
+//: the argument block for Host.raw: the number, then its six arguments.
int rawblk[7];
struct Host {
@@ -309,115 +347,160 @@ struct Host {
// is wanted.
// nr: the syscall number (x86-64 and arm64 numbers differ, so check the host)
// a..f: the six arguments, unused ones 0
- // returns: the kernel's result, or -errno when the call fails
+ // returns: the kernel's result, -errno on failure, or -1 on unsupported hosts
static int raw(int nr, int a, int b, int c, int d, int e, int f) {
rawblk[0] = nr; rawblk[1] = a; rawblk[2] = b; rawblk[3] = c;
rawblk[4] = d; rawblk[5] = e; rawblk[6] = f;
- return syscall(SYS_RAW, rawblk, 0, 0);
+ return rt.syscall(rt.SYS_RAW, rawblk, 0, 0);
}
// the value of an environment variable.
// name: the variable's name
- // dst: where the value goes, a word string
+ // dst: where the value goes, a zero-terminated character string
// n: how many words dst has
// returns: the length of the value, -1 if the variable is not set
- static int env(char* name, char* dst, int n) { return syscall(SYS_GETENV, name, dst, n); }
+ static int env(char* name, char* dst, int n) { return rt.syscall(rt.SYS_GETENV, name, dst, n); }
// the clock.
// returns: the time in seconds since 1970
- static int now() { return syscall(SYS_TIME, 0, 0, 0); }
+ static int now() { return rt.syscall(rt.SYS_TIME, 0, 0, 0); }
// writes a string to a file, replacing what was there.
// path: the file's name
// s: the text to write
// returns: how many bytes were written, -1 if the file could not be opened
static int writefile(char* path, char* s) {
- int fd = syscall(SYS_OPEN, path, 1, 0), n;
+ int fd = rt.syscall(rt.SYS_OPEN, path, 1, 0), n;
if (fd < 0) return -1;
- n = syscall(SYS_WRITE, fd, s, len_w(s));
- syscall(SYS_CLOSE, fd, 0, 0);
+ n = rt.syscall(rt.SYS_WRITE, fd, s, rt.len_w(s));
+ rt.syscall(rt.SYS_CLOSE, fd, 0, 0);
return n;
}
}
-// ---- byte strings ----
-// the same helpers for packed byte strings. word strings above are unchanged.
-// a byte string is a word whose first word is its length (see len_s).
+//: ---- byte strings ----
+
+// the same helpers for packed byte strings. character-string helpers above are unchanged.
+// a packed byte string has type bstr; len_s reads its stored byte length.
struct Bytes {
- // like Text.atoi, for a byte string.
+ // reads a decimal number at the start of s; a leading '-' makes it
+ // negative, and the first non-digit ends the number.
// s: the byte string
- // returns: the number
- static int atoi(word s) { return s_to_n(s); }
+ // returns: the number, or 0 when s starts with no digit
+ static int atoi(bstr s) { return rt.s_to_n(s); }
// like Text.itoa, but the text is made fresh.
// n: the number
// returns: a new byte string
- static word itoa(int n) { return n_to_s(n); }
+ static bstr itoa(int n) { return rt.n_to_s(n); }
// like Text.starts, for byte strings.
// s: the byte string to test
// pfx: the beginning to look for
// returns: 1 if s begins with pfx, 0 if not
- static int starts(word s, word pfx) {
+ static int starts(bstr s, bstr pfx) {
+ int i = 0, n = rt.len_s(s), m = rt.len_s(pfx);
+ if (m > n) return 0;
+ while (i < m) { if (rt.at_s(s, i) != rt.at_s(pfx, i)) return 0; i++; }
+ return 1;
+ }
+
+ // the number of bytes in a packed byte string.
+ // s: the byte string
+ // returns: its stored length
+ static int len(bstr s) { return rt.len_s(s); }
+
+ // are two packed byte strings equal, byte for byte.
+ // a, b: the byte strings to compare
+ // returns: 1 if equal, 0 otherwise
+ static int eq(bstr a, bstr b) {
int i = 0;
- if (len_s(pfx) > len_s(s)) return 0;
- while (i < len_s(pfx)) { if (at_s(s, i) != at_s(pfx, i)) return 0; i++; }
+ if (rt.len_s(a) != rt.len_s(b)) return 0;
+ while (i < rt.len_s(a)) { if (rt.at_s(a, i) != rt.at_s(b, i)) return 0; i++; }
+ return 1;
+ }
+
+ // where pat first appears in s.
+ // s: the byte string to search
+ // pat: the byte string to find; an empty pat is found at 0
+ // returns: its byte index, or -1 when absent
+ static int find(bstr s, bstr pat) {
+ int i = 0, j, n = rt.len_s(s), m = rt.len_s(pat);
+ if (!m) return 0;
+ while (i + m <= n) {
+ j = 0;
+ while (j < m && rt.at_s(s, i + j) == rt.at_s(pat, j)) j++;
+ if (j == m) return i;
+ i++;
+ }
+ return 0 - 1;
+ }
+
+ // does s end with suffix? an empty suffix always matches.
+ // s: the byte string to test
+ // suffix: the ending to look for
+ // returns: 1 when it matches, 0 otherwise
+ static int ends(bstr s, bstr suffix) {
+ int n = rt.len_s(s), m = rt.len_s(suffix), i = 0;
+ if (m > n) return 0;
+ while (i < m) { if (rt.at_s(s, n - m + i) != rt.at_s(suffix, i)) return 0; i++; }
return 1;
}
// like Host.env, for byte strings.
// name: the variable's name, a byte string
- // dst: where the value goes, a word buffer
- // n: how many bytes dst can take
+ // dst: the packed byte string whose payload receives the value
+ // n: the maximum number of bytes to write
// returns: the length of the value, -1 if the variable is not set
- static int env(word name, word dst, int n) { return bgetenv(name, dst, n); }
+ static int env(bstr name, bstr dst, int n) { return rt.bgetenv(name, dst, n); }
// like Host.writefile, for a byte string.
// path: the file's name, a byte string
// s: the bytes to write
// returns: how many bytes were written, -1 if the file could not be opened
- static int writefile(word path, word s) {
- int fd = bopen(path, 1), n;
+ static int writefile(bstr path, bstr s) {
+ int fd = rt.bopen(path, 1), n;
if (fd < 0) return -1;
- n = bwrite(fd, s, len_s(s));
- syscall(SYS_CLOSE, fd, 0, 0);
+ n = rt.bwrite(fd, s, rt.len_s(s));
+ rt.syscall(rt.SYS_CLOSE, fd, 0, 0);
return n;
}
}
-// ---- arenas ----
+//: ---- arenas ----
// a region that is freed all at once.
-// n: its size in words
+// n: its capacity in words; values below 1 allocate one word
// returns: the arena, owned by the caller
-int .u arena(int n) { return arena_new(n); }
+int .u arena(int n) { return rt.arena_new(n); }
struct Arena {
- // takes n words from an arena.
+ // takes n zeroed words from an arena.
// a: the arena
// n: how many words
- // returns: the address of the first, 0 if the arena is full
- static int alloc(word a, int n) { return arena_alloc(a, n); }
+ // returns: the first word, or 0 when n words do not fit
+ static int alloc(word a, int n) { return rt.arena_alloc(a, n); }
- // gives every block of an arena back at once.
+ // gives every block of an arena back at once. pointers returned by alloc
+ // before the reset are no longer valid.
// a: the arena
// returns: 0
- static int reset(word a) { return arena_reset(a); }
+ static int reset(word a) { return rt.arena_reset(a); }
}
-// ---- growable byte buffers ----
+//: ---- growable byte buffers ----
-// a byte buffer that knows its length and its capacity: two words, then the
+// a byte buffer stores its length and capacity in two words, followed by the
// bytes, packed.
-// n: the capacity in bytes
-// returns: the buffer, owned by the caller, empty
+// n: the capacity in bytes; a negative value is treated as 0
+// returns: an empty buffer with that capacity, owned by the caller
word .u bnew(int n) {
int words;
word p;
if (n < 0) n = 0;
words = (n + 7) >> 3;
- p = new(2 + words);
+ p = rt.new(2 + words);
p[0] = 0;
p[1] = n;
return u*p;
@@ -438,9 +521,9 @@ struct Buf {
// b: the buffer
// i: the position
// returns: the byte, 0 when i is outside the buffer
- static int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return ld8(addr_b(b + 2) + i); }
+ static int get(word b, int i) { if (i < 0 || i >= b[0]) return 0; return rt.ld8(rt.addr_b(b + 2) + i); }
- // sets one byte of a buffer. setting the byte just past the end makes the
+ // sets one byte of a buffer. setting the byte at the current length makes the
// buffer one longer.
// b: the buffer
// i: the position, below the capacity
@@ -448,8 +531,74 @@ struct Buf {
// returns: the byte stored, 0 when i is outside the capacity
static int set(word b, int i, int v) {
if (i < 0 || i >= b[1]) return 0;
- st8(addr_b(b + 2) + i, v & 255);
+ rt.st8(rt.addr_b(b + 2) + i, v & 255);
if (i >= b[0]) b[0] = i + 1;
return v & 255;
}
}
+
+//: array algorithms borrow their input and visit elements in index order.
+//: map and filter return independent handles. reference elements are shared
+//: with the collector, so freeing the input does not invalidate the result.
+
+// the first index equal to value, or -1 when no element matches.
+int array_find<T>(fixed T[?].m source, T value) {
+ int length = len(source);
+ for (int i = 0; i < length; i++) if (source[i] == value) return i;
+ return -1;
+}
+// true when an element equals value.
+int array_contains<T>(fixed T[?].m source, T value) { return array_find(source, value) >= 0; }
+// the first index whose predicate is true, or -1 when none matches.
+int array_find_if<T>(fixed T[?].m source, int(T).m predicate) {
+ int length = len(source);
+ for (int i = 0; i < length; i++) if (predicate(source[i])) return i;
+ return -1;
+}
+// true when any element satisfies predicate; false for an empty array.
+int array_any<T>(fixed T[?].m source, int(T).m predicate) { return array_find_if(source, predicate) >= 0; }
+// true when every element satisfies predicate, including an empty array.
+int array_all<T>(fixed T[?].m source, int(T).m predicate) {
+ int length = len(source);
+ for (int i = 0; i < length; i++) if (!predicate(source[i])) return 0;
+ return 1;
+}
+// the number of elements satisfying predicate.
+int array_count<T>(fixed T[?].m source, int(T).m predicate) {
+ int length = len(source);
+ int count = 0;
+ for (int i = 0; i < length; i++) if (predicate(source[i])) count++;
+ return count;
+}
+// apply transform once to each element and return the transformed array.
+// the result element type follows the callback's return type.
+U[?].u array_map<T,U>(fixed T[?].m source, U(T).m transform) {
+ int length = len(source);
+ U[?] result = rt.new(U[?]);
+ for (int i = 0; i < length; i++) push(result, run.share_value(transform(source[i])));
+ return result;
+}
+// copy elements satisfying predicate into a new array, preserving order.
+T[?].u array_filter<T>(fixed T[?].m source, int(T).m predicate) {
+ int length = len(source);
+ T[?] result = rt.new(T[?]);
+ for (int i = 0; i < length; i++) if (predicate(source[i])) push(result, run.share_value(source[i]));
+ return result;
+}
+// fold elements from left to right, starting with initial; an empty array
+// returns initial without calling combine.
+A array_reduce<T,A>(fixed T[?].m source, A initial, A(A,T).m combine) {
+ int length = len(source);
+ for (int i = 0; i < length; i++) initial = combine(initial, source[i]);
+ return initial;
+}
+
+run.method("find", array_find);
+run.method("contains", array_contains);
+run.method("find_if", array_find_if);
+run.method("any", array_any);
+run.method("all", array_all);
+run.method("count", array_count);
+run.method("map", array_map);
+run.method("filter", array_filter);
+run.method("reduce", array_reduce);
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 98e71c3..a07e4d4 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -1,31 +1,34 @@
-// ============================================================================
-// server.zc: the language server. it is appended to the genome after the
-// compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not
-// analyze zc with a second implementation of the grammar: it runs the
-// compiler itself over the document, in check mode (see chk_unit in
-// compiler.zc), and reports what the compiler said. a parse word, an
-// operator or a type the document defines changes how the rest of the
-// document is read, for the checker and for the highlighter, because both are
-// the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not
-// a protocol stream but a source file it prints the diagnostics as text.
-// ============================================================================
+namespace lsp;
+include rt, run;
-// the first constant the server declares: the blob cut for a document starts
-// here (blob_load, compiler.zc), so this name must stay the first one of the server.
+//: ============================================================================
+//: server.zc: the language server. it is appended to the genome after the
+//: compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not
+//: analyze zc with a second implementation of the grammar: it runs the
+//: compiler itself over the document, in check mode (see chk_unit in
+//: compiler.zc), and reports what the compiler said. a parse word, an
+//: operator or a type the document defines changes how the rest of the
+//: document is read, for the checker and for the highlighter, because both are
+//: the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not
+//: a protocol stream but a source file it prints the diagnostics as text.
+//: ============================================================================
+
+//: the first constant the server declares: the blob cut for a document starts
+//: here (blob_load, compiler.zc), so this name must stay the first one of the server.
const int LSP_SERVER = 1;
// the compiler's own functions, declared here so this file checks on its own
// in an editor: the build places them in front of this file, so these lines only
// promise what is already defined there
-int chk_unit(int src, int buf, int cap, int sets, int lim);
-int hl_begin(int src);
-int hl_end();
+int run.chk_unit(int src, int buf, int cap, int sets, int lim);
+int run.hl_begin(int src);
+int run.hl_end();
-// ---- memory ----
-// nothing here has a fixed address. a document is a heap block of its text, the
-// diagnostics and the check's name sets are blocks too, and a check collects
-// what the last one left behind (chk_unit). a document compiles into the
-// program region from 0 up to CODE_END, which is the map's own.
+//: ---- memory ----
+//: nothing here has a fixed address. a document is a heap block of its text, the
+//: diagnostics and the check's name sets are blocks too, and a check collects
+//: what the last one left behind (chk_unit). a document compiles into the
+//: program region from 0 up to CODE_END, which is the map's own.
const int DGCAP = 12000;
word dgb, sets; int jsize, tcap;
@@ -33,25 +36,33 @@ int dgbcap = DGCAP;
const int MAXDOC = 16, URIW = 160;
-// ---- output ----
-// every message is written twice: once to count its bytes for the header,
-// once for real. wc is the only thing that touches the terminal.
+//: ---- output ----
+//: every message is written twice: once to count its bytes for the header,
+//: once for real. wc is the only thing that touches the terminal.
int wmode, wcnt;
+//: bounded cache of completed analysis replies; document generations and include epochs are part of every key.
+const int RC_LIMIT = 64, RC_W = 8;
+word[RC_LIMIT * RC_W] rc_meta; word[RC_LIMIT] rc_payload;
+int rc_pending = -1, rc_now = -1, rc_epoch, rc_tick, rc_hits, rc_misses, analysis_checks;
+char* rc_buffer; int rc_cap = 4096;
// writes one byte of output. while a message is being sized the byte is only
// counted; otherwise it goes to the terminal.
int wc(int c) {
- if (wmode) wcnt++; else raw_c(c);
+ if (wmode == 2) {
+ if (!rc_buffer || wcnt == rc_cap) { if (rc_buffer) rc_cap = rc_cap * 2; rc_buffer = lsp_grow(rc_buffer, wcnt, rc_cap); }
+ rc_buffer[wcnt] = c; wcnt++;
+ } else if (wmode) wcnt++; else rt.raw_c(c);
return 0;
}
-// writes a zero-terminated word string, one byte per character.
-int ws(word s) {
+// writes a zero-terminated char* string, one character per word.
+int ws(char* s) {
int i = 0;
while (s[i]) { wc(s[i]); i++; }
return 0;
}
// writes n in decimal, with a minus sign when it is negative.
int wn(int n) {
- [24]word b; int i = 0;
+ word[24] b; int i = 0;
if (n < 0) { wc('-'); n = 0 - n; }
while (1) { b[i] = '0' + n % 10; i++; n = n / 10; if (!n) break; }
while (i) { i--; wc(b[i]); }
@@ -75,7 +86,7 @@ int wesc(int c) {
return 0;
}
// a json string from n chars at p, one per word
-int wjs(word p, int n) {
+int wjs(char* p, int n) {
int i = 0;
wc(34);
while (i < n) { wesc(p[i]); i++; }
@@ -85,6 +96,13 @@ int wjs(word p, int n) {
// sends one message. gen writes its body: it runs once to count the bytes for the
// Content-Length header, and once more to write them.
int send(int gen) {
+ int n, k;
+ if (rc_pending >= 0 && (gen == gen_hover || gen == gen_tokens || gen == gen_nav || gen == gen_completion || gen == gen_null)) {
+ wmode = 2; wcnt = 0; gen(); n = wcnt; wmode = 0;
+ rc_store(rc_pending, n); rc_pending = -1;
+ ws("Content-Length: "); wn(n); ws("\r\n\r\n");
+ k = 0; while (k < n) { wc(rc_buffer[k]); k++; } return 0;
+ }
wmode = 1; wcnt = 0; gen();
wmode = 0;
ws("Content-Length: "); wn(wcnt); ws("\r\n\r\n");
@@ -92,18 +110,18 @@ int send(int gen) {
return 0;
}
-// ---- documents ----
-// a document is its text, four bytes to a word, in a heap block of its own
-// (doff), with a zero word after the last byte so the lexer sees the end. a
-// replaced text is a new block, and the old one is garbage for the collector.
+//: ---- documents ----
+//: a document is its text, four bytes to a word, in a heap block of its own
+//: (doff), with a zero word after the last byte so the lexer sees the end. a
+//: replaced text is a new block, and the old one is garbage for the collector.
int nodoc, doccap = MAXDOC;
-word duri, dulen, doff, dsize, dver, dgen, dlive;
+char** duri; word dulen, doff, dsize, dver, dgen, dlive;
// Grow a heap buffer while retaining its contents. Globals keep these buffers rooted.
-unchecked word lsp_grow(word old, int count, int capacity) {
- word p = new(capacity);
- fill_w(p, 0, capacity);
- if (old && count) copy_w(p, old, count);
+word .u lsp_grow(word old, int count, int capacity) {
+ word p = rt.new(capacity);
+ rt.fill_w(p, 0, capacity);
+ if (old && count) rt.copy_w(p, old, count);
return p;
}
// Document columns grow together; each URI has its own string allocation.
@@ -119,7 +137,7 @@ int docs_grow() {
// the byte at offset i of a text. texts are packed four bytes to a word, low byte first.
word dc(word base, int i) { return (base[i >> 2] >> ((i & 3) * 8)) & 255; }
// the document with the uri p (n bytes long), or -1 when the server has no such document.
-int dfind(word p, int n) {
+int dfind(char* p, int n) {
int i = 0, k;
while (i < doccap) {
if (dlive[i] && dulen[i] == n) {
@@ -133,7 +151,7 @@ int dfind(word p, int n) {
}
// takes a free document slot for the uri p (n bytes long), growing the columns
// when needed. commit_text sets its text after this.
-int docnew(word p, int n) {
+int docnew(char* p, int n) {
int i = 0, k;
while (1) {
if (i == doccap) docs_grow();
@@ -149,34 +167,43 @@ int docnew(word p, int n) {
}
// the text, its terminator and a spare zero word
int dwords(int n) { return (n >> 2) + 2; }
-// ---- the check ----
+//: ---- the check ----
int chk_i, chk_gen; // the document the compiler's tables describe, and its generation
+int chk_stream, chk_stale, chk_items, chk_next, streamed, unpub = 0 - 1; // see check_progress
// checks document i with the compiler in check mode. the diagnostics are left in
// dgb, and the types of the untyped lets are recorded while it runs (lt_on).
int check_doc(int i) {
+ analysis_checks++;
chk_i = i; chk_gen = dgen[i];
- outmode = 1;
- lt_on = 1; lt_n = 0; ltxn = 0; ht_n = 0;
- chk_unit(doff[i], dgb, dgbcap, sets, CODE_END);
- dgb = dgbuf; dgbcap = dgcap;
- lt_on = 0;
- outmode = 0;
+ rt.outmode = 1;
+ run.lt_on = 1; run.lt_n = 0; run.ltxn = 0; run.ht_n = 0;
+ inc_setup(i);
+ run.chk_unit(doff[i], dgb, dgbcap, sets, rt.CODE_END);
+ dgb = run.dgbuf; dgbcap = run.dgcap;
+ run.lt_on = 0;
+ rt.outmode = 0;
return 0;
}
+// a check for diagnostics streams what it has found after each top-level item and is dropped
+// the moment the host says more input is waiting, which may be the text that replaces this one.
+// a dropped check leaves the document unpublished (unpub) until the next check of it finishes.
// the entry of the untyped local declared at byte offset o, or -1
int lt_find(int o) {
int k = 0;
- while (k < lt_n) { if (lt_off[k] == o) return k; k++; }
+ while (k < run.lt_n) { if (run.lt_off[k] == o) return k; k++; }
return 0 - 1;
}
int ensure_checked(int i) {
- if (chk_i != i || chk_gen != dgen[i]) check_doc(i);
+ if (chk_i != i || chk_gen != dgen[i]) {
+ check_doc(i);
+ if (unpub == i) { unpub = 0 - 1; publish(i); }
+ }
return 0;
}
-// ---- positions ----
-// a cursor over the document that turns byte offsets into the line and the
-// utf-16 column the protocol wants. it only moves forward, or starts over.
+//: ---- positions ----
+//: a cursor over the document that turns byte offsets into the line and the
+//: utf-16 column the protocol wants. it only moves forward, or starts over.
int pbase, plen, poff, pline, pcol;
// starts the position cursor at the top of document i.
int pc_reset(int i) {
@@ -210,7 +237,7 @@ int line_off(int l) {
return o;
}
-// ---- diagnostics ----
+//: ---- diagnostics ----
int gd; // the document being reported
// writes a json position, {line, character}, for byte offset off.
int wpos(int off) {
@@ -232,7 +259,7 @@ int gen_diag() {
ws(",\"version\":"); wn(dver[gd]);
ws(",\"diagnostics\":[");
pc_reset(gd);
- while (at < dgpos) {
+ while (at < run.dgpos) {
sev = dgb[at]; rln = dgb[at + 1]; off = dgb[at + 2]; rlen = dgb[at + 3]; n = dgb[at + 4];
if (off < 0) { // the compiler only knew the line: mark its text
o = line_off(rln);
@@ -271,10 +298,36 @@ int publish(int i) {
send(gen_diag);
return 0;
}
+const int STREAM_AFTER = 100; // a document of fewer top-level items is checked in one go: dropping it would cost more
+int check_progress() {
+ int sm, sc, sn, sg; word sb;
+ if (!chk_stream) return 0;
+ chk_items++;
+ if (chk_items < STREAM_AFTER) return 0;
+ if (rt.syscall(42, 0, 0, 0) > 0) { chk_stale = 1; run.chkabort = 1; return 0; }
+ // what has been found goes out when the item count doubles, so the total stays linear
+ if (chk_items < chk_next || run.dgpos == streamed) return 0;
+ chk_next = chk_items * 2;
+ sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; sg = dgb;
+ rt.outmode = 0; dgb = run.dgbuf; gd = chk_i;
+ send(gen_diag);
+ streamed = run.dgpos;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; dgb = sg;
+ return 0;
+}
+// check document i for publication: streaming, and dropped when its text is replaced. true when it finished
+int check_publish(int i) {
+ chk_stream = 1; chk_stale = 0; chk_items = 0; chk_next = STREAM_AFTER; streamed = 0; run.chk_hook = check_progress;
+ check_doc(i);
+ run.chk_hook = 0; chk_stream = 0;
+ if (chk_stale) { chk_i = 0 - 1; unpub = i; return 0; }
+ if (unpub == i) unpub = 0 - 1;
+ return 1;
+}
-// ---- semantic tokens ----
-// the legend: keyword type function variable string number operator comment
-// parameter property macro enumMember
+//: ---- semantic tokens ----
+//: the legend: keyword type function variable string number operator comment
+//: parameter property macro enumMember
const int TT_KW = 0, TT_TYPE = 1, TT_FN = 2, TT_VAR = 3, TT_STR = 4, TT_NUM = 5, TT_OP = 6, TT_COM = 7, TT_PARAM = 8, TT_PROP = 9, TT_MACRO = 10, TT_ENUM = 11;
int tfirst, tprevl, tprevc;
// one token on one line
@@ -320,7 +373,7 @@ int gap_out(int from, int to) {
}
// is c an operator character, which the highlighter marks as an operator.
int isopc(int c) {
- word s = "+-*/%=<>!&|^~?@#$";
+ char* s = "+-*/%=<>!&|^~?@#$";
int i = 0;
while (s[i]) { if (s[i] == c) return 1; i++; }
return 0;
@@ -328,84 +381,94 @@ int isopc(int c) {
// is the name n one of the words the highlighter shows as keywords even when the
// compiler has no record for it.
int iskeyword(int n) {
- return n == s_fn || n == s_var || n == s_const || n == s_else || n == s_as || n == s_unchecked || n == s_immediate
- || n == s_case || n == s_default || n == s_do || n == s_while || n == s_enum || n == s_include || n == s_memdebug
- || n == s_enhance || n == s_micro || n == s_vm || n == s_struct || n == s_type || n == s_typecheck;
+ return n == run.s_fn || n == run.s_var || n == run.s_const || n == run.s_else || n == run.s_as || n == run.s_unchecked || n == run.s_immediate
+ || n == run.s_case || n == run.s_default || n == run.s_do || n == run.s_while || n == run.s_enum || n == run.s_include || n == run.s_memdebug
+ || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typecheck || n == run.s_static;
}
// the record of the name at byte offset o (see rs_note in the compiler), or -1.
// the records are sorted by offset, so this is a binary search.
int rs_find(int o) {
- int lo = 0, hi = rsn - 1, mid, v;
+ int lo = 0, hi = run.rsn - 1, mid, v, s, k; char* name; word p;
while (lo <= hi) {
mid = (lo + hi) / 2;
- v = rsrec[mid * RS_W];
+ v = run.rsrec[mid * run.RS_W];
if (v == o) return mid;
if (v < o) lo = mid + 1; else hi = mid - 1;
}
+ // qualified module names share one compiler token; the cursor may be on
+ // any component. confirm the complete source spelling before using it.
+ if (hi >= 0 && chk_i >= 0) {
+ p = run.rsrec + hi * run.RS_W;
+ if (p[1] == run.RS_SYM && p[3] >= 0) {
+ s = p[3]; name = run.sname[s]; k = 0;
+ while (name[k] && p[0] + k < dsize[chk_i] && dc(doff[chk_i], p[0] + k) == name[k]) k++;
+ if (!name[k] && o < p[0] + k) return hi;
+ }
+ }
return 0 - 1;
}
-// rs_cur is the cursor of the highlighter's pass over the records, which move
-// forward with the tokens.
+//: rs_cur is the cursor of the highlighter's pass over the records, which move
+//: forward with the tokens.
int rs_cur;
// the class of an identifier at byte offset o: what the compiler resolved it to
// when it checked the document, so a local named like a built-in is a variable.
// classify guesses from the name alone, and only serves names with no record.
int rs_class(int o, int n, int pt, int pv) {
int r = -1, k, v, sk;
- while (rs_cur < rsn && rsrec[rs_cur * RS_W] < o) rs_cur++;
- if (rs_cur < rsn && rsrec[rs_cur * RS_W] == o) r = rs_cur;
+ while (rs_cur < run.rsn && run.rsrec[rs_cur * run.RS_W] < o) rs_cur++;
+ if (rs_cur < run.rsn && run.rsrec[rs_cur * run.RS_W] == o) r = rs_cur;
if (r < 0) return classify(n, pt, pv);
- k = rsrec[r * RS_W + 1];
- if (k == RS_LOCAL) return TT_VAR;
- if (k == RS_PARAM) return TT_PARAM;
- if (k == RS_FIELD) return TT_PROP;
- v = rsrec[r * RS_W + 2];
+ k = run.rsrec[r * run.RS_W + 1];
+ if (k == run.RS_LOCAL) return TT_VAR;
+ if (k == run.RS_PARAM) return TT_PARAM;
+ if (k == run.RS_FIELD) return TT_PROP;
+ v = run.rsrec[r * run.RS_W + 2];
sk = v & 15;
- if (sk == K_KW) { if (v & 16) return TT_MACRO; return TT_KW; }
- if (sk == K_TYPE) return TT_TYPE;
- if (sk == K_FN) { if (v & 32) return TT_FN; return TT_VAR; }
- if (sk == K_ENH) return TT_FN;
- if (sk == K_CONST) return TT_ENUM;
+ if (sk == run.K_KW) { if (v & 16) return TT_MACRO; return TT_KW; }
+ if (sk == run.K_TYPE) return TT_TYPE;
+ if (sk == run.K_FN) { if (v & 32) return TT_FN; return TT_VAR; }
+ if (sk == run.K_ENH) return TT_FN;
+ if (sk == run.K_CONST) return TT_ENUM;
return TT_VAR;
}
// the class of an identifier. pt, pv: the token before it
int classify(int n, int pt, int pv) {
int s, nx = 0;
// a name after fn is a declaration; a name after . or -> that is called is a method
- if (pt == T_ID && pv == s_fn) return TT_FN;
- if (pt == T_OP && pv == s_dot) {
- if (n == s_m || n == s_d || n == s_u) return TT_KW;
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) return TT_FN;
+ if (pt == run.T_ID && pv == run.s_fn) return TT_FN;
+ if (pt == run.T_OP && pv == run.s_dot) {
+ if (n == run.s_m || n == run.s_d || n == run.s_u) return TT_KW;
+ run.peek();
+ if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) return TT_FN;
return TT_PROP;
}
- if (pt == T_OP && pv == s_arrow) {
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) return TT_FN;
+ if (pt == run.T_OP && pv == run.s_arrow) {
+ run.peek();
+ if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) return TT_FN;
return TT_PROP;
}
if (iskeyword(n)) return TT_KW;
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) nx = 1;
- s = lookup(n);
+ run.peek();
+ if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) nx = 1;
+ s = run.lookup(n);
if (s >= 0) {
- if (skind[s] == K_KW) { if (s >= ubase) return TT_MACRO; return TT_KW; }
- if (skind[s] == K_TYPE) return TT_TYPE;
- if (skind[s] == K_FN && !(sflag[s] & F_DEF)) return TT_VAR; // never defined: the check says so
- if (skind[s] == K_FN || skind[s] == K_ENH) {
+ if (run.skind[s] == run.K_KW) { if (s >= run.ubase) return TT_MACRO; return TT_KW; }
+ if (run.skind[s] == run.K_TYPE) return TT_TYPE;
+ if (run.skind[s] == run.K_FN && !(run.sflag[s] & run.F_DEF)) return TT_VAR; // never defined: the check says so
+ if (run.skind[s] == run.K_FN || run.skind[s] == run.K_ENH) {
if (nx) return TT_FN;
- if (lsget(1, n)) return TT_PARAM;
- if (lsget(0, n)) return TT_VAR;
+ if (run.lsget(1, n)) return TT_PARAM;
+ if (run.lsget(0, n)) return TT_VAR;
return TT_FN;
}
- if (skind[s] == K_CONST) return TT_ENUM;
- if (lsget(1, n)) return TT_PARAM;
+ if (run.skind[s] == run.K_CONST) return TT_ENUM;
+ if (run.lsget(1, n)) return TT_PARAM;
return TT_VAR;
}
- if (lsget(2, n)) return TT_TYPE;
- if (lsget(1, n)) return TT_PARAM;
- if (lsget(0, n)) return TT_VAR;
- if (lsget(3, n)) return TT_PROP;
+ if (run.lsget(2, n)) return TT_TYPE;
+ if (run.lsget(1, n)) return TT_PARAM;
+ if (run.lsget(0, n)) return TT_VAR;
+ if (run.lsget(3, n)) return TT_PROP;
return TT_VAR;
}
// writes the semantic tokens of document gd: every token in order, each with the
@@ -416,40 +479,119 @@ int gen_tokens() {
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"data\":[");
pc_reset(gd);
tfirst = 1; tprevl = 0; tprevc = 0; rs_cur = 0;
- hl_begin(doff[gd]);
- while (tk != T_EOF) {
- o = toff; e = toff + toklen;
- if (toklen > 0 && o >= last) {
+ run.hl_begin(doff[gd]);
+ while (run.tk != run.T_EOF) {
+ o = run.toff; e = run.toff + run.toklen;
+ if (run.toklen > 0 && o >= last) {
gap_out(last, o);
ty = 0 - 1;
- if (tk == T_ID) ty = rs_class(o, tv, pt, pv);
- else if (tk == T_NUM) {
+ if (run.tk == run.T_ID) ty = rs_class(o, run.tv, pt, pv);
+ else if (run.tk == run.T_NUM) {
c = dc(pbase, o);
if (c == 39) ty = TT_STR; else ty = TT_NUM;
- } else if (tk == T_FLT) ty = TT_NUM;
- else if (tk == T_STR || tk == T_BSTR) ty = TT_STR;
- else if (tk == T_OP) { if (isopc(dc(pbase, o))) ty = TT_OP; }
+ } else if (run.tk == run.T_FLT) ty = TT_NUM;
+ else if (run.tk == run.T_STR || run.tk == run.T_BSTR) ty = TT_STR;
+ else if (run.tk == run.T_OP) { if (isopc(dc(pbase, o))) ty = TT_OP; }
if (ty >= 0) range_out(o, e, ty);
last = e;
}
- pt = tk; pv = tv;
- next();
+ pt = run.tk; pv = run.tv;
+ run.next();
}
gap_out(last, plen);
- hl_end();
+ run.hl_end();
ws("]}}");
return 0;
}
-// ---- json in ----
-// the body of a message is read straight off stdin a char at a time and
-// never held whole: the fields the server cares about are picked out as they
-// go by, and a document's text is decoded into the store as it arrives.
-int jleft, jpk;
-int hline, hchar, hn, hoff, hcursor; word hov; int hovcap = 2300; word htb; int htbc = 80, htn, ann_name_at; word hname; int hnamecap = 80; int hdl, hdle, hcs;
-word ann_text; int ann_n;
-word mbuf, idbuf, jk, kbuf; int mcap = 64, idcap = 64, jkcap = 16, kcap = 40; int mn, idn, idtype; word ubuf; int ucap = URIW; int un, jver, kn;
word tbase; int tn, thave, tover, tuseen;
+//: ---- files and paths ----
+//: the compiler looks an include up next to the file that has it, then in the workspace folder. a client with no
+//: way for the server to read files (the extension's wasm and vm servers) sends the text of every file a document
+//: includes (zc/file), keyed by its full path; the compiler asks for them through inc_hook before it reads a file.
+char* rootbuf; int rootcap = 256, rootn, rootset; // the workspace folder: params.rootUri of initialize
+char* pathbuf; int pathcap = 256, pathn; // the path of a file the client sends (zc/file)
+char** vf_path; char** vf_text; int vf_n, vf_cap;
+// the path inside a file: uri, decoded, with no drive-letter slash
+char*.u uri_path(char* uri, int n) {
+ char* o; int i = 0, k = 0, c, h1, h2;
+ o = lsp_grow(0, 0, n + 2);
+ if (n >= 7 && uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l' && uri[3] == 'e' && uri[4] == ':') {
+ i = 5;
+ if (uri[i] == '/' && uri[i + 1] == '/') i = 7;
+ } else return 0;
+ while (i < n) {
+ c = uri[i];
+ if (c == '%' && i + 2 < n) {
+ h1 = uri[i + 1]; h2 = uri[i + 2];
+ if (h1 >= 'a') h1 = h1 - 32;
+ if (h2 >= 'a') h2 = h2 - 32;
+ if (h1 >= 'A') h1 = h1 - 55; else h1 = h1 - '0';
+ if (h2 >= 'A') h2 = h2 - 55; else h2 = h2 - '0';
+ if (h1 >= 0 && h1 < 16 && h2 >= 0 && h2 < 16) { c = h1 * 16 + h2; i = i + 2; }
+ }
+ o[k] = c; k++; i++;
+ }
+ o[k] = 0;
+ // /c:/dir: the slash before a drive letter is not part of the path
+ if (k > 2 && o[0] == '/' && o[2] == ':') {
+ while (k > 0) { o[k - 1] = o[k]; k--; }
+ }
+ return o;
+}
+// the directory of document i, or 0 when it is not a file
+char* doc_dir(int i) {
+ char* path;
+ path = uri_path(duri[i], dulen[i]);
+ if (!path) return 0;
+ return run.inc_dir(path);
+}
+// the text the client sent for the file at path, or 0
+char* vfs_lookup(char*.m path) {
+ int k = 0;
+ while (k < vf_n) { if (rt.eq_w(vf_path[k], path)) return vf_text[k]; k++; }
+ return 0;
+}
+// keep (or with no text, forget) a file: path in pathbuf, its text in tbase / tn
+int vfs_set(int have) {
+ char* key; char* text; int k = 0, j;
+ key = run.inc_norm(pathbuf);
+ while (k < vf_n && !rt.eq_w(vf_path[k], key)) k++;
+ if (!have) {
+ if (k < vf_n) { vf_n--; vf_path[k] = vf_path[vf_n]; vf_text[k] = vf_text[vf_n]; vf_path[vf_n] = 0; vf_text[vf_n] = 0; }
+ return 0;
+ }
+ text = lsp_grow(0, 0, tn + 2);
+ j = 0; while (j < tn) { text[j] = dc(tbase, j); j++; }
+ text[tn] = 0;
+ if (k == vf_n) {
+ if (vf_n == vf_cap) {
+ if (!vf_cap) vf_cap = 32; else vf_cap = vf_cap * 2;
+ vf_path = lsp_grow(vf_path, vf_n, vf_cap); vf_text = lsp_grow(vf_text, vf_n, vf_cap);
+ }
+ vf_n++;
+ }
+ vf_path[k] = key; vf_text[k] = text;
+ return 0;
+}
+// point the compiler's include lookup at document i before it checks it
+int inc_setup(int i) {
+ run.incdir = doc_dir(i);
+ run.increoot = 0;
+ if (rootset && rootn) run.increoot = uri_path(rootbuf, rootn);
+ run.inc_hook = vfs_lookup;
+ return 0;
+}
+
+//: ---- json in ----
+//: the body of a message is read straight off stdin a char at a time and
+//: never held whole: the fields the server cares about are picked out as they
+//: go by, and a document's text is decoded into the store as it arrives.
+int jleft, jpk;
+int fm_edit_tab, fm_edit_width, fm_edit_set; // the editor's tab settings, from a formatting request
+int rdecl = 1, hline, hchar, hn, hoff, hcursor, ca_endline = -1, ca_endchar; char* hov; int hovcap = 2300; char* htb; int htbc = 80, htn, ann_name_at; char* hname; int hnamecap = 80; int hdl, hdle, hcs;
+char* ann_text; int ann_n;
+char* mbuf, idbuf, jk, kbuf; int mcap = 64, idcap = 64, jkcap = 16, kcap = 40; int mn, idn, idtype; char* ubuf; int ucap = URIW; int un, jver, kn;
// the next byte of the json body. the body is read from stdin and stops after
// jleft bytes; -1 means the end. junget returns a byte to the next jgetc.
int jgetc() {
@@ -457,8 +599,8 @@ int jgetc() {
if (jpk != 0 - 2) { c = jpk; jpk = 0 - 2; return c; }
if (jleft <= 0) return 0 - 1;
jleft--;
- if (eof()) { jleft = 0; return 0 - 1; }
- return get_c() & 255;
+ if (rt.eof()) { jleft = 0; return 0 - 1; }
+ return rt.get_c() & 255;
}
// return byte c, so that the next jgetc reads it again.
int junget(int c) { jpk = c; return 0; }
@@ -492,6 +634,12 @@ int jput(int sink, int b) {
} else if (sink == 5) {
if (kn + 1 >= kcap) { kbuf = lsp_grow(kbuf, kn, kcap * 2); kcap = kcap * 2; }
kbuf[kn] = b; kn++;
+ } else if (sink == 6) {
+ if (rootn + 1 >= rootcap) { rootbuf = lsp_grow(rootbuf, rootn, rootcap * 2); rootcap = rootcap * 2; }
+ rootbuf[rootn] = b; rootn++;
+ } else if (sink == 7) {
+ if (pathn + 1 >= pathcap) { pathbuf = lsp_grow(pathbuf, pathn, pathcap * 2); pathcap = pathcap * 2; }
+ pathbuf[pathn] = b; pathn++;
}
return 0;
}
@@ -516,7 +664,7 @@ int jutf8(int sink, int cp) {
int jhex4() {
int v = 0, i = 0, c, h;
while (i < 4) {
- c = jgetc(); h = hexv(c);
+ c = jgetc(); h = run.hexv(c);
if (h < 0) { junget(c); return v; }
v = v * 16 + h; i++;
}
@@ -551,17 +699,26 @@ int jstr(int sink) {
}
// the code of the json key in kbuf, for the keys the server reads (0 for the others).
int kcode() {
- if (eq_w(kbuf, "method")) return 1;
- if (eq_w(kbuf, "id")) return 2;
- if (eq_w(kbuf, "params")) return 3;
- if (eq_w(kbuf, "textDocument")) return 4;
- if (eq_w(kbuf, "uri")) return 5;
- if (eq_w(kbuf, "text")) return 6;
- if (eq_w(kbuf, "contentChanges")) return 7;
- if (eq_w(kbuf, "version")) return 8;
- if (eq_w(kbuf, "position")) return 9;
- if (eq_w(kbuf, "line")) return 10;
- if (eq_w(kbuf, "character")) return 11;
+ if (rt.eq_w(kbuf, "method")) return 1;
+ if (rt.eq_w(kbuf, "id")) return 2;
+ if (rt.eq_w(kbuf, "params")) return 3;
+ if (rt.eq_w(kbuf, "textDocument")) return 4;
+ if (rt.eq_w(kbuf, "uri")) return 5;
+ if (rt.eq_w(kbuf, "text")) return 6;
+ if (rt.eq_w(kbuf, "contentChanges")) return 7;
+ if (rt.eq_w(kbuf, "version")) return 8;
+ if (rt.eq_w(kbuf, "position")) return 9;
+ if (rt.eq_w(kbuf, "line")) return 10;
+ if (rt.eq_w(kbuf, "character")) return 11;
+ if (rt.eq_w(kbuf, "includeDeclaration")) return 12;
+ if (rt.eq_w(kbuf, "rootUri")) return 15;
+ if (rt.eq_w(kbuf, "path")) return 16;
+ if (rt.eq_w(kbuf, "tabSize")) return 13;
+ if (rt.eq_w(kbuf, "insertSpaces")) return 14;
+ if (rt.eq_w(kbuf, "range")) return 17;
+ if (rt.eq_w(kbuf, "start")) return 18;
+ if (rt.eq_w(kbuf, "diagnostics")) return 19;
+ if (rt.eq_w(kbuf, "end")) return 20;
return 0;
}
// is the key code one of the first dep keys on the json path to the value being read.
@@ -609,11 +766,15 @@ int jval(int key, int dep) {
if (key == 1 && dep == 1) { sink = 1; mn = 0; }
else if (key == 2 && dep == 1) { sink = 2; idn = 0; idtype = 2; }
else if (key == 5 && janc(dep, 4)) { sink = 3; un = 0; tuseen = 1; }
- else if (key == 6 && (janc(dep, 7) || janc(dep, 4))) { sink = 4; thave = 1; tover = 0; tbase = text_start(); tn = 0; }
+ else if (key == 6 && (janc(dep, 7) || janc(dep, 4) || (dep == 2 && janc(dep, 3)))) { sink = 4; thave = 1; tover = 0; tbase = text_start(); tn = 0; }
+ else if (key == 15 && dep == 2) { sink = 6; rootn = 0; rootset = 1; }
+ else if (key == 16 && dep == 2) { sink = 7; pathn = 0; }
jstr(sink);
if (sink == 1) mbuf[mn] = 0;
if (sink == 2) idbuf[idn] = 0;
if (sink == 3) ubuf[un] = 0;
+ if (sink == 6) rootbuf[rootn] = 0;
+ if (sink == 7) pathbuf[pathn] = 0;
if (sink == 4) {
k = tbase + (tn >> 2); // zero the rest of the last word and the one after it
if ((tn & 3) == 0) k[0] = 0;
@@ -626,43 +787,48 @@ int jval(int key, int dep) {
sink = 0;
if (key == 2 && dep == 1) { sink = 2; idn = 0; idtype = 1; }
k = 0; neg = 0;
+ if (key == 12) rdecl = c == 't';
+ if (key == 14) { fm_edit_set = 1; fm_edit_tab = c != 't'; }
while (c >= 0 && c != ',' && c != '}' && c != ']' && c != 32 && c != 10 && c != 13 && c != 9) {
if (sink == 2) jput(2, c);
- if ((key == 8 && janc(dep, 4)) || ((key == 10 || key == 11) && janc(dep, 9))) {
+ if ((key == 8 && janc(dep, 4)) || key == 13 || ((key == 10 || key == 11) && (janc(dep, 9) || janc(dep, 17)))) {
if (c == '-') {
neg = 1;
- } else if (isdig(c)) k = k * 10 + c - '0';
+ } else if (run.isdig(c)) k = k * 10 + c - '0';
}
c = jgetc();
}
junget(c);
if (sink == 2) {
idbuf[idn] = 0;
- if (eq_w(idbuf, "null")) idtype = 0;
+ if (rt.eq_w(idbuf, "null")) idtype = 0;
}
if (key == 8 && janc(dep, 4)) { if (neg) k = 0 - k; jver = k; }
- if (key == 10 && janc(dep, 9)) hline = k;
- if (key == 11 && janc(dep, 9)) hchar = k;
+ if (key == 13) { fm_edit_set = 1; fm_edit_width = k; }
+ if (key == 10 && (janc(dep, 9) || (janc(dep, 17) && janc(dep, 18) && !janc(dep, 19)))) hline = k;
+ if (key == 11 && (janc(dep, 9) || (janc(dep, 17) && janc(dep, 18) && !janc(dep, 19)))) hchar = k;
+ if (key == 10 && janc(dep, 17) && janc(dep, 20) && !janc(dep, 19)) ca_endline = k;
+ if (key == 11 && janc(dep, 17) && janc(dep, 20) && !janc(dep, 19)) ca_endchar = k;
return 0;
}
// the text of a document being read goes into a block that holds the whole
// message: decoded text is never longer than the message it came in
word text_start() {
tcap = dwords(jsize);
- tbase = new(tcap);
+ tbase = rt.new(tcap);
return tbase;
}
-// ---- messages in ----
-word lbuf; int lcap = 160; int rfirst = 0 - 1;
+//: ---- messages in ----
+char* lbuf; int lcap = 160; int rfirst = 0 - 1;
// one header line, without the line end. returns its length, or -1 at the end of the input
int rl() {
int n = 0, c;
while (1) {
if (rfirst >= 0) { c = rfirst; rfirst = 0 - 1; }
else {
- if (eof()) { if (!n) return 0 - 1; return n; }
- c = get_c() & 255;
+ if (rt.eof()) { if (!n) return 0 - 1; return n; }
+ c = rt.get_c() & 255;
}
if (c == 10) return n;
if (c != 13) {
@@ -680,7 +846,7 @@ int lower(int c) {
// does the header line in lbuf start with content-length: (in any case).
int is_clen(int n) {
if (n < 15) return 0;
- word s = "content-length:"; int i = 0;
+ char* s = "content-length:"; int i = 0;
while (s[i]) { if (lower(lbuf[i]) != s[i]) return 0; i++; }
return 1;
}
@@ -695,18 +861,19 @@ int read_message() {
if (is_clen(n)) {
i = 15; size = 0;
while (i < n && (lbuf[i] == 32 || lbuf[i] == 9)) i++;
- while (i < n && isdig(lbuf[i])) { size = size * 10 + lbuf[i] - '0'; i++; }
+ while (i < n && run.isdig(lbuf[i])) { size = size * 10 + lbuf[i] - '0'; i++; }
}
}
jleft = size; jpk = 0 - 2; jsize = size;
mn = 0; mbuf[0] = 0; idn = 0; idbuf[0] = 0; idtype = 0; un = 0; ubuf[0] = 0; jver = 0;
- thave = 0; tover = 0; tuseen = 0; tn = 0; tbase = 0; hline = 0; hchar = 0;
+ thave = 0; tover = 0; tuseen = 0; tn = 0; tbase = 0; hline = 0; hchar = 0; rdecl = 1; fm_edit_set = 0; pathn = 0;
+ ca_endline = -1; ca_endchar = 0;
jval(0, 0);
while (jleft > 0) jgetc(); // whatever the parser left behind
return 1;
}
-// ---- messages out ----
+//: ---- messages out ----
int wid() {
if (idtype == 2) {
wjs(idbuf, idn);
@@ -718,7 +885,7 @@ int wid() {
int gen_init() {
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid();
ws(",\"result\":{\"capabilities\":{\"textDocumentSync\":{\"openClose\":true,\"change\":1,\"save\":{\"includeText\":true}},");
- ws("\"hoverProvider\":true,\"semanticTokensProvider\":{\"legend\":{\"tokenTypes\":[\"keyword\",\"type\",\"function\",\"variable\",\"string\",\"number\",\"operator\",\"comment\",\"parameter\",\"property\",\"macro\",\"enumMember\"],\"tokenModifiers\":[]},\"full\":true}},");
+ ws("\"completionProvider\":{\"triggerCharacters\":[\".\",\">\"]},\"hoverProvider\":true,\"definitionProvider\":true,\"documentFormattingProvider\":true,\"referencesProvider\":true,\"codeActionProvider\":{\"codeActionKinds\":[\"quickfix\"]},\"semanticTokensProvider\":{\"legend\":{\"tokenTypes\":[\"keyword\",\"type\",\"function\",\"variable\",\"string\",\"number\",\"operator\",\"comment\",\"parameter\",\"property\",\"macro\",\"enumMember\"],\"tokenModifiers\":[]},\"full\":true}},");
ws("\"serverInfo\":{\"name\":\"zc\",\"version\":\"2\"}}}");
return 0;
}
@@ -729,7 +896,7 @@ int gen_null() {
ws(",\"result\":null}");
return 0;
}
-// the error code the next error reply carries.
+//: the error code the next error reply carries.
int errcode;
// an error reply with the code errcode and a matching message.
int gen_error() {
@@ -756,10 +923,15 @@ int reply_error(int code) {
return 0;
}
-// ---- hover ----
-// the documentation of the name under the cursor: the comment lines directly
-// above its declaration in the document, or, for what the compiler and the
-// standard library define, the docs stored in the image
+int hs_name = -1, hs_first = -1, hs_const;
+int bs_k, bs_off; // the embedded source and name offset found by bs_find
+
+//: ---- hover ----
+//: the documentation of the name under the cursor: the comment lines directly
+//: above its declaration in the document, or, for what the compiler and the
+//: standard library define, the docs stored in the image
+
+// reports whether a character can occur in an identifier.
int hid(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; }
// the identifier at hline, hchar of document i, into hname. returns its length
int hover_word(int i) {
@@ -815,6 +987,8 @@ word hover_decl(int base, int ls, int le) {
// it (hcs is -1 when none) which runs to the line at hdl. a declaration needs
// no comment, so the signature still shows without one.
int hover_scan(int i) {
+ hs_name = -1; hs_first = -1; hs_const = 0;
+ if (hover_source_decl(i) || hover_source_function(i)) return 1;
int base = doff[i], size = dsize[i], ls = 0, le, cs = 0 - 1, c0;
while (ls < size) {
le = ls;
@@ -839,11 +1013,11 @@ int hov_put(int c) {
hov[hn] = c; hn++;
return 0;
}
-// comment ranges are scanned once per document version, outside strings and chars.
+//: comment ranges are scanned once per document version, outside strings and chars.
int hc_i = -1, hc_gen = -1, hc_n, hc_cap = 64;
word hc_start, hc_end, hc_kind;
int hover_comments(int i) {
- int o = 0, size = dsize[i], base = doff[i], c, start, kind, bol = 1, quote;
+ int o = 0, size = dsize[i], base = doff[i], c, run.start, kind, bol = 1, quote;
if (hc_i == i && hc_gen == dgen[i]) return 0;
hc_i = i; hc_gen = dgen[i]; hc_n = 0;
while (o < size) {
@@ -855,9 +1029,9 @@ int hover_comments(int i) {
while (o < size && dc(base, o) != quote) { if (dc(base, o) == 92) o++; o++; }
o++; bol = 0; continue;
}
- kind = 0; start = o;
+ kind = 0; run.start = o;
if (c == '/' && dc(base, o + 1) == '/') {
- kind = 1; if (bol) kind = 2;
+ kind = 1; if (bol) kind = 2; if (dc(base, o + 2) == ':') kind = 4;
o = o + 2; while (o < size && dc(base, o) != 10) o++;
} else if (c == '/' && dc(base, o + 1) == '*') {
kind = 3; o = o + 2;
@@ -868,7 +1042,7 @@ int hover_comments(int i) {
if (hc_start) hc_cap = hc_cap * 2;
hc_start = lsp_grow(hc_start, hc_n, hc_cap); hc_end = lsp_grow(hc_end, hc_n, hc_cap); hc_kind = lsp_grow(hc_kind, hc_n, hc_cap);
}
- hc_start[hc_n] = start; hc_end[hc_n] = o; hc_kind[hc_n] = kind; hc_n++;
+ hc_start[hc_n] = run.start; hc_end[hc_n] = o; hc_kind[hc_n] = kind; hc_n++;
}
return 0;
}
@@ -906,22 +1080,77 @@ int hover_comment_text(int i, int index) {
}
return hn - before;
}
-// attach leading docs and adjacent block docs to this exact declaration name.
-int hover_docs(int i, int head, int name, int length) {
- int k, first = -1, last = -1, pos = head, base = doff[i];
+// a trailing declaration comment belongs to its last name, not the next line.
+// commas in calls and subscripts do not separate declarations.
+int doc_trailing(int base, int from, int to) {
+ int c, quote, paren = 0, bracket = 0, ended = 0;
+ while (from < to) {
+ c = dc(base, from); from++;
+ if (c == 10 || c == 13) return 0;
+ if (c == 32 || c == 9) continue;
+ if (ended) return 0;
+ if (c == 34 || c == 39) {
+ quote = c;
+ while (from < to && dc(base, from) != quote) { if (dc(base, from) == 92) from++; from++; }
+ if (from >= to) return 0; from++; continue;
+ }
+ if (c == '(') paren++; else if (c == ')') { if (!paren) return 0; paren--; }
+ else if (c == '[') bracket++; else if (c == ']') { if (!bracket) return 0; bracket--; }
+ else if (c == '{' || c == '}') return 0;
+ else if (!paren && !bracket && c == ',') return 0;
+ else if (!paren && !bracket && c == ';') ended = 1;
+ }
+ return !paren && !bracket;
+}
+// the comment from..to sits on one line with its declaration: no line break before it, only white space after it
+int doc_same_line(int base, int from, int to) {
+ while (from < to) { if (dc(base, from) == 10) return 0; from++; }
+ return 1;
+}
+int doc_line_end(int base, int from, int size) {
+ int c;
+ while (from < size) {
+ c = dc(base, from); from++;
+ if (c == 10) return 1;
+ if (c != 32 && c != 9 && c != 13) return 0;
+ }
+ return 1;
+}
+int hover_comment_section(int i, int k) {
+ if (hc_kind[k] == 4) return 1;
+ int base = doff[i], from = hc_start[k] + 2, end = hc_end[k], a, b;
+ if (hc_kind[k] == 3) end = end - 2;
+ while (from < end) {
+ b = from; while (b < end && dc(base, b) != 10) b++;
+ a = from;
+ while (a < b && (dc(base, a) == 32 || dc(base, a) == 9 || dc(base, a) == '*')) a++;
+ while (b > a && (dc(base, b - 1) == 32 || dc(base, b - 1) == 9 || dc(base, b - 1) == 13)) b--;
+ if (b - a >= 4 && dc(base, a) == '-' && dc(base, a + 1) == '-' && dc(base, b - 1) == '-' && dc(base, b - 2) == '-') return 1;
+ while (from < end && dc(base, from) != 10) from++; from++;
+ }
+ return 0;
+}
+
+// attach leading docs and adjacent or trailing docs to this exact declaration name.
+int hover_docs(int i, int head, int name, int length, int tail) {
+ int k, first = -1, last = -1, pos = head, base = doff[i], section = 0;
hover_comments(i);
k = hc_n - 1;
while (k >= 0 && hc_end[k] > head) k--;
+ // a comment block that holds a section heading is section text, not documentation
while (k >= 0 && hc_kind[k] != 1 && doc_gap(base, hc_end[k], pos)) {
if (last < 0) last = k;
+ if (hover_comment_section(i, k)) section = 1;
first = k; pos = hc_start[k]; k--;
}
- if (first >= 0) { k = first; while (k <= last) { hover_comment_text(i, k); k++; } }
+ if (first >= 0 && !section) { k = first; while (k <= last) { hover_comment_text(i, k); k++; } }
k = 0;
while (k < hc_n) {
- if (hc_kind[k] == 3 && (k < first || k > last)) {
- if (hc_start[k] >= head && hc_end[k] <= name && doc_gap(base, hc_end[k], name)) hover_comment_text(i, k);
- else if (hc_start[k] >= name + length && doc_gap(base, name + length, hc_start[k])) hover_comment_text(i, k);
+ if ((k < first || k > last) && !hover_comment_section(i, k)) {
+ if (hc_kind[k] == 3 && hc_start[k] >= head && hc_end[k] <= name && doc_gap(base, hc_end[k], name)) hover_comment_text(i, k);
+ else if (length > 0 && hc_start[k] >= name + length && (hc_kind[k] == 1 || hc_kind[k] == 3) && doc_trailing(base, name + length, hc_start[k])) hover_comment_text(i, k);
+ // a one-line declaration owns the comment that ends its line
+ else if (length < 0 && hc_start[k] >= tail && (hc_kind[k] == 1 || hc_kind[k] == 3) && doc_same_line(base, tail, hc_start[k]) && doc_line_end(base, hc_end[k], dsize[i])) hover_comment_text(i, k);
}
k++;
}
@@ -930,33 +1159,43 @@ int hover_docs(int i, int head, int name, int length) {
// the markdown for a declaration: its signature as a code block, then the
// comment block above it, without the slashes, if there is one
int hover_fill(int i) {
- int base = doff[i], o = hdl, c, cs, ls, typed = 0, k, start, end;
+ int base = doff[i], o = hdl, c, cs, ls, typed = 0, k, run.start, end;
hn = 0;
+ if (hs_name >= 0) {
+ hov_text("```zc\n"); c = hdl;
+ while (c < hs_first) { hov_put(dc(base, c)); c++; }
+ c = hs_name; while (c < hdle) { hov_put(dc(base, c)); c++; }
+ hov_text("\n```"); hover_docs(i, hdl, hs_name, rt.len_w(hname), hdle);
+ if (bs_is(i) && hs_const) hover_blob_value();
+ return hn;
+ }
while (o < hdle && dc(base, o) != '{' && dc(base, o) != ';' && dc(base, o) != '=') o++;
while (o > hdl && (dc(base, o - 1) == 32 || dc(base, o - 1) == 9)) o--;
hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10);
c = hdl;
// an untyped fn: the return type the body gives it replaces the fn
- if (dc(base, hdl) == 'f' && dc(base, hdl + 1) == 'n' && dc(base, hdl + 2) == 32) {
+ if ((dc(base, hdl) == 'f' && dc(base, hdl + 1) == 'n' && dc(base, hdl + 2) == 32) || (dc(base, hdl) == '.' && dc(base, hdl + 1) == 'u')) {
ensure_annot(i);
- k = hdl + 3;
+ k = hdl; while (k < hdle && !hid(dc(base, k))) k++;
+ while (k < hdle && hid(dc(base, k))) k++;
while (k < hdle && (dc(base, k) == 32 || dc(base, k) == 9)) k++;
- if (ann_type(i, k)) {
- start = ann_name_at;
- while (start > 0 && dc(ann_text, start - 1) != 10 && dc(ann_text, start - 1) != ';' && dc(ann_text, start - 1) != '}') start--;
- while (start < ann_name_at && (dc(ann_text, start) == 32 || dc(ann_text, start) == 9)) start++;
+ ann_name_at = -1; ann_type(i, k);
+ if (ann_name_at >= 0) {
+ run.start = ann_name_at;
+ while (run.start > 0 && dc(ann_text, run.start - 1) != 10 && dc(ann_text, run.start - 1) != ';' && dc(ann_text, run.start - 1) != '}') run.start--;
+ while (run.start < ann_name_at && (dc(ann_text, run.start) == 32 || dc(ann_text, run.start) == 9)) run.start++;
end = ann_name_at;
while (end < ann_n && dc(ann_text, end) != '{' && dc(ann_text, end) != ';') end++;
- while (end > start && (dc(ann_text, end - 1) == 32 || dc(ann_text, end - 1) == 9 || dc(ann_text, end - 1) == 10 || dc(ann_text, end - 1) == 13)) end--;
- k = start; while (k < end) { hov_put(dc(ann_text, k)); k++; }
- hov_text("\n```"); htn = 0; hover_docs(i, hdl, hdl, 0);
+ while (end > run.start && (dc(ann_text, end - 1) == 32 || dc(ann_text, end - 1) == 9 || dc(ann_text, end - 1) == 10 || dc(ann_text, end - 1) == 13)) end--;
+ k = run.start; while (k < end) { hov_put(dc(ann_text, k)); k++; }
+ hov_text("\n```"); htn = 0; hover_docs(i, hdl, hdl, 0 - 1, o);
return hn;
}
htn = 0;
}
while (c < o) { hov_put(dc(base, c)); c++; }
hov_put(10); hov_put('`'); hov_put('`'); hov_put('`');
- hover_docs(i, hdl, hdl, 0);
+ hover_docs(i, hdl, hdl, 0 - 1, o);
return hn;
}
// the hover reply: the markdown in hov, or null when there is nothing to show.
@@ -971,28 +1210,28 @@ int gen_hover() {
// the doc of a name from the image's own symbol blob, or 0. the checks reset the
// symbol table (zc_init, chk_mark), so the docs are read from the blob itself,
// which stays in the image
-word blobdoc(word nm) {
- word p = GORG[5]; int k;
+bstr blobdoc(char* nm) {
+ word p = rt.GORG[5]; int k;
while (*p) {
- if (p[0] == K_DOC && p[3] == 'd' && p[4] == 'o' && p[5] == 'c' && p[6] == ' ') {
+ if (p[0] == run.K_DOC && p[3] == 'd' && p[4] == 'o' && p[5] == 'c' && p[6] == ' ') {
k = 0;
while (nm[k] && p[7 + k] == nm[k]) k++;
if (!nm[k] && !p[7 + k]) return p[1];
}
- p = p + 3 + len_w(p + 3) + 1;
+ p = p + 3 + rt.len_w(p + 3) + 1;
}
return 0;
}
// the signature of a name from the image's own symbol blob, or 0
-word blobsig(word nm) {
- word p = GORG[5]; int k;
+bstr blobsig(char* nm) {
+ word p = rt.GORG[5]; int k;
while (*p) {
- if (p[0] == K_DOC && p[3] == 's' && p[4] == 'i' && p[5] == 'g' && p[6] == ' ') {
+ if (p[0] == run.K_DOC && p[3] == 's' && p[4] == 'i' && p[5] == 'g' && p[6] == ' ') {
k = 0;
while (nm[k] && p[7 + k] == nm[k]) k++;
if (!nm[k] && !p[7 + k]) return p[1];
}
- p = p + 3 + len_w(p + 3) + 1;
+ p = p + 3 + rt.len_w(p + 3) + 1;
}
return 0;
}
@@ -1089,7 +1328,7 @@ int hover_local(int i) {
return 0;
}
// does the word from q to e equal w
-word hl_is(int base, int q, int e, word w) {
+word hl_is(int base, int q, int e, char* w) {
int k = 0;
while (w[k] && q + k < e && dc(base, q + k) == w[k]) k++;
return !w[k] && q + k == e;
@@ -1102,7 +1341,7 @@ word hl_kw(int base, int q) {
return hl_is(base, q, e, "return") || hl_is(base, q, e, "if") || hl_is(base, q, e, "while") || hl_is(base, q, e, "else") || hl_is(base, q, e, "break") || hl_is(base, q, e, "continue");
}
// where a declaration's name ends on the line from q: at = ; , { ( or a [ that
-// follows a name (let b[24]); the [ of a type (fixed []int) is passed over
+// follows a name (let b[24]); the [ of a type (fixed int[?]) is passed over
int hdeclend(int base, int q, int le) {
int e = q, c;
while (e < le) {
@@ -1118,11 +1357,11 @@ word abase, aerr; int nbase, aerrcap;
word afx, aft, amask; int anf, afcap, atcap;
word awp; int awn;
word ann_half;
-// the inferred types of the untyped declarations. annotate_doc runs the annotate
-// rounds on a document (in doc slot 0, which is put back afterwards), and the
-// text it ends with is kept in ann_text. ann_type then finds, for a name the
-// document declares untyped, the type annotate gave that name: the text between
-// the token before it in the annotated text and the name, after the last ( , { ; or =.
+//: the inferred types of the untyped declarations. annotate_doc runs the annotate
+//: rounds on a document (in doc slot 0, which is put back afterwards), and the
+//: text it ends with is kept in ann_text. ann_type then finds, for a name the
+//: document declares untyped, the type annotate gave that name: the text between
+//: the token before it in the annotated text and the name, after the last ( , { ; or =.
int ann_i = -1, ann_gen = -1, ai_end;
// runs the annotate rounds on document i. the rounds run in doc slot 0, and the
// slots they use are put back afterwards. the text they end with is kept in
@@ -1134,11 +1373,11 @@ int annotate_doc(int i) {
sv_base1 = doff[1]; sv_slot1 = dsize[1]; sv_live1 = dlive[1]; sv_ulen1 = dulen[1]; sv_gen1 = dgen[1];
dlive[0] = 1; dulen[0] = 0;
doff[0] = doff[i]; dsize[0] = dsize[i]; dgen[0] = sv_gen + 1;
- ifstrict = 1;
+ run.ifstrict = 1;
check_doc(0);
nbase = err_lines(abase);
while (round < AROUNDS) {
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
save_facts();
if (!anf) break;
k = 0; while (k < anf) { amask[k] = 0; k++; }
@@ -1149,7 +1388,7 @@ int annotate_doc(int i) {
doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1;
round++;
}
- ifstrict = 0; ifon = 0;
+ run.ifstrict = 0; run.ifon = 0;
ann_text = doff[0]; ann_n = dsize[0];
doff[0] = sv_base; dsize[0] = sv_size; dlive[0] = sv_live; dulen[0] = sv_ulen; dgen[0] = sv_gen;
doff[1] = sv_base1; dsize[1] = sv_slot1; dlive[1] = sv_live1; dulen[1] = sv_ulen1; dgen[1] = sv_gen1;
@@ -1269,57 +1508,53 @@ int ann_type(int i, int o) {
}
// the markdown of a local or parameter: its declaration text, then what it is
int hover_put_sig(int base, int sb, int se, int kind, int lk) {
- int k = sb;
- hn = 0;
- hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10);
- while (k < se) { hov_put(dc(base, k)); k++; }
- if (kind == 1) {
- hov_put(' '); hov_put(' '); hov_put('/'); hov_put('/'); hov_put(' '); k = 0; while ("parameter"[k]) { hov_put("parameter"[k]); k++; }
- if (htn > 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- }
- else {
- hov_put(' '); hov_put(' '); hov_put('/'); hov_put('/'); hov_put(' '); k = 0; while ("local"[k]) { hov_put("local"[k]); k++; }
- if (lk >= 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = lt_at[lk]; while (k < lt_at[lk] + lt_len[lk]) { hov_put(ltxt[k]); k++; } }
- else if (htn > 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- }
- hov_put(10); hov_put('`'); hov_put('`'); hov_put('`');
+ int k, type_end = se - rt.len_w(hname);
+ while (type_end > sb && (dc(base, type_end - 1) == 32 || dc(base, type_end - 1) == 9)) type_end--;
+ hn = 0; hov_text("```zc\n(");
+ if (lk >= 0) hov_type(run.lt_type[lk]);
+ else if (htn) { k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
+ else if (type_end > sb && !hl_is(base, sb, type_end, "let")) { k = sb; while (k < type_end) { hov_put(dc(base, k)); k++; } }
+ else hov_text("word");
+ hov_text(") let "); hov_text(hname);
+ if (kind == 1) hov_text(" // parameter;"); else hov_text("; // local");
+ hov_text("\n```");
return hn;
}
// a genome name: its signature as a code block, then its doc if the image has one
int hover_blob(int i) {
- int ss, ds, k = 0, n = 0;
+ bstr ss, ds; int k = 0, n = 0;
ss = blobsig(hname);
ds = blobdoc(hname);
hn = 0;
if (ss) {
hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10);
- while (k < len_s(ss)) { hov_put(at_s(ss, k)); k++; }
+ while (k < rt.len_s(ss)) { hov_put(rt.at_s(ss, k)); k++; }
hov_put(10); hov_put('`'); hov_put('`'); hov_put('`');
}
if (ds) {
if (ss) { hov_put(10); hov_put(10); }
k = 0;
- while (k < len_s(ds)) { hov_put(at_s(ds, k)); k++; }
+ while (k < rt.len_s(ds)) { hov_put(rt.at_s(ds, k)); k++; }
}
return hn;
}
// does the hovered word equal w
-word hname_is(word w) {
+word hname_is(char* w) {
int k = 0;
while (w[k] && hname[k] == w[k]) k++;
return !w[k] && !hname[k];
}
-// append a word string, or a compiler type, to the markdown buffer.
-int hov_text(word s) { int k = 0; while (s[k]) { hov_put(s[k]); k++; } return 0; }
+// append a char* string or a compiler type to the markdown buffer.
+int hov_text(char* s) { int k = 0; while (s[k]) { hov_put(s[k]); k++; } return 0; }
int hov_type(word t) {
- int sm = outmode, sb = outbuf, sn = outn, sc = outcap, need;
- outmode = 3; outn = 0; tprint(t); need = hn + outn;
+ int sm = rt.outmode, sb = rt.outbuf, sn = rt.outn, sc = rt.outcap, need;
+ rt.outmode = 3; rt.outn = 0; run.tprint(t); need = hn + rt.outn;
if (need > hovcap) {
while (need > hovcap) hovcap = hovcap * 2;
hov = lsp_grow(hov, hn, hovcap);
}
- outmode = 2; outbuf = hov; outn = hn; outcap = hovcap; tprint(t); hn = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ rt.outmode = 2; rt.outbuf = hov; rt.outn = hn; rt.outcap = hovcap; run.tprint(t); hn = rt.outn;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
return 0;
}
// explain composite types without relying on their source spelling.
@@ -1327,46 +1562,47 @@ int hover_type_info(word t) {
int kind, k;
if (!t) return 0;
kind = t[0];
- if (kind == TY_PTR) {
+ if (kind == run.TY_PTR) {
hov_text("\n\na pointer to `"); hov_type(t[4]); hov_text("`. the pointer occupies one word; the pointed-to storage is separate.");
- } else if (kind == TY_ARR) {
+ } else if (kind == run.TY_ARR) {
hov_text("\n\nan array stored in place, with a fixed length of ");
- k = t[3]; hov_number(k); hov_text(" elements of type `"); hov_type(t[4]); hov_text("`. storage: "); hov_number(tsz(t)); hov_text(" words.");
- } else if (kind == TY_DYN) {
+ k = t[3]; if (k < 0) hov_text("determined by its initializer; elements of type `"); else { hov_number(k); hov_text(" elements of type `"); } hov_type(t[4]); hov_text("`. storage: "); hov_number(run.tsz(t)); hov_text(" words.");
+ } else if (kind == run.TY_DYN) {
if (t[3]) hov_text("\n\nan array handle with a fixed runtime length. ");
else hov_text("\n\na growable array handle. ");
hov_text("element type: `"); hov_type(t[4]); hov_text("`. `len` gives the current length and `cap` gives capacity.");
if (t[3]) hov_text(" `push`, `pop`, and `resize` cannot change its length.");
- } else if (kind == TY_STRUCT) {
- hov_text("\n\na struct stored in place. storage: "); hov_number(tsz(t)); hov_text(" words.");
+ } else if (kind == run.TY_STRUCT) {
+ hov_text("\n\na struct stored in place. storage: "); hov_number(run.tsz(t)); hov_text(" words.");
if (t[3] > 0) {
hov_text("\n\nfields:\n\n```zc\n");
k = 0;
while (k < t[3]) { hov_type(t[4][k * 3 + 1]); hov_put(' '); hov_text(t[4][k * 3]); hov_text(";\n"); k++; }
hov_text("```");
}
- } else if (kind == TY_FN) hov_text("\n\na callable value. the type lists its result and parameter types.");
+ } else if (kind == run.TY_BSTR) hov_text("\n\na packed byte string. its header stores the byte length; data follows in little-endian packed words.");
+ else if (kind == run.TY_FN) hov_text("\n\na callable value. the type lists its result and parameter types.");
return 0;
}
// decimal numbers in markdown use the ordinary printer through the capture buffer.
int hov_number(int n) {
- int sm = outmode, sb = outbuf, sn = outn, sc = outcap;
+ int sm = rt.outmode, sb = rt.outbuf, sn = rt.outn, sc = rt.outcap;
while (hn + 24 > hovcap) { hov = lsp_grow(hov, hn, hovcap * 2); hovcap = hovcap * 2; }
- outmode = 2; outbuf = hov; outn = hn; outcap = hovcap; put_n(n); hn = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ rt.outmode = 2; rt.outbuf = hov; rt.outn = hn; rt.outcap = hovcap; rt.put_n(n); hn = rt.outn;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
return 0;
}
// a parsed type includes its brackets and stars, so those characters hover too.
int hover_type_span(int i) {
int k = 0, best = 0 - 1, width = 0, end; word t;
- while (k < ht_n) {
- end = ht_end[k];
- while (end > ht_start[k] && (dc(doff[i], end - 1) == 32 || dc(doff[i], end - 1) == 9 || dc(doff[i], end - 1) == 10 || dc(doff[i], end - 1) == 13)) end--;
- if (hcursor >= ht_start[k] && hcursor < end && end - ht_start[k] > width) { best = k; width = end - ht_start[k]; }
+ while (k < run.ht_n) {
+ end = run.ht_end[k];
+ while (end > run.ht_start[k] && (dc(doff[i], end - 1) == 32 || dc(doff[i], end - 1) == 9 || dc(doff[i], end - 1) == 10 || dc(doff[i], end - 1) == 13)) end--;
+ if (hcursor >= run.ht_start[k] && hcursor < end && end - run.ht_start[k] > width) { best = k; width = end - run.ht_start[k]; }
k++;
}
if (best < 0) return 0;
- t = ht_type[best];
+ t = run.ht_type[best];
hov_text("```zc\n"); hov_type(t); hov_text("\n```"); hover_type_info(t);
k = kwtext();
if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) {
@@ -1379,7 +1615,8 @@ int hover_type_span(int i) {
// one table holds every keyword the compiler registers and the built-ins that
// have their own form. the form comes first, then a ~, then what it does (the ~
// separates the two). a name the table does not hold gets the image's docs.
-int kwtext() {
+char* kwtext() {
+ if (hname_is("bstr")) return "bstr~packed byte string with a length header; indexing yields a byte and slicing returns a packed string";
if (hname_is("word")) return "word~one 64-bit vm word; the default type for untyped integer values and word-sized storage";
if (hname_is("int")) return "int~one signed, word-sized integer; arrays of int use one word per element";
if (hname_is("char")) return "char~one word holding a character code; arrays of char use one word per element";
@@ -1396,8 +1633,9 @@ int kwtext() {
if (hname_is("i32")) return "i32~signed 32-bit integer, range -2147483648..2147483647; values narrow on store and arrays or pointers pack two elements per word";
if (hname_is("byte")) return "byte~alias for u8, an unsigned 8-bit integer packed in arrays and pointers";
if (hname_is("fixed")) return "fixed~marks an array whose length cannot change; fixed arrays cannot grow or shrink";
- if (hname_is("new")) return "new(T) or new(T, n)~returns a fresh object on the heap. bound to a local, the local owns it (tagged m) and the block frees it when it ends, so you don't write free yourself.";
- if (hname_is("free")) return "free(d*p)~frees a heap object. the block is kept until the next collection finds nothing pointing at it, and a second free does nothing. free takes an object tagged d, so a checked call is free(d*p).";
+ if (hname_is("new")) return "new(T) or new(T, n)~returns a fresh heap object. a local owns it and the compiler releases it when its block ends; pass it to a .d parameter or call free(p) to transfer it explicitly.";
+ if (hname_is("free")) return "free(p)~releases a heap object. a checked call consumes an owned object automatically; the block is kept until collection can safely reuse it, and a repeated free does nothing.";
+ if (hname_is("len_s")) return "len_s(s)~the number of bytes in a packed byte string. unlike len_w, it reads the stored byte length and does not scan for a zero terminator.";
if (hname_is("sizeof")) return "sizeof(e) or sizeof(T)~the size in words, found by compiling the expression dry, so it is not run. a packed array counts (n * bytes + 7) / 8 words.";
if (hname_is("typeof")) return "typeof(e) or typeof(T)~the type record of an expression or a type, found by compiling it dry. tkind, tsize, tname and the other t functions read it at runtime.";
if (hname_is("syscall")) return "syscall(op, a, b, c)~a call to the host through the gate word. the ops are SYS_OPEN, SYS_CLOSE, SYS_READ, SYS_WRITE, SYS_GETENV, SYS_TIME and the rest of the named ones, and SYS_RAW (40) runs any linux syscall on the host. o3 code does not go through the gate, so call syscall from ordinary code.";
@@ -1408,8 +1646,9 @@ int kwtext() {
if (hname_is("resize")) return "resize(a, n)~sets the length of a growable array to n, growing or shrinking it.";
if (hname_is("o3")) return "o3~marks code for the optimizing tier. it goes on a function, a block or a single variable, and its variables live in registers.";
if (hname_is("fn")) return "fn name(a, b) { }~declares a function whose return is a word. a typed function is written like c, int f(int x) { }.";
+ if (hname_is("static")) return "static fn name(args) { }~declares a function inside a struct without a hidden this parameter. call it as Struct.name(args).";
if (hname_is("let")) return "let name = e~declares a local whose type comes from its value. a number stays a plain word, and a pointer or a struct keeps its type.";
- if (hname_is("const")) return "const [type] NAME = e~a named constant. its value is worked out while compiling, so it can size an array or label a case.";
+ if (hname_is("const")) return "const [type] NAME = e / const fixed T[N] name~declares a compile-time constant or read-only storage. fixed and const may appear in either order; read-only storage cannot be modified or converted to a mutable reference.";
if (hname_is("else")) return "else~the other branch of an if. after an enhance's body, else names the fallback function that runs where the enhancement can't.";
if (hname_is("as")) return "e as T~changes the type of an expression without changing its bits.";
if (hname_is("unchecked")) return "unchecked fn f() { } / unchecked { } / unchecked;~turns the lifetime analyzer off for a function, a block, or the rest of the unit. the code is not checked, so the analyzer's rules don't apply to it.";
@@ -1423,13 +1662,14 @@ int kwtext() {
if (hname_is("switch")) return "switch (e) { case 1, 2: ... default: ... }~a branch on constant labels. the cases don't fall through, and break leaves early.";
if (hname_is("break")) return "break;~leaves the innermost loop or switch.";
if (hname_is("continue")) return "continue;~goes on to the next pass of the innermost loop.";
- if (hname_is("return")) return "return e;~leaves the function with a value. a u object leaves with u*x, and the function's return is then owned.";
+ if (hname_is("return")) return "return e; or return;~leaves the function with a value, or leaves a void function. returning an owned local transfers it to the caller automatically.";
if (hname_is("enum")) return "enum name { A, B = 5 };~global constants. under typecheck on they are a type of their own, and a member can carry fields to make a constructor.";
if (hname_is("struct")) return "struct name { fields };~a record type. struct name<T> { } makes it generic, one copy for each set of type arguments.";
- if (hname_is("type")) return "type name = T;~an alias for a type, such as type row = [3]int; for a pointer to an array.";
- if (hname_is("include")) return "include \"path\";~reads a file and compiles it at that point, its signatures first.";
+ if (hname_is("type")) return "type name = T;~an alias for a type, such as type row = int[3]; for a pointer to an array.";
+ if (hname_is("include")) return "include \"path\"; / include std;~compiles a file or the embedded standard library at that point, its signatures first.";
if (hname_is("memdebug")) return "memdebug on; / memdebug off; / memdebug scrub;~header checks, allocation sites and leak reports for the rest of the file. scrub also fills a freed payload with 0xDEAD.";
if (hname_is("typecheck")) return "typecheck on; / typecheck off;~turns on type checks for the code that follows, enums included.";
+ if (hname_is("hints")) return "hints on; / hints small; / hints off;~controls subsequent hint diagnostics: informational ranges by default, compact markers with small, or no hints with off.";
if (hname_is("docs")) return "docs on; / docs off;~keeps the doc comments above declarations in the image, so the language server can show them on hover.";
if (hname_is("micro")) return "enhance name(args) micro \"recipe\";~the body of an enhancement, written as micro-ops on registers r0 to r15. the inputs arrive in r0 and up, and the outputs leave from r0.";
if (hname_is("vm")) return "enhance name(args) vm { ... };~the body of an enhancement, written as stack machine asm. it becomes one opcode when the core lifts it.";
@@ -1441,7 +1681,7 @@ int kwtext() {
return 0;
}
// the markdown of a keyword: its form as a code block, then what it does
-int kw_put(word s) {
+int kw_put(char* s) {
int k = 0;
hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10);
while (s[k] && s[k] != '~') { hov_put(s[k]); k++; }
@@ -1498,41 +1738,42 @@ int hover_method(int i) {
k = sg;
while (k < se) { hov_put(dc(base, k)); k++; }
hov_put(10); hov_put('`'); hov_put('`'); hov_put('`');
- hover_docs(i, sg, sg, 0);
+ hover_docs(i, sg, sg, 0, 0);
return 1;
}
// the markdown of a local at its declaration: the name's declaration line, with
// the type an untyped let was given. o is the byte offset of the declared name.
int hover_named(int i, int o, int kind) {
- int base = doff[i], end = o, head = o, k, entry, untyped; word t = 0;
+ int base = doff[i], end = o, head = o, k, entry, untyped, r, st = 0; word t = 0;
while (end < dsize[i] && hid(dc(base, end))) end++;
if (end == o) return 0;
untyped = ann_untyped(i, o); htn = 0;
if (untyped) { ensure_annot(i); ann_type(i, o); ensure_checked(i); }
entry = lt_find(o);
- if (entry >= 0) t = lt_type[entry];
+ if (entry >= 0) t = run.lt_type[entry];
+ if (t && (t[0] == run.TY_ARR || t[0] == run.TY_STRUCT)) htn = 0;
k = 0;
- while (k < ht_n) { if (ht_end[k] == o && ht_start[k] < head) head = ht_start[k]; k++; }
+ while (k < run.ht_n) { if (run.ht_end[k] == o && run.ht_start[k] < head) head = run.ht_start[k]; k++; }
if (untyped && kind != 1) {
k = o - 1;
while (k >= 0 && (dc(base, k) == 32 || dc(base, k) == 9)) k--;
if (k >= 2 && dc(base, k) == 't' && dc(base, k - 1) == 'e' && dc(base, k - 2) == 'l') head = k - 2;
}
hn = 0; hov_text("```zc\n");
- if (untyped && kind != 1) hov_text("let ");
- else if (!untyped && t) { hov_type(t); hov_put(' '); }
- k = o; while (k < end) { hov_put(dc(base, k)); k++; }
- if (kind == 1) hov_text(" // parameter");
- else if (kind == 3) hov_text(" // global");
- else if (kind == 4) hov_text(" // field");
- else hov_text(" // local");
- if (htn || entry >= 0) {
- hov_text("; type ");
+ hov_put('(');
if (htn) { k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- else { k = lt_at[entry]; while (k < lt_at[entry] + lt_len[entry]) { hov_put(ltxt[k]); k++; } }
- }
- hov_text("\n```"); hover_type_info(t); hover_docs(i, head, o, end - o);
- if (hoff != o) hover_docs(i, hoff, hoff, len_w(hname));
+ else if (t) hov_type(t); else hov_text("word");
+ hov_text(") let");
+ r = rs_find(hoff); if (kind == 2 && r >= 0) st = (run.rsrec[r * run.RS_W + 11] & 15) - 1;
+ if (st == run.S_M || st == run.S_BORROW || st == run.S_CHILD) hov_text(".m");
+ else if (st == run.S_U) hov_text(".u"); else if (st == run.S_D) hov_text(".d"); else if (st == run.S_DEAD) hov_text(".expired");
+ hov_put(' '); k = o; while (k < end) { hov_put(dc(base, k)); k++; }
+ if (kind == 1) hov_text(" // parameter;");
+ else if (kind == 3) hov_text(" // global;");
+ else if (kind == 4) hov_text(" // field;");
+ else hov_text("; // local");
+ hov_text("\n```"); hover_type_info(t); hover_docs(i, head, o, end - o, 0);
+ if (hoff != o) hover_docs(i, hoff, hoff, rt.len_w(hname), 0);
htn = 0;
return 1;
}
@@ -1542,14 +1783,14 @@ int hover_at_decl(int i, int o) { return hover_named(i, o, 2); }
int hover_storage(int r) {
word p, t = 0; int entry, kind;
if (r < 0 || !hn) return 0;
- p = rsrec + r * RS_W; kind = p[6];
+ p = run.rsrec + r * run.RS_W; kind = p[6];
if (!kind) {
- if (p[1] == RS_LOCAL || p[1] == RS_PARAM || p[1] == RS_FIELD) {
- entry = lt_find(p[2]); if (entry >= 0) t = lt_type[entry];
- } else if (p[1] == RS_SYM && p[3] >= 0) t = stype[p[3]];
- if (!t || (t[0] != TY_PTR && t[0] != TY_ARR && t[0] != TY_DYN)) return 0;
+ if (p[1] == run.RS_LOCAL || p[1] == run.RS_PARAM || p[1] == run.RS_FIELD) {
+ entry = lt_find(p[2]); if (entry >= 0) t = run.lt_type[entry];
+ } else if (p[1] == run.RS_SYM && p[3] >= 0) t = run.stype[p[3]];
+ if (!t || (t[0] != run.TY_PTR && t[0] != run.TY_ARR && t[0] != run.TY_DYN)) return 0;
hov_text("\n\nsize: ");
- if (t[0] == TY_ARR) { hov_number(t[3]); hov_text(" elements"); }
+ if (t[0] == run.TY_ARR) { hov_number(t[3]); hov_text(" elements"); }
else hov_text("unknown");
hov_text(". lifetime: unknown at this position."); return 0;
}
@@ -1565,17 +1806,95 @@ int hover_storage(int r) {
if (p[7]) { hov_text(". storage begins on line "); hov_number(p[7] & 0xFFFFFF); }
hov_text("."); return 0;
}
+// the line (from 1) of a byte offset in document i
+int off_line(int i, int off) {
+ int base = doff[i], o = 0, ln = 1;
+ while (o < off && o < dsize[i]) { if (dc(base, o) == 10) ln++; o++; }
+ return ln;
+}
+// the name written at a byte offset, in backquotes
+int hover_ident(int i, int off) {
+ int base = doff[i];
+ hov_put('`');
+ while (off < dsize[i] && hid(dc(base, off))) { hov_put(dc(base, off)); off++; }
+ hov_put('`'); return 0;
+}
+// what the analyzer knows of a local's ownership at this reference, and where its object
+// goes: up through a return, block value or assignment to an enclosing block, down to a
+// d parameter or a container, to the collector through a global, or freed when its block ends.
+int hover_ownership(int i, int r, int decl) {
+ word p; int st, k, n = 0, kind;
+ if (r < 0 || !hn) return 0;
+ p = run.rsrec + r * run.RS_W;
+ st = p[11] & 15;
+ if (st > 1 && st - 1 == run.S_CHILD && p[12]) {
+ hov_text("\n\nborrows from "); hover_ident(i, p[12] - 1);
+ hov_text(", declared on line "); hov_number(off_line(i, p[12] - 1));
+ hov_text("; valid while that owner holds the object.");
+ }
+ k = 0;
+ while (k < run.evn) {
+ if (run.evrec[k * run.EV_W] == decl) {
+ kind = run.evrec[k * run.EV_W + 1];
+ if (!n) hov_text("\n\n");
+ else hov_text(" ");
+ n++;
+ if (kind == run.EV_RETURN) hov_text("passed up: returned");
+ else if (kind == run.EV_VALUE) hov_text("passed up: the value of its block");
+ else if (kind == run.EV_UP) hov_text("passed up: assigned to a variable of the enclosing block");
+ else if (kind == run.EV_DOWN) hov_text("passed down: given to a d parameter");
+ else if (kind == run.EV_STORE) hov_text("passed down: stored in a container");
+ else if (kind == run.EV_GLOBAL) hov_text("handed to the collector through a global");
+ else if (kind == run.EV_MOVE) hov_text("moved: replaces a released owner in");
+ else if (kind == run.EV_CAPTURE) hov_text("captured: shares storage with a closure");
+ else hov_text("freed where its block ends");
+ if (run.evrec[k * run.EV_W + 3]) { hov_text(" "); hover_ident(i, run.evrec[k * run.EV_W + 3] - 1); }
+ hov_text(" on line "); hov_number(run.evrec[k * run.EV_W + 2]); hov_text(".");
+ }
+ k++;
+ }
+ return 0;
+}
+// values are saved per reference, so later stores cannot rewrite earlier tips.
+int hover_value(int r) {
+word p; char* text; int kind, n, k = 0, c;
+ if (r < 0 || !hn) return 0;
+ p = run.rsrec + r * run.RS_W; kind = p[9];
+ if (!kind) return 0;
+ hov_text("\n\nvalue at this position:\n```zc\n");
+ if (kind == 1) hov_number(p[10]);
+ else if (kind == 2) {
+char[64] buf; n = rt.syscall(41, p[10], buf, 64);
+ if (n >= 0) hov_text(buf); else hov_text("unknown");
+ } else {
+ text = p[10]; if (kind == 4) hov_put('b'); hov_put('"');
+ while (text && text[k]) {
+ c = text[k]; k++;
+ if (c == 10) hov_text("\\n"); else if (c == 13) hov_text("\\r"); else if (c == 9) hov_text("\\t");
+ else { if (c == 34 || c == 92) hov_put(92); hov_put(c); }
+ }
+ hov_put('"');
+ }
+ hov_text("\n```"); return 0;
+}
+
// the hover of a name that is not a local. kind is 1 for a built-in (the guide's
// text, then the image's docs), 2 for a symbol the document declares (its
// declaration, a method, then the image's docs), and 0 when no record says (the
// guide, then the text of the document, as before)
int hover_global(int i, int kind) {
- int kt;
+ int kt, r, s;
+ r = rs_find(hoff);
+ if (r >= 0 && run.rsrec[r * run.RS_W + 1] == run.RS_SYM && run.rsrec[r * run.RS_W + 3] >= 0 && kind != 2) {
+ s = run.rsrec[r * run.RS_W + 3];
+ if (run.skind[s] != run.K_KW && bs_find(run.sname[s])) { hover_fill(bs_load(bs_k)); return hn; }
+ }
if (kind != 2) { kt = kwtext(); if (kt) return kw_put(kt); }
- if (kind == 1) { hover_blob(i); return hn; }
+ if (kind == 1) { if (bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; } hover_blob(i); return hn; }
if (hover_scan(i)) { hover_fill(i); return hn; }
if (hover_method(i)) return hn;
if (kind == 0 && hover_local(i)) return hn;
+ if (kind != 2 && bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; }
hover_blob(i);
return hn;
}
@@ -1588,72 +1907,1441 @@ int do_hover(int i) {
hn = 0;
ensure_checked(i);
has_word = hover_word(i);
+ if (has_word && hover_module(i)) { send(gen_hover); return 0; }
+ if (hover_instruction(i)) { send(gen_hover); return 0; }
if (hover_type_span(i)) { send(gen_hover); return 0; }
if (has_word) {
r = rs_find(hoff);
- if (r >= 0) { k = rsrec[r * RS_W + 1]; v = rsrec[r * RS_W + 2]; }
- if (k == RS_LOCAL && v >= 0) hover_at_decl(i, v);
- else if (k == RS_PARAM && v >= 0) hover_named(i, v, 1);
- else if (k == RS_FIELD && v >= 0) hover_named(i, v, 4);
- else if (k == RS_LOCAL) hover_local(i);
- else if (k == RS_SYM) {
- if ((v & 15) == K_GVAR || (v & 15) == K_GARR) {
- s = rsrec[r * RS_W + 3]; q = 0; o = 0 - 1;
- while (q < rsn) { if (rsrec[q * RS_W + 3] == s && lt_find(rsrec[q * RS_W]) >= 0) { o = rsrec[q * RS_W]; break; } q++; }
+ if (r >= 0) { k = run.rsrec[r * run.RS_W + 1]; v = run.rsrec[r * run.RS_W + 2]; }
+ if (k == run.RS_LOCAL && v >= 0) hover_at_decl(i, v);
+ else if (k == run.RS_PARAM && v >= 0) hover_named(i, v, 1);
+ else if (k == run.RS_FIELD && v >= 0) hover_named(i, v, 4);
+ else if (k == run.RS_LOCAL) hover_local(i);
+ else if (k == run.RS_SYM) {
+ if ((v & 15) == run.K_GVAR || (v & 15) == run.K_GARR) {
+ s = run.rsrec[r * run.RS_W + 3]; q = 0; o = 0 - 1;
+ while (q < run.rsn) { if (run.rsrec[q * run.RS_W + 3] == s && lt_find(run.rsrec[q * run.RS_W]) >= 0) { o = run.rsrec[q * run.RS_W]; break; } q++; }
if (o >= 0) hover_named(i, o, 3); else if (v & 16) hover_global(i, 2); else hover_global(i, 1);
} else if (v & 16) hover_global(i, 2); else hover_global(i, 1);
}
else hover_global(i, 0);
- r = rs_find(hoff); hover_storage(r);
+ r = rs_find(hoff); hover_storage(r); hover_value(r);
+ if (r >= 0 && hn && (run.rsrec[r * run.RS_W + 1] == run.RS_LOCAL || run.rsrec[r * run.RS_W + 1] == run.RS_PARAM) && run.rsrec[r * run.RS_W + 2] >= 0) hover_ownership(i, r, run.rsrec[r * run.RS_W + 2]);
}
send(gen_hover);
return 0;
}
-// ---- dispatch ----
-// the text just read belongs to the document named by the uri
+
+//: ---- definition and references ----
+//: the compiler's records say what the name under the cursor is: a local, a
+//: parameter or a field names its declaration by offset, a symbol names itself
+//: and the compiler kept where it was defined (sdoff, and sdsrc for a name from
+//: an include). definitions and references come from those, not from text.
+int bs_doc_now;
+word bs_idx;
+int nav_i, nav_decl, nav_sym, nav_kind, nav_refs, nav_bdoc; char* nav_file_buf; int nav_file_n;
+char* navp; int navpn, navcap = 256;
+int nav_put(int c) {
+ if (navpn + 2 >= navcap) { navp = lsp_grow(navp, navpn, navcap * 2); navcap = navcap * 2; }
+ navp[navpn] = c; navpn++; navp[navpn] = 0; return 0;
+}
+// shorten a path in place to its parent directory; 0 means there is no parent.
+int nav_parent(char*.m path) {
+ int n = rt.len_w(path);
+ if (n == 1 && path[0] == '/') return 1;
+ while (n > 0 && path[n - 1] != '/') n--;
+ if (n > 1) n--;
+ path[n] = 0;
+ return n;
+}
+// resolve navigation with the same paths and client-provided files as the compiler.
+// copy the text because navigation releases its buffer, while a virtual file stays cached.
+char* nav_find(int i, char* path) {
+ char* buf, copy, dir, other; int k = 0, n;
+ inc_setup(i); buf = run.inc_open(path); if (!buf) return 0;
+ // a path read as written can be relative. find its absolute spelling through the
+ // document's ancestors, checking the contents so a different file is never selected.
+ dir = doc_dir(i);
+ if (run.incres[0] != '/' && run.incres[1] != ':') {
+ while (dir) {
+ char* combined = run.inc_cat(dir, run.incres); char* candidate = run.inc_norm(combined); other = m*run.inc_try(candidate);
+ if (other && rt.eq_w(other, buf)) { run.incres = candidate; break; }
+ if (rt.eq_w(dir, "/")) break;
+ if (!nav_parent(dir)) break;
+ }
+ }
+ navpn = 0;
+ if (!navp) navp = lsp_grow(0, 0, navcap);
+ while (run.incres[k]) { nav_put(run.incres[k]); k++; }
+ n = rt.len_w(buf); copy = lsp_grow(0, 0, n + 1); rt.copy_w(copy, buf, n);
+ return copy;
+}
+// {"line", "character"} of byte offset off in a file read into buf
+int nav_pos_file(char* buf, int off) {
+ int o = 0, run.line = 0, col = 0, b;
+ while (o < off && buf[o]) {
+ b = buf[o];
+ if (b == 10) { run.line++; col = 0; }
+ else if (b < 128 || b >= 192) { if (b >= 240) col = col + 2; else col++; }
+ o++;
+ }
+ ws("{\"line\":"); wn(run.line); ws(",\"character\":"); wn(col); wc('}');
+ return 0;
+}
+// a location in the document: the name written at offset off
+int nav_loc_doc(int i, int off) {
+ int e = off, base = doff[i], r, k = 0, dot = -1; char* name;
+ if (i == chk_i) {
+ r = rs_find(off);
+ if (r >= 0 && run.rsrec[r * run.RS_W + 1] == run.RS_SYM && run.rsrec[r * run.RS_W + 3] >= 0) {
+ name = run.sname[run.rsrec[r * run.RS_W + 3]];
+ while (name[k] && off + k < dsize[i] && dc(base, off + k) == name[k]) { if (name[k] == '.') dot = k; k++; }
+ if (!name[k] && dot >= 0) off += dot + 1;
+ }
+ }
+ e = off;
+ while (e < dsize[i] && hid(dc(base, e))) e++;
+ if (e == off) e = off + 1;
+ pc_reset(i);
+ ws("{\"uri\":"); wjs(duri[i], dulen[i]); ws(",\"range\":{\"start\":"); wpos(off); ws(",\"end\":"); wpos(e); ws("}}");
+ return 0;
+}
+// a location in the include file nav_file_buf, whose path is in navp
+int nav_loc_file(int off) {
+ int e = off, k;
+ while (nav_file_buf[e] && hid(nav_file_buf[e])) e++;
+ if (e == off) e = off + 1;
+ ws("{\"uri\":\"file://");
+ k = 0;
+ while (k < navpn) { if (navp[k] == 32) ws("%20"); else wesc(navp[k]); k++; }
+ ws("\",\"range\":{\"start\":"); nav_pos_file(nav_file_buf, off); ws(",\"end\":"); nav_pos_file(nav_file_buf, e); ws("}}");
+ return 0;
+}
+// does record r name what the cursor is on
+int nav_match(int r) {
+ word p = run.rsrec + r * run.RS_W;
+ if (nav_sym >= 0) return p[1] == run.RS_SYM && p[3] == nav_sym;
+ return (p[1] == run.RS_LOCAL || p[1] == run.RS_PARAM || p[1] == run.RS_FIELD) && p[2] == nav_decl;
+}
+// the reply: the definition, or with references every use of the name in the document
+int gen_nav() {
+ int r = 0, first = 1, declared = 0;
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":[");
+ if (nav_kind) {
+ while (r < run.rsn) {
+ if (nav_match(r)) {
+ if (run.rsrec[r * run.RS_W] == nav_decl) { if (!rdecl) { r++; continue; } declared = 1; }
+ if (!first) wc(',');
+ first = 0; nav_loc_doc(nav_i, run.rsrec[r * run.RS_W]);
+ }
+ r++;
+ }
+ }
+ if ((!nav_kind || (rdecl && !declared)) && (nav_decl >= 0 || nav_refs == 1)) {
+ if (!first) wc(',');
+ first = 0;
+ if (nav_refs == 1) nav_loc_file(nav_decl);
+ else if (nav_refs == 2) nav_loc_doc(nav_bdoc, nav_decl);
+ else nav_loc_doc(nav_i, nav_decl);
+ }
+ ws("]}");
+ return 0;
+}
+// textDocument/definition (refs = 0) and textDocument/references (refs = 1)
+int do_nav(int i, int refs) {
+ int r, k, v, s;
+ ensure_checked(i);
+ nav_i = i; nav_decl = 0 - 1; nav_sym = 0 - 1; nav_kind = refs; nav_refs = 0; nav_file_buf = 0;
+ if (!hover_word(i)) { send(gen_null); return 0; }
+ r = rs_find(hoff);
+ if (r < 0) {
+ // the compiler has no record of the name (a built-in source is not checked): look for its declaration as text
+ if (hover_scan(i)) { nav_decl = decl_off(doff[i], hdl, hdle); send(gen_nav); return 0; }
+ if (!refs && bs_find(hname)) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); send(gen_nav); return 0; }
+ send(gen_null); return 0;
+ }
+ k = run.rsrec[r * run.RS_W + 1]; v = run.rsrec[r * run.RS_W + 2]; s = run.rsrec[r * run.RS_W + 3];
+ if (k == run.RS_SYM && s >= 0) {
+ nav_sym = s;
+ if (run.sdoff && run.sdoff[s]) {
+ nav_decl = run.sdoff[s] - 1;
+ if (run.sdsrc[s] && bs_name(run.sdsrc[s])) {
+ k = bs_source(run.sdsrc[s]);
+ if (k >= 0) { nav_refs = 2; nav_bdoc = bs_load(k); }
+ else if (bs_find(run.sname[s])) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); }
+ else nav_decl = 0 - 1;
+ }
+ else if (run.sdsrc[s]) {
+ nav_file_buf = nav_find(i, run.sdsrc[s]);
+ if (nav_file_buf) nav_refs = 1; else nav_decl = 0 - 1;
+ }
+ } else if (bs_find(run.sname[s])) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); }
+ } else if ((k == run.RS_LOCAL || k == run.RS_PARAM || k == run.RS_FIELD) && v >= 0) nav_decl = v;
+ else { send(gen_null); return 0; }
+ send(gen_nav);
+ if (nav_file_buf) rt.free(nav_file_buf);
+ return 0;
+}
+
+//: ---- the sources of this image ----
+//: the image carries the text of rt.zc, compiler.zc, server.zc, std.zc, icon.zc and core.zc (the
+//: pieces src/tools/embed.sh made), so the definition of a built-in has somewhere to go. each source is a document
+//: of its own at tinyvm:/NAME.zc, which the editor shows read only and can ask for with zc/source
+const int BS_COUNT = 6;
+bstr rt_part(int i); bstr compiler_part(int i); bstr server_part(int i); bstr core_part(int i);
+bstr bs_part(int k, int i) {
+ if (k == 0) return rt_part(i);
+ if (k == 1) return compiler_part(i);
+ if (k == 2) return server_part(i);
+ if (k == 3) return run.std_part(i);
+ if (k == 4) return run.icon_part(i);
+ return core_part(i);
+}
+char* bs_uri(int k) {
+ if (k == 0) return "tinyvm:/rt.zc";
+ if (k == 1) return "tinyvm:/compiler.zc";
+ if (k == 2) return "tinyvm:/server.zc";
+ if (k == 3) return "tinyvm:/std.zc";
+ if (k == 4) return "tinyvm:/icon.zc";
+ return "tinyvm:/core.zc";
+}
+// the document of built-in source k, loaded the first time it is asked for
+int bs_load(int k) {
+ int i, n = 0, j = 0, b; bstr p; word text;
+ if (!bs_idx) bs_idx = lsp_grow(0, 0, BS_COUNT);
+ if (bs_idx[k]) return bs_idx[k] - 1;
+ while (1) { p = bs_part(k, j); if (!p) break; n = n + rt.len_s(p); j++; }
+ text = lsp_grow(0, 0, (n >> 2) + 3);
+ n = 0; j = 0;
+ while (1) {
+ p = bs_part(k, j); if (!p) break;
+ b = 0;
+ while (b < rt.len_s(p)) {
+ if ((n & 3) == 0) text[n >> 2] = 0;
+ text[n >> 2] = text[n >> 2] | (rt.at_s(p, b) << ((n & 3) * 8));
+ n++; b++;
+ }
+ j++;
+ }
+ i = docnew(bs_uri(k), rt.len_w(bs_uri(k)));
+ doff[i] = text; dsize[i] = n; dver[i] = 1; dgen[i]++;
+ bs_idx[k] = i + 1;
+ return i;
+}
+// is document i one of those sources, or anything else with a tinyvm: address
+int bs_is(int i) {
+ return dulen[i] > 7 && bs_name(duri[i]);
+}
+// is the include name a path like tinyvm:std.zc
+int bs_name(char* path) {
+ return path[0] == 't' && path[1] == 'i' && path[2] == 'n' && path[3] == 'y' && path[4] == 'v' && path[5] == 'm' && path[6] == ':';
+}
+// the offset of the name a declaration line declares: the word before its first delimiter
+int decl_off(int base, int ls, int le) {
+ if (hs_name >= 0) return hs_name;
+ int dl = ls, e, b;
+ while (dl < le && !hdelim(dc(base, dl))) dl++;
+ e = dl;
+ while (e > ls && (dc(base, e - 1) == 32 || dc(base, e - 1) == 9)) e--;
+ b = e;
+ while (b > ls && hid(dc(base, b - 1))) b--;
+ return b;
+}
+// hname becomes the string s
+int set_hname(char* s) {
+ int n = rt.len_w(s), k = 0;
+ if (n + 1 > hnamecap) { hnamecap = n + 1; hname = lsp_grow(0, 0, hnamecap); }
+ while (k < n) { hname[k] = s[k]; k++; }
+ hname[k] = 0;
+ return 0;
+}
+// the offset of method m of struct st in document d (name is Struct.method, the dot at dot), or -1
+int bs_method(int dd, char* name, int dot) {
+ int base = doff[dd], size = dsize[dd], ls = 0, le, inside = 0, q, k, ns, e, po, ok;
+ while (ls < size) {
+ le = ls;
+ while (le < size && dc(base, le) != 10) le++;
+ if (!inside) {
+ if (dc(base, ls) == 's' && dc(base, ls + 1) == 't' && dc(base, ls + 2) == 'r' && dc(base, ls + 3) == 'u' && dc(base, ls + 4) == 'c' && dc(base, ls + 5) == 't' && dc(base, ls + 6) == 32) {
+ k = 0;
+ while (k < dot && ls + 7 + k < le && dc(base, ls + 7 + k) == name[k]) k++;
+ if (k == dot && !hid(dc(base, ls + 7 + k))) inside = 1;
+ }
+ } else {
+ if (dc(base, ls) == '}') return 0 - 1;
+ q = ls;
+ while (q < le && (dc(base, q) == 32 || dc(base, q) == 9)) q++;
+ if (q > ls) {
+ po = q;
+ while (po < le && dc(base, po) != '(') po++;
+ if (po < le) {
+ e = po; ns = e;
+ while (ns > q && hid(dc(base, ns - 1))) ns--;
+ k = 0; ok = ns < e;
+ while (ok && ns + k < e) { if (dc(base, ns + k) != name[dot + 1 + k]) ok = 0; k++; }
+ if (ok && !name[dot + 1 + k] && ns > q) return ns;
+ }
+ }
+ }
+ ls = le + 1;
+ }
+ return 0 - 1;
+}
+// locate a declaration inside one source, after removing its module prefix.
+int bs_find_in(int k, char* name) {
+ int n = 0, dot = -1, o, dd = bs_load(k);
+ while (name[n]) { if (name[n] == '.' && dot < 0) dot = n; n++; }
+ if (dot > 0 && name[dot + 1]) {
+ o = bs_method(dd, name, dot);
+ if (o >= 0) { bs_k = k; bs_off = o; return 1; }
+ } else if (dot < 0) {
+ set_hname(name);
+ if (hover_scan(dd)) { bs_k = k; bs_off = decl_off(doff[dd], hdl, hdle); return 1; }
+ }
+ return 0;
+}
+char* bs_module_name(int k) {
+ if (k == 0) return "rt";
+ if (k == 1) return "run";
+ if (k == 2) return "lsp";
+ if (k == 3) return "std";
+ if (k == 4) return "icon";
+ return "core";
+}
+// qualified symbols stay in their own module, even when another module exports
+// the same name. unqualified source-only lookups retain the usual search order.
+int bs_find(char* name) {
+ int k = 0, n, step; char* owner;
+ while (k < BS_COUNT) {
+ owner = bs_module_name(k); n = 0;
+ while (owner[n] && owner[n] == name[n]) n++;
+ if (!owner[n] && name[n] == '.') return bs_find_in(k, name + n + 1);
+ k++;
+ }
+ step = 0;
+ while (step < BS_COUNT) {
+ k = step;
+ if (step == 2) k = 3;
+ if (step == 3) k = 2;
+ if (bs_find_in(k, name)) return 1;
+ step++;
+ }
+ return 0;
+}
+// the embedded document for a built-in source path, or -1 for another path.
+int bs_source(char* path) {
+ if (rt.eq_w(path, "tinyvm:std.zc")) return 3;
+ if (rt.eq_w(path, "tinyvm:icon.zc")) return 4;
+ return 0 - 1;
+}
+// zc/source: the text of a built-in source, for the editor to show
+int gen_source() {
+ int k = 0, dd = bs_doc_now;
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid();
+ if (dd < 0) { ws(",\"result\":null}"); return 0; }
+ ws(",\"result\":\"");
+ while (k < dsize[dd]) { wesc(dc(doff[dd], k)); k++; }
+ ws("\"}");
+ return 0;
+}
+// which source a name like /rt.zc or compiler.zc is, as a document, or -1
+int bs_named(char* p, int n) {
+ int k = 0, j, ok; char* nm;
+ if (n && p[0] == '/') { p = p + 1; n--; }
+ while (k < BS_COUNT) {
+ nm = bs_uri(k) + 8;
+ j = 0; ok = 1;
+ while (nm[j]) { if (j >= n || p[j] != nm[j]) ok = 0; j++; }
+ if (ok && j == n) return bs_load(k);
+ k++;
+ }
+ return 0 - 1;
+}
+
+//: ---- formatting ----
+//: a pretty-printer that keeps the line breaks the author chose and sets everything else: the indent of
+//: each line from the braces, parentheses and open statements above it, the spaces between the tokens of a
+//: line, the blank lines between lines and the end of the file. it moves no token to another line, so
+//: formatting never changes what a program means. the options are the keys of a .zcformat file found from
+//: the document's directory upward (one "Key: value" per line), then the editor's tab settings, then the
+//: defaults:
+//: UseTab: true|false indent with tabs (default true)
+//: IndentWidth: n the spaces of a level when UseTab is false (default 4)
+//: MaxEmptyLines: n blank lines kept in a row (default 1)
+//: ContinuationIndent: n levels added to the lines of an unfinished statement or parenthesis (default 1)
+//: NormalizeSpacing: bool spaces after commas and around = == && ... (default true)
+//: CaseIndent: n levels a case label is set in from its switch (default 1)
+//: FinalNewline: bool end the file with one line break (default true)
+//: strings, comments, asm and vm blocks and what follows a backtick keep their text.
+int fm_tab = 1, fm_width = 4, fm_blank = 1, fm_cont = 1, fm_norm = 1, fm_casein = 1, fm_final = 1;
+char* fm_out; int fm_n, fm_cap;
+word fm_s, fm_e, fm_k, fm_o, fm_g, fm_l, fm_el, fm_sw; int fm_nt, fm_tcap, fm_swcap;
+// one character of the output
+int fm_put(int c) {
+ if (fm_n + 2 >= fm_cap) { fm_out = lsp_grow(fm_out, fm_n, fm_cap * 2); fm_cap = fm_cap * 2; }
+ fm_out[fm_n] = c; fm_n++;
+ return 0;
+}
+// start a new line: the spaces at the end of the one before are dropped
+int fm_newline() {
+ while (fm_n > 0 && (fm_out[fm_n - 1] == 32 || fm_out[fm_n - 1] == 9)) fm_n--;
+ fm_put(10);
+ return 0;
+}
+// token kinds (fm_k): 1 word or number, 2 string, 3 character, 4 line comment, 5 block comment, 6 symbol, 7 backtick.
+// symbol codes (fm_o): 1 comma, 2 semicolon, 3 ( or [, 4 ) or ], 5 {, 6 }, 7 an operator with a space each side, 8 colon, 9 ?
+int fm_add(int s, int e, int k, int o, int g, int l, int el) {
+ int cap;
+ if (fm_nt >= fm_tcap) {
+ cap = fm_tcap * 2;
+ fm_s = lsp_grow(fm_s, fm_nt, cap); fm_e = lsp_grow(fm_e, fm_nt, cap); fm_k = lsp_grow(fm_k, fm_nt, cap);
+ fm_o = lsp_grow(fm_o, fm_nt, cap); fm_g = lsp_grow(fm_g, fm_nt, cap); fm_l = lsp_grow(fm_l, fm_nt, cap);
+ fm_el = lsp_grow(fm_el, fm_nt, cap); fm_tcap = cap;
+ }
+ fm_s[fm_nt] = s; fm_e[fm_nt] = e; fm_k[fm_nt] = k; fm_o[fm_nt] = o; fm_g[fm_nt] = g; fm_l[fm_nt] = l; fm_el[fm_nt] = el;
+ fm_nt++;
+ return 0;
+}
+int fm_wc(int c) { return hid(c) || c >= 128; }
+// does token t spell the word w
+int fm_is(int i, int t, char* w) {
+ int base = doff[i], o = fm_s[t], k = 0;
+ if (fm_k[t] != 1) return 0;
+ while (w[k]) { if (o + k >= fm_e[t] || dc(base, o + k) != w[k]) return 0; k++; }
+ return o + k == fm_e[t];
+}
+//: the symbol at o: its length, and its code in fm_sym
+int fm_sym;
+int fm_symbol(int base, int o, int size) {
+ int c = dc(base, o), c1 = 0, c2 = 0, n = 1;
+ if (o + 1 < size) c1 = dc(base, o + 1);
+ if (o + 2 < size) c2 = dc(base, o + 2);
+ fm_sym = 0;
+ if (c == ',') fm_sym = 1;
+ else if (c == ';') fm_sym = 2;
+ else if (c == '(' || c == '[') fm_sym = 3;
+ else if (c == ')' || c == ']') fm_sym = 4;
+ else if (c == '{') fm_sym = 5;
+ else if (c == '}') fm_sym = 6;
+ else if (c == ':') fm_sym = 8;
+ else if (c == '?') fm_sym = 9;
+ else if ((c == '<' && c1 == '<' && c2 == '=') || (c == '>' && c1 == '>' && c2 == '=')) { fm_sym = 7; n = 3; }
+ else if (c1 == '=' && (c == '=' || c == '!' || c == '<' || c == '>' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^')) { fm_sym = 7; n = 2; }
+ else if ((c == '&' && c1 == '&') || (c == '|' && c1 == '|')) { fm_sym = 7; n = 2; }
+ else if ((c == '<' && c1 == '<') || (c == '>' && c1 == '>') || (c == '+' && c1 == '+') || (c == '-' && c1 == '-')) n = 2;
+ else if (c == '=') fm_sym = 7;
+ return n;
+}
+// split document i into tokens, each with the spaces before it on its line (-1 for the first of a line)
+int fm_lex(int i) {
+ int base = doff[i], size = dsize[i], o = 0, ln = 0, gap = 0 - 1, c, s, q, e, el, n;
+ fm_nt = 0;
+ if (!fm_s) {
+ fm_tcap = 256;
+ fm_s = lsp_grow(0, 0, 256); fm_e = lsp_grow(0, 0, 256); fm_k = lsp_grow(0, 0, 256); fm_o = lsp_grow(0, 0, 256);
+ fm_g = lsp_grow(0, 0, 256); fm_l = lsp_grow(0, 0, 256); fm_el = lsp_grow(0, 0, 256);
+ }
+ while (o < size) {
+ c = dc(base, o);
+ if (c == 10) { ln++; gap = 0 - 1; o++; continue; }
+ if (c == 13) { o++; continue; }
+ if (c == 32 || c == 9) { if (gap >= 0) gap++; o++; continue; }
+ s = o; el = ln;
+ if (c == '/' && o + 1 < size && dc(base, o + 1) == '/') {
+ while (o < size && dc(base, o) != 10) o++;
+ e = o;
+ while (e > s && (dc(base, e - 1) == 32 || dc(base, e - 1) == 9 || dc(base, e - 1) == 13)) e--;
+ fm_add(s, e, 4, 0, gap, ln, el);
+ } else if (c == '/' && o + 1 < size && dc(base, o + 1) == '*') {
+ o = o + 2;
+ while (o < size && !(dc(base, o) == '*' && o + 1 < size && dc(base, o + 1) == '/')) { if (dc(base, o) == 10) el++; o++; }
+ if (o < size) o = o + 2;
+ fm_add(s, o, 5, 0, gap, ln, el);
+ } else if (c == 34 || c == 39) {
+ q = c; o++;
+ while (o < size && dc(base, o) != q && dc(base, o) != 10) { if (dc(base, o) == 92) o++; o++; }
+ if (o < size && dc(base, o) == q) o++;
+ if (o > size) o = size;
+ if (q == 34) fm_add(s, o, 2, 0, gap, ln, el); else fm_add(s, o, 3, 0, gap, ln, el);
+ } else if (c == 96) {
+ fm_add(s, size, 7, 0, gap, ln, el);
+ o = size;
+ } else if (fm_wc(c)) {
+ while (o < size && fm_wc(dc(base, o))) o++;
+ fm_add(s, o, 1, 0, gap, ln, el);
+ } else {
+ n = fm_symbol(base, o, size);
+ o = o + n;
+ fm_add(s, o, 6, fm_sym, gap, ln, el);
+ }
+ if (el > ln) ln = el;
+ gap = 0;
+ }
+ return 0;
+}
+//: ---- source-only hover lookup ----
+//: embedded documents have no compiler records. parse declaration boundaries rather than assuming
+//: there is only one declaration, or one name, on a line. never interpret initializer uses as declarations.
+int hs_word(int i, int t) { return fm_k[t] == 1 && !run.isdig(dc(doff[i], fm_s[t])); }
+int hs_equal(int i, int t) { return fm_k[t] == 6 && fm_e[t] == fm_s[t] + 1 && dc(doff[i], fm_s[t]) == '='; }
+int hs_match(int i, int t) { return fm_is(i, t, hname); }
+// locate top-level function headers by token boundaries, including adjacent declarations.
+int hover_source_function(int i) {
+ int t = 0, head = -1, previous = -1, depth = 0, nested = 0, initializer = 0, matched = 0;
+ fm_lex(i);
+ while (t < fm_nt) {
+ if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; }
+ if (fm_k[t] == 7) break;
+ if (depth) {
+ if (fm_k[t] == 6 && fm_o[t] == 5) depth++;
+ else if (fm_k[t] == 6 && fm_o[t] == 6) depth--;
+ if (!depth) { head = -1; previous = -1; initializer = 0; }
+ t++; continue;
+ }
+ if (head < 0) head = t;
+ if (!nested && fm_k[t] == 6 && fm_o[t] == 3 && dc(doff[i], fm_s[t]) == '(' && previous > head && !initializer && hs_match(i, previous)) matched = 1;
+ if (fm_k[t] == 6 && fm_o[t] == 3) nested++;
+ else if (fm_k[t] == 6 && fm_o[t] == 4) nested--;
+ if (!nested && hs_equal(i, t)) initializer = 1;
+ if (!nested && fm_k[t] == 6 && (fm_o[t] == 5 || fm_o[t] == 2)) {
+ if (matched) { hdl = fm_s[head]; hdle = fm_s[t]; hs_name = -1; hs_first = -1; hs_const = 0; hcs = -1; return 1; }
+ if (fm_o[t] == 5) depth = 1;
+ head = -1; previous = -1; initializer = 0; matched = 0;
+ } else previous = t;
+ t++;
+ }
+ return 0;
+}
+int hover_source_decl(int i) {
+ int t = 0, head = -1, first = -1, candidate = -1, depth = 0, brackets = 0, init = 0, constant = 0, matched = 0, tail, prev = -1;
+ fm_lex(i);
+ while (t < fm_nt) {
+ if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; }
+ if (fm_k[t] == 7) break;
+ if (depth) {
+ if (fm_k[t] == 6 && fm_o[t] == 5) depth++;
+ if (fm_k[t] == 6 && fm_o[t] == 6) depth--;
+ if (!depth) { head = -1; prev = -1; }
+ t++; continue;
+ }
+ if (head < 0) { head = t; first = -1; candidate = -1; init = 0; brackets = 0; matched = 0; constant = fm_is(i, t, "const"); }
+ if (fm_k[t] == 6 && fm_o[t] == 5) { depth = 1; head = -1; prev = -1; t++; continue; }
+ if (fm_k[t] == 6 && fm_o[t] == 3) {
+ // a function header belongs to the existing function lookup, not this variable lookup.
+ if (!brackets && !init && dc(doff[i], fm_s[t]) == '(') { head = -1; }
+ brackets++;
+ }
+ if (fm_k[t] == 6 && fm_o[t] == 4) brackets--;
+ if (!brackets && head >= 0 && (hs_equal(i, t) || (fm_k[t] == 6 && (fm_o[t] == 1 || fm_o[t] == 2)))) {
+ if (!init && prev >= 0 && hs_word(i, prev) && prev > head) {
+ candidate = prev; if (first < 0) first = prev;
+ // directives and type aliases are not variable declarations.
+ if (!fm_is(i, head, "include") && !fm_is(i, head, "docs") && !fm_is(i, head, "hints") && !fm_is(i, head, "typecheck") && !fm_is(i, head, "type") && !fm_is(i, head, "enhance")) matched = hs_match(i, candidate);
+ }
+ if (hs_equal(i, t)) init = 1;
+ else {
+ if (matched) {
+ hdl = fm_s[head]; hs_first = fm_s[first]; hs_name = fm_s[candidate]; hs_const = constant;
+ tail = fm_s[t]; while (tail > hs_name && (dc(doff[i], tail - 1) == 32 || dc(doff[i], tail - 1) == 9 || dc(doff[i], tail - 1) == 10)) tail--;
+ hdle = tail; return 1;
+ }
+ init = 0; matched = 0; candidate = -1;
+ if (fm_o[t] == 2) { head = -1; first = -1; }
+ }
+ }
+ prev = t; t++;
+ }
+ return 0;
+}
+// the image's evaluated constants are safe to read even while viewing its uncompiled source.
+int hover_blob_value() {
+ word p = rt.GORG[5]; int k;
+ while (*p) {
+ if (p[0] == run.K_CONST) {
+ k = 0; while (hname[k] && p[3 + k] == hname[k]) k++;
+ if (!hname[k] && !p[3 + k]) { hov_text("\n\nvalue at this position:\n```zc\n"); hov_number(p[1]); hov_text("\n```"); return 1; }
+ }
+ p = p + 3 + rt.len_w(p + 3) + 1;
+ }
+ return 0;
+}
+char* asm_description(int op) {
+ if (op == 0) return "halt~stops the virtual machine. stack: ( -- ).";
+ if (op == 1) return "load~reads the word at address a. stack: ( a -- value ). addresses are measured in vm words.";
+ if (op == 2) return "store~writes value to the word at address a. stack: ( value a -- ).";
+ if (op == 3) return "pick~duplicates the item n places below the top after consuming n; 0 duplicates the top item. stack: ( ... n -- ... item ).";
+ if (op == 4) return "drop~discards the top item. stack: ( value -- ).";
+ if (op == 5) return "swap~exchanges the top two items. stack: ( a b -- b a ).";
+ if (op == 6) return "call~calls the code at target, saving the next instruction on the return stack. stack: ( target -- ).";
+ if (op == 7) return "jump~continues execution at target. stack: ( target -- ).";
+ if (op == 8) return "jump if zero~jumps to target when condition is zero, consuming both operands. stack: ( condition target -- ).";
+ if (op == 9) return "return~resumes at the address popped from the return stack. leaves the data stack alone.";
+ if (op == 10) return "input~reads one input byte as a signed character and records end-of-input status. stack: ( -- character ).";
+ if (op == 11) return "end of input~pushes 1 at end of input, otherwise 0. stack: ( -- flag ).";
+ if (op == 12) return "output~writes the low byte of character to output. stack: ( character -- ).";
+ if (op == 13) return "add~adds two 64-bit words, wrapping on overflow. stack: ( a b -- a + b ).";
+ if (op == 14) return "subtract~subtracts the top item from the item below it. stack: ( a b -- a - b ).";
+ if (op == 15) return "multiply~multiplies two 64-bit words, wrapping on overflow. stack: ( a b -- a * b ).";
+ if (op == 16) return "bitwise and~stack: ( a b -- a & b ).";
+ if (op == 17) return "bitwise or~stack: ( a b -- a | b ).";
+ if (op == 18) return "bitwise xor~stack: ( a b -- a ^ b ).";
+ if (op == 19) return "arithmetic shift right~shifts a right by b & 63 bits, extending its sign. stack: ( a b -- result ).";
+ if (op == 20) return "signed less than~compares a with b, returning 1 or 0. stack: ( a b -- a < b ).";
+ if (op == 21) return "install enhancement~installs the enhancement descriptor and returns its slot. stack: ( descriptor -- slot ).";
+ if (op == 600) return "float literal~reads the following raw word as ieee binary64 bits and pushes it. stack: ( -- float ).";
+ if (op == 601) return "float add~adds binary64 values. stack: ( a b -- a + b ).";
+ if (op == 602) return "float subtract~subtracts binary64 values. stack: ( a b -- a - b ).";
+ if (op == 603) return "float multiply~multiplies binary64 values. stack: ( a b -- a * b ).";
+ if (op == 604) return "float divide~divides binary64 values. stack: ( a b -- a / b ).";
+ if (op == 605) return "float equal~compares binary64 values for equality. stack: ( a b -- flag ).";
+ if (op == 606) return "float less than~compares binary64 values. stack: ( a b -- flag ).";
+ if (op == 607) return "float less or equal~compares binary64 values. stack: ( a b -- flag ).";
+ if (op == 608) return "integer to float~converts a signed integer to binary64. stack: ( integer -- float ).";
+ if (op == 609) return "float to integer~converts binary64 to a signed integer, truncating toward zero. stack: ( float -- integer ).";
+ if (op == 619) return "data stack pointer~pushes the data stack address before this instruction. stack: ( -- address ).";
+ if (op == 620) return "native call~calls a host function through its descriptor, consuming its arguments and returning one value. stack: ( arguments descriptor -- result ).";
+ if (op == 612) return "load byte~reads an unsigned byte at a byte address. stack: ( address -- value ).";
+ if (op == 613) return "store byte~writes the low 8 bits at a byte address. stack: ( value address -- ).";
+ if (op == 614) return "load 16 bits~reads an unsigned little-endian 16-bit value at a byte address. stack: ( address -- value ).";
+ if (op == 615) return "store 16 bits~writes the low 16 bits at a byte address. stack: ( value address -- ).";
+ if (op == 616) return "load 32 bits~reads an unsigned little-endian 32-bit value at a byte address. stack: ( address -- value ).";
+ if (op == 617) return "store 32 bits~writes the low 32 bits at a byte address. stack: ( value address -- ).";
+ return 0;
+}
+// concise overviews for modules supplied by the compiler and language server.
+char* module_description() {
+ if (hname_is("rt")) return "the runtime module: input and output, strings, allocation, ownership, collection and host operations.";
+ if (hname_is("run")) return "the resident compiler module: compile and evaluate source, inspect tokens and functions, and register syntax or receiver extensions.";
+ if (hname_is("core")) return "the vm host interface: host gates, operand-stack addresses, byte-addressed memory access and program termination.";
+ if (hname_is("lsp")) return "the language server module: document analysis, diagnostics, markdown hovers, completion, navigation and formatting.";
+ if (hname_is("std")) return "the embedded standard library: text and byte strings, numeric helpers, files, buffers, array algorithms and command-line arguments. no file is read and nothing imports it automatically.";
+ if (hname_is("icon")) return "structured runtime syntax transformations: token templates, balanced captures, expression and block edits, function publication and scalar execution inspection.";
+ return 0;
+}
+// source modules provide their overview in the first comment after namespace
+// and include directives. declaration comments further down remain symbol docs.
+int module_overview(int i) {
+ char* prefix = run.inc_cat("src/lib", hname); int length = rt.len_w(prefix), at = 0, end, start, count = 0, a, b;
+ char* path = rt.new(length + 4), source;
+ rt.copy_w(path, prefix, length); rt.copy_w(path + length, ".zc", 4);
+ inc_setup(i); source = run.inc_open(path);
+ if (!source) return 0;
+ length = rt.len_w(source);
+ while (at < length) {
+ end = at; while (end < length && source[end] != 10) end++;
+ start = at; while (start < end && (source[start] == 32 || source[start] == 9 || source[start] == 13)) start++;
+ if (source[start] == '/' && source[start + 1] == '/') {
+ start += 2; if (source[start] == ':') start++;
+ if (source[start] == 32) start++;
+ if (run.doc_section(source + start, end - start)) break;
+ if (!count) hov_text("\n\n"); else hov_put(10);
+ while (start < end && source[start] != 13) { hov_put(source[start]); start++; }
+ count++;
+ } else if (source[start] == '/' && source[start + 1] == '*') {
+ if (count) break;
+ hov_text("\n\n"); start += 2;
+ while (source[start] && !(source[start] == '*' && source[start + 1] == '/')) {
+ if (source[start] == 10) {
+ hov_put(10); start++;
+ while (source[start] == 32 || source[start] == 9) start++;
+ if (source[start] == '*' && source[start + 1] != '/') { start++; if (source[start] == 32) start++; }
+ } else { hov_put(source[start]); start++; }
+ }
+ return 1;
+ } else {
+ if (count) break;
+ if (start != end) {
+ a = 0; while (a < 9 && source[start + a] == "namespace"[a]) a++;
+ b = 0; while (b < 7 && source[start + b] == "include"[b]) b++;
+ if (!((a == 9 && (source[start + a] == 32 || source[start + a] == ';')) || (b == 7 && source[start + b] == 32))) break;
+ }
+ }
+ at = end + 1;
+ }
+ return count;
+}
+// recognize each identifier in include lists without treating module-named
+// locals, comments or strings as imports.
+int hover_module(int i) {
+ int t = 0, active = 0; char* description;
+ fm_lex(i);
+ while (t < fm_nt && fm_s[t] <= hcursor) {
+ if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; }
+ if (fm_is(i, t, "include")) active = 1;
+ else if (active && fm_k[t] == 1) {
+ if (hcursor >= fm_s[t] && hcursor < fm_e[t]) {
+ hn = 0; hov_text("```zc\ninclude "); hov_text(hname); hov_text(";\n```\n\n");
+ description = module_description();
+ if (description) hov_text(description); else { hov_text("a source library module."); module_overview(i); }
+ hov_text("\n\nexports use the `"); hov_text(hname); hov_text(".name` prefix. the include applies to this source file; dependency imports do not become imports of the caller.");
+ return 1;
+ }
+ active = 2;
+ } else if (active && fm_k[t] == 6 && fm_o[t] == 1) active = 1;
+ else active = 0;
+ t++;
+ }
+ return 0;
+}
+// only instruction tokens in asm/vm blocks get instruction descriptions. strings,
+// comments and ordinary expressions retain their own hovers.
+int hover_instruction(int i) {
+ int t = 0, depth = 0, raw = 0, pending = 0, prev = -1, c, op, encoded = 0, rawword = 0, k; word desc;
+ if (hcursor < 0) return 0;
+ fm_lex(i);
+ while (t < fm_nt) {
+ if (fm_s[t] > hcursor) break;
+ if (fm_k[t] == 4 || fm_k[t] == 5 || fm_k[t] == 2 || fm_k[t] == 3) { t++; continue; }
+ if (fm_k[t] == 7) break;
+ if (hcursor >= fm_s[t] && hcursor < fm_e[t]) {
+ if (prev >= 0 && fm_is(i, prev, "include") && fm_is(i, t, "std")) {
+ hn = 0; return kw_put("include std;~compiles the embedded standard library here. no file is read and nothing imports it automatically. provides Text, Bytes, Host, Arena, Buf, capture, and command-line helpers.");
+ }
+ if (!raw || (prev >= 0 && fm_k[prev] == 6 && fm_o[prev] == 8)) return 0;
+ op = -1;
+ if (fm_e[t] == fm_s[t] + 1) op = run.mnemidx(dc(doff[i], fm_s[t]));
+ if (dc(doff[i], fm_s[t]) == '#' || (prev >= 0 && dc(doff[i], fm_s[prev]) == '#')) {
+ k = t; if (dc(doff[i], fm_s[t]) == '#') k++;
+ if (k < fm_nt) {
+ c = fm_s[k]; rawword = 0;
+ while (c < fm_e[k] && run.isdig(dc(doff[i], c))) { rawword = rawword * 10 + dc(doff[i], c) - '0'; c++; }
+ if (c == fm_e[k]) { encoded = 1; if (!(rawword & 1)) op = rawword >> 1; }
+ }
+ }
+ desc = asm_description(op); if (!desc && !encoded) return 0;
+ hn = 0; hov_text("```zc\n");
+ if (encoded) { hov_put('#'); hov_number(rawword); }
+ else hov_put(dc(doff[i], fm_s[t]));
+ hov_text("\n```\n\n");
+ if (desc) { k = 0; while (desc[k] && desc[k] != '~') { hov_put(desc[k]); k++; } hov_text(" (opcode "); hov_number(op); hov_text(").\n\n"); if (desc[k]) k++; while (desc[k]) { hov_put(desc[k]); k++; } }
+ else if (rawword & 1) { hov_text("encoded literal: pushes "); hov_number(rawword >> 1); hov_put('.'); }
+ else { hov_text("raw encoded opcode "); hov_number(op); hov_put('.'); }
+ return 1;
+ }
+ if (fm_k[t] == 6 && fm_o[t] == 5) { depth++; if (pending) raw = depth; pending = 0; }
+ else if (fm_k[t] == 6 && fm_o[t] == 6) { if (raw == depth) raw = 0; depth--; }
+ else if (fm_is(i, t, "asm") || fm_is(i, t, "vm")) pending = 1;
+ else if (pending) pending = 0;
+ prev = t; t++;
+ }
+ return 0;
+}
+
+int fm_defaults() {
+ fm_tab = 1; fm_width = 4; fm_blank = 1; fm_cont = 1; fm_norm = 1; fm_casein = 1; fm_final = 1;
+ if (fm_edit_set) {
+ fm_tab = fm_edit_tab;
+ if (fm_edit_width > 0 && fm_edit_width < 17) fm_width = fm_edit_width;
+ }
+ return 0;
+}
+int fm_bool(char* v, int n) { return n >= 1 && (v[0] == 't' || v[0] == 'T' || v[0] == 'y' || v[0] == 'Y' || v[0] == '1'); }
+int fm_key(char* buf, int a, int b, char* name) {
+ int k = 0;
+ while (name[k]) { if (a + k >= b || buf[a + k] != name[k]) return 0; k++; }
+ return a + k == b;
+}
+// one "Key: value" line of a .zcformat file, at buf[a..b)
+int fm_option(char* buf, int a, int b) {
+ int k = a, ks, ke, vs, ve, num = 0, j;
+ while (k < b && (buf[k] == 32 || buf[k] == 9)) k++;
+ if (k >= b || buf[k] == '#') return 0;
+ ks = k;
+ while (k < b && buf[k] != ':') k++;
+ if (k >= b) return 0;
+ ke = k;
+ while (ke > ks && (buf[ke - 1] == 32 || buf[ke - 1] == 9)) ke--;
+ k++;
+ while (k < b && (buf[k] == 32 || buf[k] == 9)) k++;
+ vs = k; ve = b;
+ while (ve > vs && (buf[ve - 1] == 32 || buf[ve - 1] == 9 || buf[ve - 1] == 13)) ve--;
+ j = vs;
+ while (j < ve && buf[j] >= '0' && buf[j] <= '9') { num = num * 10 + buf[j] - '0'; j++; }
+ if (fm_key(buf, ks, ke, "UseTab")) fm_tab = fm_bool(buf + vs, ve - vs);
+ else if (fm_key(buf, ks, ke, "IndentWidth")) { if (j > vs && num > 0 && num < 17) fm_width = num; }
+ else if (fm_key(buf, ks, ke, "MaxEmptyLines")) { if (j > vs && num < 100) fm_blank = num; }
+ else if (fm_key(buf, ks, ke, "ContinuationIndent")) { if (j > vs && num < 9) fm_cont = num; }
+ else if (fm_key(buf, ks, ke, "NormalizeSpacing")) fm_norm = fm_bool(buf + vs, ve - vs);
+ else if (fm_key(buf, ks, ke, "CaseIndent")) { if (j > vs && num < 9) fm_casein = num; }
+ else if (fm_key(buf, ks, ke, "FinalNewline")) fm_final = fm_bool(buf + vs, ve - vs);
+ return 0;
+}
+// the options for document i: the defaults, the editor's tab settings, then a .zcformat from the
+// document's directory or the nearest one above it
+
+int fm_read_config(char* path) {
+ char* buf = rt.readfile(path); int k = 0, run.line;
+ if (!buf) return 0;
+ while (buf[k]) {
+ run.line = k;
+ while (buf[k] && buf[k] != 10) k++;
+ fm_option(buf, run.line, k);
+ if (buf[k]) k++;
+ }
+ rt.free(buf);
+ return 1;
+}
+int fm_config(int i) {
+ char* uri; int n, run.start = 0, end, k;
+ fm_defaults();
+ uri = duri[i]; n = dulen[i];
+ if (n > 7 && uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l' && uri[3] == 'e') run.start = 7;
+ end = n;
+ while (end > run.start && uri[end - 1] != '/') end--;
+ if (end > run.start) end--;
+ while (end >= run.start) {
+ navpn = 0; navp = lsp_grow(navp, 0, navcap); k = run.start;
+ while (k < end) { nav_put(uri[k]); k++; }
+ nav_put('/'); k = 0;
+ while (".zcformat"[k]) { nav_put(".zcformat"[k]); k++; }
+ if (fm_read_config(navp)) return 1;
+ if (end == run.start) break;
+ end--;
+ while (end > run.start && uri[end - 1] != '/') end--;
+ if (end > run.start) end--;
+ }
+ return 0;
+}
+// n levels of indent
+int fm_indent(int levels) {
+ int k = 0, w;
+ if (levels < 0) levels = 0;
+ if (fm_tab) { while (k < levels) { fm_put(9); k++; } }
+ else { w = levels * fm_width; while (k < w) { fm_put(32); k++; } }
+ return 0;
+}
+// the spaces between tokens a and b of one line when the source had g; raw: inside an asm or vm block;
+// qopen: a ? is waiting for its colon
+int fm_gap(int i, int a, int b, int g, int raw, int qopen) {
+ int ka = fm_k[a], kb = fm_k[b], oa = fm_o[a], ob = fm_o[b], keep = 0;
+ if (g > 0) keep = 1;
+ if (raw) return g;
+ if (kb == 4) { if (g > 0) return g; return 1; }
+ if (!fm_norm) return keep;
+ if (ka == 5 || kb == 5) return keep;
+ if (ka == 6 && oa == 3) return 0;
+ if (kb == 6 && ob == 4) return 0;
+ if (kb == 6 && (ob == 1 || ob == 2)) return 0;
+ if (ka == 6 && (oa == 1 || oa == 2)) return 1;
+ if (ka == 6 && oa == 5 && kb == 6 && ob == 6) return keep;
+ if ((ka == 6 && oa == 5) || (kb == 6 && ob == 6)) return 1;
+ if ((ka == 6 && (oa == 7 || oa == 9)) || (kb == 6 && (ob == 7 || ob == 9))) return 1;
+ if (qopen && ((ka == 6 && oa == 8) || (kb == 6 && ob == 8))) return 1;
+ if (kb == 6 && ob == 3 && dc(doff[i], fm_s[b]) == '(' && ka == 1 && (fm_is(i, a, "if") || fm_is(i, a, "while") || fm_is(i, a, "for") || fm_is(i, a, "switch"))) return 1;
+ if (kb == 6 && ob == 5 && ((ka == 6 && oa == 4) || ka == 1)) return 1;
+ if (ka == 6 && oa == 6 && kb == 1 && (fm_is(i, b, "else") || fm_is(i, b, "while"))) return 1;
+ return keep;
+}
+// a block comment at token t, indented to levels: lines that start with a star line up under the first
+int fm_comment(int i, int t, int levels) {
+ int base = doff[i], o = fm_s[t], e = fm_e[t], c, k;
+ while (o < e) {
+ c = dc(base, o);
+ if (c == 13) { o++; continue; }
+ o++;
+ if (c == 10) {
+ fm_newline();
+ k = o;
+ while (k < e && (dc(base, k) == 32 || dc(base, k) == 9)) k++;
+ if (k < e && dc(base, k) == '*') { fm_indent(levels); fm_put(32); o = k; }
+ } else fm_put(c);
+ }
+ return 0;
+}
+//: the state after token x: braces, parentheses, switch bodies and asm blocks
+int fm_bd, fm_pd, fm_raw, fm_rawd, fm_pendsw, fm_pendraw, fm_lastk, fm_lasto;
+int fm_step(int i, int x) {
+ int k = fm_k[x], o = fm_o[x];
+ if (k == 4 || k == 5) return 0;
+ if (k == 1) {
+ fm_pendraw = 0;
+ if (fm_is(i, x, "switch")) fm_pendsw = 1;
+ if (fm_bd > 0 && fm_sw[fm_bd] && (fm_is(i, x, "case") || fm_is(i, x, "default"))) fm_sw[fm_bd] = 2;
+ if (fm_is(i, x, "asm") || fm_is(i, x, "vm")) fm_pendraw = 1;
+ } else if (k == 6 && o == 5) {
+ fm_bd++;
+ if (fm_bd + 2 >= fm_swcap) { fm_sw = lsp_grow(fm_sw, fm_swcap, fm_swcap * 2); fm_swcap = fm_swcap * 2; }
+ fm_sw[fm_bd] = 0;
+ if (fm_pendsw && fm_pd == 0) fm_sw[fm_bd] = 1;
+ fm_pendsw = 0;
+ if (fm_pendraw && !fm_raw) { fm_raw = 1; fm_rawd = fm_bd; }
+ fm_pendraw = 0;
+ } else {
+ fm_pendraw = 0;
+ if (k == 6 && o == 6) {
+ if (fm_raw && fm_bd == fm_rawd) fm_raw = 0;
+ if (fm_bd > 0) fm_bd--;
+ } else if (k == 6 && o == 3) fm_pd++;
+ else if (k == 6 && o == 4) { if (fm_pd > 0) fm_pd--; }
+ else if (k == 6 && o == 2) fm_pendsw = 0;
+ }
+ fm_lastk = k; fm_lasto = o;
+ return 0;
+}
+// format document i into fm_out
+int fm_run(int i) {
+ int base = doff[i], size = dsize[i], t = 0, t1, x, lead_b, lead_p, k, lvl, cont, blank, endl = 0 - 1, qopen, g, c, first_raw, depth_index, swindent;
+ fm_n = 0;
+ if (!fm_out) { fm_cap = 1024; fm_out = lsp_grow(0, 0, 1024); }
+ if (!fm_sw) { fm_swcap = 64; fm_sw = lsp_grow(0, 0, 64); }
+ fm_lex(i);
+ fm_bd = 0; fm_pd = 0; fm_raw = 0; fm_rawd = 0; fm_pendsw = 0; fm_pendraw = 0; fm_lastk = 0; fm_lasto = 0;
+ while (t < fm_nt) {
+ t1 = t + 1;
+ while (t1 < fm_nt && fm_g[t1] >= 0) t1++;
+ if (fm_n > 0) {
+ blank = fm_l[t] - endl - 1;
+ if (blank > fm_blank) blank = fm_blank;
+ while (blank > 0) { fm_newline(); blank--; }
+ }
+ first_raw = fm_raw;
+ lead_b = 0; lead_p = 0; k = t;
+ while (k < t1 && fm_k[k] == 6 && (fm_o[k] == 6 || fm_o[k] == 4)) { if (fm_o[k] == 6) lead_b++; else lead_p++; k++; }
+ if (first_raw && !(fm_k[t] == 6 && fm_o[t] == 6 && fm_bd == fm_rawd)) {
+ // inside an asm or vm block the line keeps its text
+ fm_indent(fm_bd);
+ x = fm_s[t];
+ while (x < fm_e[t1 - 1]) { c = dc(base, x); if (c != 13) fm_put(c); x++; }
+ x = t; while (x < t1) { fm_step(i, x); x++; }
+ fm_newline();
+ endl = fm_el[t1 - 1];
+ t = t1;
+ continue;
+ }
+ swindent = 0; depth_index = 1;
+ while (depth_index < fm_bd) { if (fm_sw[depth_index] == 2) swindent = swindent + fm_casein; depth_index++; }
+ cont = 0;
+ if (fm_k[t] == 1 && (fm_is(i, t, "case") || fm_is(i, t, "default")) && fm_bd > 0 && fm_sw[fm_bd]) {
+ lvl = fm_bd - 1 + fm_casein + swindent;
+ } else {
+ if (lead_b == 0 && fm_bd > 0 && fm_sw[fm_bd] == 2) swindent = swindent + fm_casein;
+ lvl = fm_bd - lead_b + swindent;
+ if (fm_pd - lead_p > 0) cont = fm_cont;
+ else if (fm_lastk != 0 && !(fm_lastk == 6 && (fm_lasto == 1 || fm_lasto == 2 || fm_lasto == 5 || fm_lasto == 6 || fm_lasto == 8)) && !(fm_k[t] == 6 && (fm_o[t] == 5 || fm_o[t] == 6))) cont = fm_cont;
+ }
+ if (lvl < 0) lvl = 0;
+ fm_indent(lvl + cont);
+ qopen = 0;
+ x = t;
+ while (x < t1) {
+ if (x > t) {
+ g = fm_gap(i, x - 1, x, fm_g[x], fm_raw, qopen);
+ while (g > 0) { fm_put(32); g--; }
+ }
+ if (fm_k[x] == 5) fm_comment(i, x, lvl + cont);
+ else if (fm_k[x] == 7) {
+ // what follows a backtick is data
+ k = fm_s[x];
+ while (k < size) { c = dc(base, k); if (c != 13) fm_put(c); k++; }
+ return 0;
+ } else {
+ k = fm_s[x];
+ while (k < fm_e[x]) { fm_put(dc(base, k)); k++; }
+ }
+ if (fm_k[x] == 6 && fm_o[x] == 9) qopen++;
+ else if (fm_k[x] == 6 && fm_o[x] == 8 && qopen > 0) qopen--;
+ fm_step(i, x);
+ x++;
+ }
+ fm_newline();
+ endl = fm_el[t1 - 1];
+ t = t1;
+ }
+ // one line break ends the file
+ while (fm_n > 0 && (fm_out[fm_n - 1] == 10 || fm_out[fm_n - 1] == 32 || fm_out[fm_n - 1] == 9)) fm_n--;
+ if (fm_n > 0 && fm_final) fm_put(10);
+ return 0;
+}
+// the formatted text of document i is in fm_out
+int fm_format(int i) {
+ fm_config(i);
+ fm_run(i);
+ return 0;
+}
+// is the formatted text the same as the document
+int fm_same(int i) {
+ int k = 0;
+ if (fm_n != dsize[i]) return 0;
+ while (k < fm_n) { if (fm_out[k] != dc(doff[i], k)) return 0; k++; }
+ return 1;
+}
+// textDocument/formatting: one edit that replaces the whole text, or none when it is already formatted
+int gen_format() {
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid();
+ if (fm_same(nav_i)) { ws(",\"result\":[]}"); return 0; }
+ pc_reset(nav_i);
+ ws(",\"result\":[{\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":"); wpos(dsize[nav_i]);
+ ws("},\"newText\":"); wjs(fm_out, fm_n); ws("}]}");
+ return 0;
+}
+int do_format(int i) {
+ nav_i = i;
+ fm_format(i);
+ send(gen_format);
+ return 0;
+}
+// zclsp --format < file.zc prints the file formatted
+int format_cli(int first) {
+ int k = 0;
+ load_stdin(first);
+ fm_defaults();
+ fm_run(0);
+ while (k < fm_n) { rt.put_c(fm_out[k]); k++; }
+ return 0;
+}
+
+
+//: ---- completion ----
+//: candidates own their text: another compiler check can replace all symbol and type tables.
+char** cp_name, cp_detail, cp_docs; word cp_kind, cp_dead, cp_rank, cp_order, cp_tmp;
+int cp_n, cp_cap, cp_i, cp_start, cp_end, cp_cursor; char* cp_prefix;
+char* cp_copy(char* text) {
+ int n = rt.len_w(text); char* p = lsp_grow(0, 0, n + 1);
+ rt.copy_w(p, text, n); return p;
+}
+char* cp_hov() {
+ char* p = lsp_grow(0, 0, hn + 1); rt.copy_w(p, hov, hn); return p;
+}
+int cp_lower(int c) { if (c >= 'A' && c <= 'Z') return c + 32; return c; }
+// subsequence matching rewards exact spelling, prefixes, consecutive letters and word boundaries.
+int cp_score(char* name) {
+ int i = 0, j = 0, score = 0, last = -2, n = rt.len_w(name), exact = 1;
+ if (!cp_prefix[0]) return 100;
+ while (cp_prefix[i]) {
+ while (j < n && cp_lower(name[j]) != cp_lower(cp_prefix[i])) j++;
+ if (j == n) return -1;
+ if (j != i || name[j] != cp_prefix[i]) exact = 0;
+ score = score + j;
+ if (j != last + 1) score = score + 12;
+ if (j == 0 || name[j - 1] == '_' || (name[j] >= 'A' && name[j] <= 'Z')) score = score - 8;
+ last = j; j++; i++;
+ }
+ if (exact) { if (n == i) return 0; return 10 + n - i; }
+ return 100 + score + n - i;
+}
+int cp_add(char* name, int kind, int dead, int relevance) {
+ int score = cp_score(name);
+ if (score < 0) return 0;
+ if (cp_n == cp_cap) {
+ if (!cp_cap) cp_cap = 64; else cp_cap = cp_cap * 2;
+ cp_name = lsp_grow(cp_name, cp_n, cp_cap); cp_detail = lsp_grow(cp_detail, cp_n, cp_cap);
+ cp_docs = lsp_grow(cp_docs, cp_n, cp_cap); cp_kind = lsp_grow(cp_kind, cp_n, cp_cap);
+ cp_dead = lsp_grow(cp_dead, cp_n, cp_cap); cp_rank = lsp_grow(cp_rank, cp_n, cp_cap); cp_order = lsp_grow(cp_order, cp_n, cp_cap); cp_tmp = lsp_grow(cp_tmp, 0, cp_cap);
+ }
+ cp_name[cp_n] = cp_copy(name); cp_kind[cp_n] = kind; cp_dead[cp_n] = dead;
+ cp_detail[cp_n] = cp_hov(); cp_docs[cp_n] = 0;
+ cp_rank[cp_n] = dead * 10000000 + score * 10 + relevance; cp_n++;
+ return 1;
+}
+bstr cp_signature(char* name) {
+ int k = 0, j = 0, s;
+ run.doc_name_room(rt.len_w(name) + 5);
+ while ("sig "[k]) { run.dnmb[k] = "sig "[k]; k++; }
+ while (name[j]) { run.dnmb[k] = name[j]; k++; j++; } run.dnmb[k] = 0;
+ s = run.lookup(run.intern(run.dnmb));
+ if (s >= 0 && run.skind[s] == run.K_DOC) return run.sval[s];
+ return 0;
+}
+// signatures and comments collected by the compiler, with types for globals and fields.
+int cp_symbol(int s, char* label) {
+ if (cp_score(label) < 0) return 0;
+ bstr sig = cp_signature(run.sname[s]), doc; int k = 0, kind = 6;
+ hn = 0;
+ if (sig) { while (k < rt.len_s(sig)) { hov_put(rt.at_s(sig, k)); k++; } }
+ else { if (run.stype[s]) { hov_type(run.stype[s]); hov_put(' '); } hov_text(label); }
+ if (run.skind[s] == run.K_FN || run.skind[s] == run.K_ENH) kind = 3;
+ else if (run.skind[s] == run.K_TYPE) kind = 22;
+ else if (run.skind[s] == run.K_CONST) kind = 21;
+ else if (run.skind[s] == run.K_KW) kind = 14;
+ if (!cp_add(label, kind, 0, 3)) return 0;
+ hn = 0; doc = run.docofs(run.sname[s]);
+ if (doc) { k = 0; while (k < rt.len_s(doc)) { hov_put(rt.at_s(doc, k)); k++; } }
+ if (hn) cp_docs[cp_n - 1] = cp_hov();
+ return 0;
+}
+// array methods expose the receiver's element type in their signatures.
+int cp_array_method(word result, char* name, word parameter, char* argument, char* docs) {
+ hn = 0; hov_type(result);
+ if (rt.eq_s(name, "pop") && run.isref(result)) hov_text(".u");
+ hov_put(' '); hov_text(name); hov_put('(');
+ if (parameter) { hov_type(parameter); hov_put(' '); hov_text(argument); }
+ hov_put(')');
+ if (cp_add(name, 2, 0, 0)) cp_docs[cp_n - 1] = cp_copy(docs);
+ return 0;
+}
+int cp_array_members(word t) {
+ word link;
+ cp_array_method(run.tint, "len", 0, "", "returns the number of elements in the array.");
+ cp_array_method(run.tint, "cap", 0, "", "returns the allocated element capacity; for a fixed array, this equals its length.");
+ if (t[0] == run.TY_DYN && !t[3] && !run.treadonly(t)) {
+ if (!run.isagg(t[4])) {
+ cp_array_method(run.tint, "push", t[4], "value", "appends one element. growth may relocate storage and invalidate borrowed element addresses.");
+ cp_array_method(t[4], "pop", 0, "", "removes and returns the last element, transferring ownership for references. an empty array reports an error.");
+ }
+ cp_array_method(run.tint, "resize", run.tint, "length", "sets the array length and zero-fills added elements. resizing invalidates borrowed element addresses.");
+ }
+ link = run.receiver_methods;
+ while (link) {
+ if (!run.array_method(link[1]) && run.receiver_method(t, link[1]) == link[2]) cp_symbol(link[2], link[1]);
+ link = link[0];
+ }
+ return 0;
+}
+int cp_before(int a, int b) {
+ int k = 0;
+ if (cp_rank[a] != cp_rank[b]) return cp_rank[a] < cp_rank[b];
+ while (cp_name[a][k] && cp_name[a][k] == cp_name[b][k]) k++;
+ return cp_name[a][k] < cp_name[b][k];
+}
+int gen_completion() {
+ int i = 0, j, k, width = 1, lo, mid, hi, a, b, dest; word swap;
+ while (i < cp_n) { cp_order[i] = i; i++; }
+ // bottom-up merge sort keeps large completion lists responsive on the interpreter hosts.
+ while (width < cp_n) {
+ lo = 0;
+ while (lo < cp_n) {
+ mid = lo + width; if (mid > cp_n) mid = cp_n;
+ hi = mid + width; if (hi > cp_n) hi = cp_n;
+ a = lo; b = mid; dest = lo;
+ while (dest < hi) {
+ if (a < mid && (b >= hi || cp_before(cp_order[a], cp_order[b]))) { cp_tmp[dest] = cp_order[a]; a++; }
+ else { cp_tmp[dest] = cp_order[b]; b++; }
+ dest++;
+ }
+ lo = hi;
+ }
+ swap = cp_order; cp_order = cp_tmp; cp_tmp = swap; width = width * 2;
+ }
+ pc_reset(cp_i);
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"isIncomplete\":true,\"items\":[");
+ i = 0;
+ while (i < cp_n) {
+ k = cp_order[i]; if (i) wc(',');
+ ws("{\"label\":"); wjs(cp_name[k], rt.len_w(cp_name[k])); ws(",\"kind\":"); wn(cp_kind[k]);
+ ws(",\"detail\":"); wjs(cp_detail[k], rt.len_w(cp_detail[k]));
+ // the server has already matched the prefix; equal client scores preserve its relevance and lifetime order.
+ if (cp_prefix[0]) { ws(",\"filterText\":"); wjs(cp_prefix, rt.len_w(cp_prefix)); }
+ ws(",\"sortText\":\""); j = 1000000000;
+ while (j > 0) { wc('0' + ((i / j) % 10)); j = j / 10; } wc('"');
+ if (cp_dead[k]) ws(",\"tags\":[1],\"deprecated\":true");
+ if (cp_docs[k] || cp_dead[k]) {
+ ws(",\"documentation\":{\"kind\":\"markdown\",\"value\":"); hn = 0;
+ if (cp_docs[k]) hov_text(cp_docs[k]);
+ if (cp_dead[k]) hov_text("\n\nlifetime ended: this variable has been consumed and cannot be used here.");
+ wjs(hov, hn); wc('}');
+ }
+ ws(",\"textEdit\":{\"range\":{\"start\":"); wpos(cp_start); ws(",\"end\":"); wpos(cp_end);
+ ws("},\"newText\":"); wjs(cp_name[k], rt.len_w(cp_name[k])); ws("}}"); i++;
+ }
+ ws("]}}"); return 0;
+}
+int do_completion(int i) {
+ int s, k, j, old, n, head, c; word member, fields; char* nm; char* owner = 0;
+ cp_i = i; cp_n = 0;
+ hover_word(i); if (hcursor < 0) { send(gen_null); return 0; }
+ cp_cursor = hcursor; cp_start = cp_cursor; cp_end = cp_cursor;
+ while (cp_start > 0 && hid(dc(doff[i], cp_start - 1))) cp_start--;
+ while (cp_end < dsize[i] && hid(dc(doff[i], cp_end))) cp_end++;
+ n = cp_cursor - cp_start; cp_prefix = lsp_grow(0, 0, n + 1);
+ k = 0; while (k < n) { cp_prefix[k] = dc(doff[i], cp_start + k); k++; }
+ if (cp_start > 0 && dc(doff[i], cp_start - 1) == '.') {
+ head = cp_start - 1;
+ while (head > 0 && hid(dc(doff[i], head - 1))) head--;
+ n = cp_start - 1 - head; owner = m*lsp_grow(0, 0, n + 1);
+ k = 0; while (k < n) { owner[k] = dc(doff[i], head + k); k++; } owner[n] = 0;
+ if (!run.module_known(owner)) owner = 0;
+ }
+ // the full check supplies functions declared later; the probe supplies locals and their current lifetimes.
+ ensure_checked(i);
+ if (owner) {
+ if (run.module_imported(owner)) {
+ n = rt.len_w(owner); s = 0;
+ while (s < run.nsym) {
+ nm = run.sname[s]; k = 0; while (k < n && nm[k] == owner[k]) k++;
+ if (k == n && nm[k] == '.' && run.skind[s] != run.K_DOC && run.lookup(nm) == s) cp_symbol(s, nm + n + 1);
+ s++;
+ }
+ }
+ send(gen_completion); return 0;
+ }
+ s = 0;
+ while (s < run.nsym) {
+ if (run.skind[s] != run.K_DOC && run.lookup(run.sname[s]) == s && (run.skind[s] != run.K_FN || (run.sflag[s] & (run.F_DEF | run.F_DECL)))) cp_symbol(s, run.sname[s]);
+ s++;
+ }
+ run.probe_off = cp_cursor; old = chk_stream; chk_stream = 0; check_doc(i); chk_stream = old; run.probe_off = -1;
+ member = 0;
+ if (run.probe_has == 2 && run.probe_mem && run.probe_mem[0] == run.TY_STRUCT) member = run.probe_mem[2];
+ if (run.probe_has == 3) member = run.probe_static;
+ if (run.probe_has == 2 || run.probe_has == 3) {
+ cp_n = 0;
+ if (run.probe_has == 2 && run.probe_mem && (run.probe_mem[0] == run.TY_ARR || run.probe_mem[0] == run.TY_DYN)) cp_array_members(run.probe_mem);
+ if (run.probe_has == 2 && run.probe_mem && run.probe_mem[0] == run.TY_STRUCT) {
+ fields = run.probe_mem[4]; k = 0;
+ while (k < run.probe_mem[3]) {
+ hn = 0; hov_type(fields[k * 3 + 1]); hov_put(' '); hov_text(fields[k * 3]);
+ cp_add(fields[k * 3], 5, 0, 0); k++;
+ }
+ }
+ if (member) {
+ n = rt.len_w(member); s = 0;
+ while (s < run.nsym) {
+ nm = run.sname[s]; k = 0; while (k < n && nm[k] == member[k]) k++;
+ if (k == n && nm[k] == '.' && run.lookup(nm) == s && (run.probe_has != 2 || !run.probe_mem || run.probe_mem[0] != run.TY_STRUCT || run.tfindfield(run.probe_mem, nm + n + 1) < 0) && (run.skind[s] == run.K_FN || run.skind[s] == run.K_CONST) && (run.probe_has != 3 || run.skind[s] == run.K_CONST || (run.sflag[s] & run.F_STATIC))) cp_symbol(s, nm + n + 1);
+ s++;
+ }
+ }
+ } else {
+ k = 0;
+ while (k < run.probe_n) {
+ // a local shadows a global with the same spelling.
+ j = 0; while (j < cp_n) { if (rt.eq_w(cp_name[j], run.probe_name[k])) { cp_n--; cp_name[j] = cp_name[cp_n]; cp_detail[j] = cp_detail[cp_n]; cp_docs[j] = cp_docs[cp_n]; cp_kind[j] = cp_kind[cp_n]; cp_dead[j] = cp_dead[cp_n]; cp_rank[j] = cp_rank[cp_n]; break; } j++; }
+ hn = 0; if (run.probe_type[k]) { hov_type(run.probe_type[k]); hov_put(' '); } hov_text(run.probe_name[k]);
+ if (cp_add(run.probe_name[k], 6, run.probe_state[k], 0)) {
+ hn = 0; j = run.probe_decl[k];
+ if (j >= 0) {
+ head = j;
+ while (head > 0) { c = dc(doff[i], head - 1); if (c == 10 || c == ';' || c == '{' || c == '}') break; head--; }
+ while (head < j && (dc(doff[i], head) == 32 || dc(doff[i], head) == 9)) head++;
+ hover_docs(i, head, j, rt.len_w(run.probe_name[k]), 0);
+ }
+ if (hn) cp_docs[cp_n - 1] = cp_hov();
+ }
+ k++;
+ }
+ }
+ // a partial check must never serve subsequent hovers, navigation or diagnostics.
+ chk_i = -1; ann_i = -1;
+ send(gen_completion); return 0;
+}
+
+
+// import fixes are derived from current compiler errors and qualified exports.
+// each edit is tied to the document version that was checked.
+word ca_list; int ca_i, ca_start, ca_end, ca_insert, ca_crlf;
+int ca_add(char*.d owner, int qualify) {
+ word p = ca_list;
+ while (p) { if (rt.eq_w(p[1], owner)) return 0; p = p[0]; }
+ p = rt.new(3); p[0] = ca_list; p[1] = owner; p[2] = qualify; ca_list = p;
+ return 0;
+}
+int ca_import_point(int i) {
+ int t = 0, at = 0, c;
+ fm_lex(i);
+ while (t < fm_nt) {
+ if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; }
+ if (!fm_is(i, t, "include") && !fm_is(i, t, "namespace")) break;
+ while (t < fm_nt && !(fm_k[t] == 6 && fm_o[t] == 2)) t++;
+ if (t == fm_nt) break;
+ at = fm_e[t]; t++;
+ }
+ while (at < dsize[i] && (dc(doff[i], at) == 32 || dc(doff[i], at) == 9)) at++;
+ if (at < dsize[i] && dc(doff[i], at) == 13) at++;
+ if (at < dsize[i] && dc(doff[i], at) == 10) at++;
+ ca_crlf = 0; c = 0;
+ while (c < dsize[i] && dc(doff[i], c) != 10) c++;
+ if (c > 0 && dc(doff[i], c - 1) == 13) ca_crlf = 1;
+ return at;
+}
+int ca_newline() { if (ca_crlf) hov_put(13); hov_put(10); return 0; }
+int gen_code_actions() {
+ word p = ca_list; int first = 1, imported;
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":[");
+ while (p) {
+ if (!first) wc(','); first = 0; imported = run.module_imported(p[1]);
+ hn = 0;
+ if (!imported) { hov_text("include "); hov_text(p[1]); hov_put(';'); }
+ if (p[2]) { if (!imported) hov_text(" and "); hov_text("qualify as "); hov_text(p[1]); hov_put('.'); hov_text(hname); }
+ ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
+ wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":[");
+ pc_reset(ca_i);
+ if (!imported) {
+ ws("{\"range\":{\"start\":"); wpos(ca_insert); ws(",\"end\":"); wpos(ca_insert); ws("},\"newText\":");
+ hn = 0; if (ca_insert && dc(doff[ca_i], ca_insert - 1) != 10) ca_newline();
+ hov_text("include "); hov_text(p[1]); hov_put(';'); ca_newline(); wjs(hov, hn); wc('}');
+ }
+ if (p[2]) {
+ if (!imported) wc(',');
+ ws("{\"range\":{\"start\":"); wpos(ca_start); ws(",\"end\":"); wpos(ca_end); ws("},\"newText\":");
+ hn = 0; hov_text(p[1]); hov_put('.'); hov_text(hname); wjs(hov, hn); wc('}');
+ }
+ ws("]}]}}"); p = p[0];
+ }
+ ws("]}"); return 0;
+}
+int do_code_actions(int i) {
+ int at = 0, error = -1, s, dot, k, n, base = doff[i], start, end, cursor, limit, sl = hline, sc = hchar;
+ ca_list = 0; ca_i = i;
+ ensure_checked(i);
+ hover_word(i); cursor = hcursor; limit = cursor;
+ if (ca_endline >= 0) { hline = ca_endline; hchar = ca_endchar; hover_word(i); limit = hcursor; hline = sl; hchar = sc; }
+ while (at < run.dgpos) {
+ start = dgb[at + 2]; end = start + dgb[at + 3];
+ if (dgb[at] == 1 && start >= 0 && cursor >= 0 && start <= limit && end > cursor) { error = at; break; }
+ at += 5 + dgb[at + 4];
+ }
+ if (error < 0) { send(gen_code_actions); return 0; }
+ pc_reset(i); pc_to(dgb[error + 2] + dgb[error + 3] - 1); hline = pline; hchar = pcol;
+ if (!hover_word(i)) { send(gen_code_actions); return 0; }
+ ca_start = hoff; ca_end = hoff + rt.len_w(hname);
+ ca_insert = ca_import_point(i);
+ start = ca_start;
+ while (start > 0 && (hid(dc(base, start - 1)) || dc(base, start - 1) == '.')) start--;
+ dot = start; while (dot < ca_end && dc(base, dot) != '.') dot++;
+ if (dot < ca_end) {
+ char* owner = rt.new(dot - start + 1); k = start; n = 0;
+ while (k < dot) { owner[n] = dc(base, k); n++; k++; } owner[n] = 0;
+ if (run.module_known(owner) && !run.module_imported(owner)) ca_add(d*owner, 0); else rt.free(owner);
+ } else {
+ s = 0;
+ while (s < run.nsym) {
+ if (run.skind[s] != run.K_DOC) {
+ char* symbol = run.sname[s]; dot = 0; while (symbol[dot] && symbol[dot] != '.') dot++;
+ if (symbol[dot] == '.' && rt.eq_w(symbol + dot + 1, hname)) {
+ char* owner = rt.new(dot + 1); rt.copy_w(owner, symbol, dot); owner[dot] = 0;
+ if (run.module_known(owner)) ca_add(d*owner, 1); else rt.free(owner);
+ }
+ }
+ s++;
+ }
+ }
+ send(gen_code_actions); return 0;
+}
+
+// analysis-result caching avoids reparsing for repeated requests even after another document was checked.
+// cache entries contain serialized result values, never compiler addresses that a later check can overwrite.
+int rc_method() {
+ if (rt.eq_w(mbuf, "textDocument/hover")) return 1;
+ if (rt.eq_w(mbuf, "textDocument/semanticTokens/full")) return 2;
+ if (rt.eq_w(mbuf, "textDocument/definition")) return 3;
+ if (rt.eq_w(mbuf, "textDocument/references")) return 4;
+ if (rt.eq_w(mbuf, "textDocument/completion")) return 5;
+ return 0;
+}
+int rc_lookup(int i, int method) {
+ int k = 0, slot = 0, oldest = -1; word p;
+ rc_tick++;
+ while (k < RC_LIMIT) {
+ p = rc_meta + k * RC_W;
+ if (p[0] == i + 1 && p[1] == dgen[i] && p[2] == rc_epoch && p[3] == method && p[4] == hline && p[5] == hchar && p[6] == rdecl && rc_payload[k]) {
+ p[7] = rc_tick; rc_now = k; rc_hits++; return 1;
+ }
+ if (!p[0] || oldest < 0 || p[7] < oldest) { slot = k; oldest = p[7]; if (!p[0]) oldest = 0; }
+ k++;
+ }
+ p = rc_meta + slot * RC_W;
+ p[0] = i + 1; p[1] = dgen[i]; p[2] = rc_epoch; p[3] = method;
+ p[4] = hline; p[5] = hchar; p[6] = rdecl; p[7] = rc_tick;
+ rc_payload[slot] = 0; rc_pending = slot; rc_misses++; return 0;
+}
+int rc_store(int slot, int n) {
+ int at = 0, k, run.start = -1; char* marker = ",\"result\":";
+ while (at < n && run.start < 0) {
+ k = 0; while (marker[k] && at + k < n && rc_buffer[at + k] == marker[k]) k++;
+ if (!marker[k]) run.start = at + k; else at++;
+ }
+ if (run.start < 0 || n <= run.start) return 0;
+ rc_payload[slot] = lsp_grow(0, 0, n - run.start);
+ rt.copy_w(rc_payload[slot], rc_buffer + run.start, n - run.start - 1); // omit the response envelope's final brace
+ return 0;
+}
+int gen_cached() {
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":"); ws(rc_payload[rc_now]); wc('}'); return 0;
+}
+int rc_forget(int i) {
+ int k = 0;
+ while (k < RC_LIMIT) { if (rc_meta[k * RC_W] == i + 1) { rc_meta[k * RC_W] = 0; rc_payload[k] = 0; } k++; }
+ return 0;
+}
+int gen_cache_stats() {
+ ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"hits\":"); wn(rc_hits);
+ ws(",\"misses\":"); wn(rc_misses); ws(",\"checks\":"); wn(analysis_checks); ws("}}"); return 0;
+}
+
+//: ---- dispatch ----
+//: the text just read belongs to the document named by the uri
int commit_text() {
int i;
if (!thave || !tuseen) return 0 - 1;
i = dfind(ubuf, un);
if (i < 0) i = docnew(ubuf, un);
if (i < 0) return 0 - 1;
- doff[i] = tbase; dsize[i] = tn; dver[i] = jver; dgen[i]++;
+ doff[i] = tbase; dsize[i] = tn; dver[i] = jver; dgen[i]++; rc_epoch++;
return i;
}
// handles one message: the lifecycle (initialize, shutdown, exit), changes to a
// document (which are checked and published), and the requests for semantic
// tokens and hovers.
int handle() {
- int i;
- if (eq_w(mbuf, "initialize")) { inited = 1; send(gen_init); return 0; }
- if (eq_w(mbuf, "exit")) { quit = 1; return 0; }
- if (eq_w(mbuf, "shutdown")) { shut = 1; send(gen_null); return 0; }
+ int i, method;
+ rc_pending = -1;
+ if (rt.eq_w(mbuf, "initialize")) { rc_epoch++; inited = 1; send(gen_init); return 0; }
+ if (rt.eq_w(mbuf, "exit")) { quit = 1; return 0; }
+ if (rt.eq_w(mbuf, "shutdown")) { shut = 1; send(gen_null); return 0; }
if (!inited) { if (idtype) reply_error(0 - 32002); return 0; }
- if (eq_w(mbuf, "textDocument/didOpen") || eq_w(mbuf, "textDocument/didChange") || eq_w(mbuf, "textDocument/didSave")) {
+ if (rt.eq_w(mbuf, "textDocument/didOpen") || rt.eq_w(mbuf, "textDocument/didChange") || rt.eq_w(mbuf, "textDocument/didSave")) {
+ if (rt.eq_w(mbuf, "textDocument/didSave")) { rc_epoch++; chk_i = -1; }
i = commit_text();
if (i < 0 && tuseen) i = dfind(ubuf, un);
- if (i >= 0) { check_doc(i); publish(i); }
+ if (i >= 0 && bs_is(i)) { run.dgpos = 0; publish(i); }
+ else if (i >= 0 && check_publish(i)) publish(i);
+ return 0;
+ }
+ if (rt.eq_w(mbuf, "zc/source")) {
+ bs_doc_now = bs_named(pathbuf, pathn);
+ send(gen_source);
+ return 0;
+ }
+ if (rt.eq_w(mbuf, "zc/file")) {
+ if (pathn) { vfs_set(thave); rc_epoch++; }
+ chk_i = 0 - 1; // a document that includes it has to be checked again
return 0;
}
- if (eq_w(mbuf, "textDocument/didClose")) {
+ if (rt.eq_w(mbuf, "textDocument/didClose")) {
i = dfind(ubuf, un);
if (i >= 0) {
- dgpos = 0; publish(i); // an empty list clears its markers
+ rc_forget(i); rc_epoch++;
+ run.dgpos = 0; publish(i); // an empty list clears its markers
dlive[i] = 0; dsize[i] = 0; doff[i] = 0; duri[i] = 0;
chk_i = 0 - 1;
hc_i = 0 - 1; ann_i = 0 - 1;
}
return 0;
}
- if (eq_w(mbuf, "textDocument/semanticTokens/full")) {
+ if (rt.eq_w(mbuf, "zc/cacheStats")) { send(gen_cache_stats); return 0; }
+ method = rc_method();
+ if (method) { i = dfind(ubuf, un); if (i >= 0 && rc_lookup(i, method)) { send(gen_cached); return 0; } }
+ if (rt.eq_w(mbuf, "textDocument/semanticTokens/full")) {
i = dfind(ubuf, un);
if (i < 0) { send(gen_empty); return 0; }
ensure_checked(i);
gd = i; send(gen_tokens);
return 0;
}
- if (eq_w(mbuf, "textDocument/hover")) {
+ if (rt.eq_w(mbuf, "textDocument/definition") || rt.eq_w(mbuf, "textDocument/references")) {
+ i = dfind(ubuf, un);
+ if (i < 0) { send(gen_null); return 0; }
+ do_nav(i, rt.eq_w(mbuf, "textDocument/references"));
+ return 0;
+ }
+ if (rt.eq_w(mbuf, "textDocument/formatting")) {
+ i = dfind(ubuf, un);
+ if (i < 0) { send(gen_null); return 0; }
+ do_format(i);
+ return 0;
+ }
+ if (rt.eq_w(mbuf, "textDocument/completion")) {
+ i = dfind(ubuf, un);
+ if (i < 0) { send(gen_null); return 0; }
+ do_completion(i); return 0;
+ }
+ if (rt.eq_w(mbuf, "textDocument/codeAction")) {
+ i = dfind(ubuf, un);
+ if (i < 0) { ca_list = 0; send(gen_code_actions); return 0; }
+ do_code_actions(i); return 0;
+ }
+ if (rt.eq_w(mbuf, "textDocument/hover")) {
i = dfind(ubuf, un);
if (i < 0) { send(gen_null); return 0; }
do_hover(i);
@@ -1663,24 +3351,24 @@ int handle() {
return 0;
}
-// ---- command line ----
-// a source file on stdin instead of a protocol stream: print what the
-// compiler found, one line each. with --annotate, print the file with the
-// types the compiler can see written in
-unchecked int load_stdin(int first) {
+//: ---- command line ----
+//: a source file on stdin instead of a protocol stream: print what the
+//: compiler found, one line each. with --annotate, print the file with the
+//: types the compiler can see written in
+int load_stdin(int first) {
int n = 0, c = first, cap = 4096; word nb; int k;
- tbase = new(cap);
+ tbase = rt.new(cap);
while (c >= 0) {
if ((n >> 2) + 4 >= cap) {
- nb = new(cap * 2); k = 0;
+ nb = rt.new(cap * 2); k = 0;
while (k < cap) { nb[k] = tbase[k]; k++; }
tbase = nb; cap = cap * 2;
}
if ((n & 3) == 0) tbase[n >> 2] = 0;
tbase[n >> 2] = tbase[n >> 2] | (c << ((n & 3) * 8));
n++;
- if (eof()) break;
- c = get_c() & 255;
+ if (rt.eof()) break;
+ c = rt.get_c() & 255;
}
if (n & 3) { } else tbase[n >> 2] = 0;
tbase[(n >> 2) + 1] = 0;
@@ -1694,28 +3382,28 @@ int cli(int first) {
load_stdin(first);
check_doc(0);
pc_reset(0);
- while (at < dgpos) {
+ while (at < run.dgpos) {
o = dgb[at + 2];
- if (o < 0) { put_s("line "); put_n(dgb[at + 1]); }
- else { pc_to(o); put_n(pline + 1); put_c(':'); put_n(pcol + 1); }
- if (dgb[at] == 1) put_s(": error: "); else if (dgb[at] == 2) put_s(": warning: "); else put_s(": hint: ");
- i = 0; while (i < dgb[at + 4]) { put_c(dgb[at + 5 + i]); i++; }
- put_c(10);
+ if (o < 0) { rt.put_s("line "); rt.put_n(dgb[at + 1]); }
+ else { pc_to(o); rt.put_n(pline + 1); rt.put_c(':'); rt.put_n(pcol + 1); }
+ if (dgb[at] == 1) rt.put_s(": error: "); else if (dgb[at] == 2) rt.put_s(": warning: "); else rt.put_s(": hint: ");
+ i = 0; while (i < dgb[at + 4]) { rt.put_c(dgb[at + 5 + i]); i++; }
+ rt.put_c(10);
at = at + 5 + dgb[at + 4];
}
- if (!dgpos) put_s("no errors\n");
+ if (!run.dgpos) rt.put_s("no errors\n");
return 0;
}
-// ---- annotate ----
-// zclsp --annotate < file.zc prints the file with the types the compiler can
-// see written in: locals from their initializers, parameters from what every
-// call passes them or from being used as an address, returns from what the
-// function returns (the type facts of compiler.zc). the typechecker is on for
-// it, and every edit has to leave the file with no error it didn't have, so
-// what comes out compiles wherever the input did. when a batch of edits
-// fails, the batch is cut in halves until the bad edits are found and left
-// out. run it again and the types it just wrote let the next round see more.
+//: ---- annotate ----
+//: zclsp --annotate < file.zc prints the file with the types the compiler can
+//: see written in: locals from their initializers, parameters from what every
+//: call passes them or from being used as an address, returns from what the
+//: function returns (the type facts of compiler.zc). the typechecker is on for
+//: it, and every edit has to leave the file with no error it didn't have, so
+//: what comes out compiles wherever the input did. when a batch of edits
+//: fails, the batch is cut in halves until the bad edits are found and left
+//: out. run it again and the types it just wrote let the next round see more.
int aw_start(int p) { awp = p; awn = 0; return 0; }
// appends byte c to the output being built at awp (awn bytes so far).
int aw(int c) {
@@ -1732,13 +3420,13 @@ int aw_end() {
}
// the lines of the errors the last check found
int err_lines(word dst) {
- int at = 0, n = 0, need = dgpos / 5 + 1, baseline = dst == abase;
+ int at = 0, n = 0, need = run.dgpos / 5 + 1, baseline = dst == abase;
if (need > aerrcap) {
while (need > aerrcap) { if (!aerrcap) aerrcap = 512; else aerrcap = aerrcap * 2; }
abase = lsp_grow(abase, nbase, aerrcap); aerr = lsp_grow(aerr, 0, aerrcap);
}
if (baseline) dst = abase; else dst = aerr;
- while (at < dgpos) {
+ while (at < run.dgpos) {
if (dgb[at] == 1) { dst[n] = dgb[at + 1]; n++; }
at = at + 5 + dgb[at + 4];
}
@@ -1753,10 +3441,10 @@ int in_set(int v, word set, int n) {
// the facts, in the order of the text
int sort_facts() {
int i = 1, j; word a, b; int t0, t1, t2, t3;
- while (i < nifx) {
+ while (i < run.nifx) {
j = i;
- while (j > 0 && ifx[(j - 1) * 4] > ifx[j * 4]) {
- a = ifx + (j - 1) * 4; b = ifx + j * 4;
+ while (j > 0 && run.ifx[(j - 1) * 4] > run.ifx[j * 4]) {
+ a = run.ifx + (j - 1) * 4; b = run.ifx + j * 4;
t0 = a[0]; t1 = a[1]; t2 = a[2]; t3 = a[3];
a[0] = b[0]; a[1] = b[1]; a[2] = b[2]; a[3] = b[3];
b[0] = t0; b[1] = t1; b[2] = t2; b[3] = t3;
@@ -1770,20 +3458,20 @@ int sort_facts() {
int save_facts() {
int i = 0, nt = 0; word f; int k;
sort_facts();
- if (nifx > afcap) {
- while (nifx > afcap) { if (!afcap) afcap = 4096; else afcap = afcap * 2; }
+ if (run.nifx > afcap) {
+ while (run.nifx > afcap) { if (!afcap) afcap = 4096; else afcap = afcap * 2; }
afx = lsp_grow(afx, 0, afcap * 4); amask = lsp_grow(amask, 0, afcap);
}
- if (nift > atcap) {
- while (nift > atcap) { if (!atcap) atcap = 24000; else atcap = atcap * 2; }
+ if (run.nift > atcap) {
+ while (run.nift > atcap) { if (!atcap) atcap = 24000; else atcap = atcap * 2; }
aft = lsp_grow(aft, 0, atcap);
}
anf = 0;
- while (i < nifx) {
- f = ifx + i * 4;
+ while (i < run.nifx) {
+ f = run.ifx + i * 4;
i++;
afx[anf * 4] = f[0]; afx[anf * 4 + 1] = f[1]; afx[anf * 4 + 2] = nt; afx[anf * 4 + 3] = f[3];
- k = 0; while (k < f[3]) { aft[nt + k] = ift[f[2] + k]; k++; }
+ k = 0; while (k < f[3]) { aft[nt + k] = run.ift[f[2] + k]; k++; }
nt = nt + f[3];
anf++;
}
@@ -1812,7 +3500,7 @@ int apply_masked(int dst) {
word fresh_half() {
int n = dsize[0], i = 0;
while (i < anf) { n = n + afx[i * 4 + 3]; i++; }
- ann_half = new(dwords(n));
+ ann_half = rt.new(dwords(n));
return ann_half;
}
// does the document with the masked facts applied check with no new error?
@@ -1845,11 +3533,11 @@ int try_range(int lo, int hi) {
int annotate(int first) {
int round = 0, n, i;
load_stdin(first);
- ifstrict = 1; // the typechecker runs, so a type that doesn't fit how a name is used is an error
+ run.ifstrict = 1; // the typechecker runs, so a type that doesn't fit how a name is used is an error
check_doc(0);
nbase = err_lines(abase);
while (round < AROUNDS) {
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
save_facts();
if (!anf) break;
i = 0; while (i < anf) { amask[i] = 0; i++; }
@@ -1860,23 +3548,24 @@ int annotate(int first) {
doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1;
round++;
}
- ifstrict = 0;
+ run.ifstrict = 0;
i = 0;
- while (i < dsize[0]) { put_c(dc(doff[0], i)); i++; }
+ while (i < dsize[0]) { rt.put_c(dc(doff[0], i)); i++; }
return 0;
}
// the first argument: 1 for --annotate, 2 for --types, which lists the edits
// instead of making them
-unchecked int cli_mode() {
- int n = syscall(SYS_ARGC, 0, 0, 0), len; word s; int r = 0;
+int cli_mode() {
+ int n = rt.syscall(rt.SYS_ARGC, 0, 0, 0), len; char* s; int r = 0;
if (n < 2) return 0;
- len = syscall(SYS_ARG, 1, 0, 0);
- if (len != 10 && len != 7) return 0;
- s = new(len + 1);
- syscall(SYS_ARG, 1, s, len + 1);
+ len = rt.syscall(rt.SYS_ARG, 1, 0, 0);
+ if (len != 10 && len != 7 && len != 8) return 0;
+ s = rt.new(len + 1);
+ rt.syscall(rt.SYS_ARG, 1, s, len + 1);
if (s[0] == '-' && s[1] == '-' && s[2] == 'a' && s[3] == 'n' && len == 10) r = 1;
if (s[0] == '-' && s[1] == '-' && s[2] == 't' && s[3] == 'y' && len == 7) r = 2;
- free(s);
+ if (s[0] == '-' && s[1] == '-' && s[2] == 'f' && s[3] == 'o' && len == 8) r = 3;
+ rt.free(s);
return r;
}
// zclsp --types < file.zc: one line per edit, line:column, how many characters
@@ -1884,15 +3573,15 @@ unchecked int cli_mode() {
int list_types(int first) {
int i = 0; word f; int k;
load_stdin(first);
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
sort_facts();
pc_reset(0);
- while (i < nifx) {
- f = ifx + i * 4;
+ while (i < run.nifx) {
+ f = run.ifx + i * 4;
pc_to(f[0]);
- put_n(pline + 1); put_c(':'); put_n(pcol + 1); put_c(32); put_n(f[1]); put_c(32);
- k = 0; while (k < f[3]) { put_c(ift[f[2] + k]); k++; }
- put_c(10);
+ rt.put_n(pline + 1); rt.put_c(':'); rt.put_n(pcol + 1); rt.put_c(32); rt.put_n(f[1]); rt.put_c(32);
+ k = 0; while (k < f[3]) { rt.put_c(run.ift[f[2] + k]); k++; }
+ rt.put_c(10);
i++;
}
return 0;
@@ -1903,27 +3592,32 @@ int list_types(int first) {
// --types), or the json-rpc stream of a language server (when it starts with a
// Content-Length header).
int lsp_main() {
- int c, k;
- docs_on = 0; // the image was made with docs on; the documents checked here are not docs
- chk_heap = 1;
- dgb = new(DGCAP); sets = new(4 * LSW);
+ int c, k, i;
+ run.docs_on = 0; // the image was made with docs on; the documents checked here are not docs
+ run.chk_heap = 1;
+ dgb = rt.new(DGCAP); sets = rt.new(4 * run.LSW);
duri = lsp_grow(0, 0, doccap); dulen = lsp_grow(0, 0, doccap);
doff = lsp_grow(0, 0, doccap); dsize = lsp_grow(0, 0, doccap);
dver = lsp_grow(0, 0, doccap); dgen = lsp_grow(0, 0, doccap); dlive = lsp_grow(0, 0, doccap);
mbuf = lsp_grow(0, 0, mcap); idbuf = lsp_grow(0, 0, idcap); jk = lsp_grow(0, 0, jkcap); kbuf = lsp_grow(0, 0, kcap); lbuf = lsp_grow(0, 0, lcap);
ubuf = lsp_grow(0, 0, ucap); hov = lsp_grow(0, 0, hovcap); hname = lsp_grow(0, 0, hnamecap); htb = lsp_grow(0, 0, htbc);
chk_i = 0 - 1; jpk = 0 - 2; rfirst = 0 - 1;
- if (eof()) return 0;
- c = get_c() & 255;
+ if (rt.eof()) return 0;
+ c = rt.get_c() & 255;
if (c != 'C' && c != 'c') {
k = cli_mode();
- if (k == 1) annotate(c); else if (k == 2) list_types(c); else cli(c);
+ if (k == 1) annotate(c); else if (k == 2) list_types(c); else if (k == 3) format_cli(c); else cli(c);
return 0;
}
rfirst = c;
while (!quit) {
if (!read_message()) break;
handle();
+ // a check that was dropped for newer input still has to be published once nothing newer is waiting
+ while (unpub >= 0 && !quit && rt.syscall(42, 0, 0, 0) <= 0) {
+ i = unpub; unpub = 0 - 1;
+ if (dlive[i] && check_publish(i)) publish(i);
+ }
}
return 0;
}
diff --git a/src/micro/micro.c b/src/micro/micro.c
index 4542bd8..528382a 100644
--- a/src/micro/micro.c
+++ b/src/micro/micro.c
@@ -19,6 +19,17 @@
words). a smaller board compiles this file against the headers src/tools/genmap
writes, and against the genome built for that same map. */
#include <stdint.h>
+#ifdef ZY_THREADS
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
#ifdef USE_LSP_GENOME
#include "lsp_genome.h"
#elif defined(GENOME_H)
@@ -68,22 +79,22 @@ typedef struct {
/* the machine: m is mw words, and grows on demand through the port (micro_mem_alloc).
every access goes through m and mw, which the port's memory may move under. */
-static Cell *m;
-static UCell mw;
-static UCell memory_initial = MICRO_MW_INIT, memory_limit = MICRO_MW_LIMIT;
+static VM_TLS Cell *m;
+static VM_TLS UCell mw;
+static VM_TLS UCell memory_initial = MICRO_MW_INIT, memory_limit = MICRO_MW_LIMIT;
int micro_memory_config(UCell initial, UCell limit)
{
if (m || initial < MICRO_MW_INIT || limit < initial || limit > 0x7fffffff) return 0;
memory_initial = initial; memory_limit = limit; return 1;
}
-static Xop xops[XPOOL];
-static int xuse;
-static Enh enh[NENH];
-static Xop build[MAXOPS];
-static int bn, bnv, berr;
-static Cell xv[MAXV];
-static int jflag;
-static int jknown; /* 0 until something asks for the end-of-input flag: the probe reads the port, so it waits for the first ask */
+static VM_TLS Xop xops[XPOOL];
+static VM_TLS int xuse;
+static VM_TLS Enh enh[NENH];
+static VM_TLS Xop build[MAXOPS];
+static VM_TLS int bn, bnv, berr;
+static VM_TLS Cell xv[MAXV];
+static VM_TLS int jflag;
+static VM_TLS int jknown; /* 0 until something asks for the end-of-input flag: the probe reads the port, so it waits for the first ask */
UCell micro_mem_words(void) { return mw; }
static int inmem(Cell a) { return (UCell)a < mw; }
@@ -109,7 +120,7 @@ static void b_store(Cell addr, Cell val, int n)
#define PC_PAST_END (-5)
#define PC_BAD_OP (-6)
#define PC_HALT (-7) /* the zero word: the program's halt, not an error */
-static int bad_op;
+static VM_TLS int bad_op;
/* grows the machine to at least words words, through the port. the contents stay,
and the new words are zero. 1 on success, 0 when the port has no more memory. */
@@ -482,13 +493,16 @@ static int xrun(Enh *e, int s)
/* a run suspends when the port has no input yet but more may come (streaming,
see micro_port.h): micro_getc returns MICRO_WAIT, the getc is run again on
micro_resume, and the stacks wait where they are */
-static int sus_p, sus_s, sus_r;
+static VM_TLS int sus_p, sus_s, sus_r;
+static VM_TLS int final_s;
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp);
+static int context_cancelled(void);
static int run_from(int p, int s, int r)
{
int op;
Cell w, a, b;
- while ((UCell)p < mw) {
+ while ((UCell)p < mw && !context_cancelled()) {
if (s < stack_data_limit() || r > stack_return_limit()) {
Cell sp = s, rp = r; int ret = r > stack_return_limit();
if (!stack_control || stack_expand(ret, stack_get(ret ? SC_RETURN_TOP : SC_DATA_TOP) - stack_get(ret ? SC_RETURN_LOW : SC_DATA_LOW) + 1, &sp, &rp) < 0) {
@@ -517,7 +531,16 @@ static int run_from(int p, int s, int r)
Cell limit = m[a + 2];
if (limit < 0 || (UCell)limit < mw || limit > 0x7fffffff) result = -1;
else { memory_limit = (UCell)limit; result = limit; }
- } else result = micro_syscall(m, b, m[a + 1], m[a + 2], m[a + 3]);
+ } else if (b == 41) {
+ char text[400]; int i, n = fop_format_mode((uint64_t)m[a + 1], 0, 17, text);
+ Cell dst = m[a + 2], cap = m[a + 3];
+ if (dst < 0 || cap <= n || (UCell)dst > mw || (UCell)cap > mw - (UCell)dst) result = -1;
+ else { for (i = 0; i < n; i++) m[dst + i] = (unsigned char)text[i]; m[dst + n] = 0; result = n; }
+ } else if (b == 42) {
+ /* poll: a streaming port suspends once, so its host can deliver input that is waiting */
+ result = micro_syscall(m, b, 0, 0, 0);
+ if (result == MICRO_WAIT) { sus_p = p - 1; sus_s = s; sus_r = r; return MICRO_SUSPENDED; }
+ } else result = context_call(b, m[a + 1], m[a + 2], m[a + 3], s + 2, r);
m[a] = result;
s += 2;
break;
@@ -604,6 +627,7 @@ static int run_from(int p, int s, int r)
else { bad_op = op; p = PC_BAD_OP; }
}
}
+ final_s = s;
if (p == PC_DATA_OVERFLOW) { micro_msg("zy: data stack overflow\n"); return 2; }
if (p == PC_RETURN_OVERFLOW) { micro_msg("zy: return stack overflow\n"); return 2; }
if (p == PC_PAST_END) { micro_msg("zy: the program counter is past the end of memory\n"); return 2; }
@@ -620,6 +644,124 @@ static int run_from(int p, int s, int r)
return 0;
}
+/* hosted contexts own their memory, input buffer and channel endpoints. */
+#ifdef ZY_THREADS
+extern size_t micro_context_port_size(void);
+extern void micro_context_port_save(void *);
+extern void micro_context_port_restore(const void *);
+extern void micro_context_port_free(void);
+#define CONTEXT_HANDLE_LO 0x40000000
+typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel;
+typedef struct VmContext {
+ ContextChannel parent_channel, worker_channel;
+ pthread_t thread;
+ struct VmContext *next;
+ atomic_int stop;
+ Cell token, entry, stack;
+ UCell words, limit, initial;
+ int sp, rp, exit_code, requested_exit, enhancement_count, input_flag, input_known;
+ Cell *memory;
+ void *port;
+ Xop operations[XPOOL];
+ Enh enhancements[NENH];
+} VmContext;
+static VM_TLS VmContext *context_current, *context_children;
+static VM_TLS ContextChannel *context_channels;
+static atomic_llong context_sequence;
+static atomic_int context_records_live, context_memory_live;
+static Cell context_join(Cell token, int cancel);
+static int context_cancelled(void) { return context_current && atomic_load_explicit(&context_current->stop, memory_order_acquire); }
+static int context_descriptor(Cell handle) {
+ if (handle < CONTEXT_HANDLE_LO) return handle >= 0 && handle <= 0x7fffffff ? (int)handle : -1;
+ for (ContextChannel *channel = context_channels; channel; channel = channel->next) if (channel->handle == handle) return channel->fd;
+ return -1;
+}
+static int context_close(Cell handle) {
+ ContextChannel **link = &context_channels;
+ while (*link && (*link)->handle != handle) link = &(*link)->next;
+ if (!*link) return -1;
+ ContextChannel *channel = *link; *link = channel->next;
+ if (channel->fd < 0) return -1;
+ int result = close(channel->fd); channel->fd = -1; return result;
+}
+static void context_snapshot_free(VmContext *context) {
+ free(context->port);
+ if (context->memory) { free(context->memory); atomic_fetch_sub(&context_memory_live, 1); }
+}
+static void *context_worker(void *opaque) {
+ VmContext *context = opaque;
+ context_current = context; context->worker_channel.next = context_channels; context_channels = &context->worker_channel;
+ micro_context_port_restore(context->port); free(context->port); context->port = 0;
+ memory_limit = context->limit; memory_initial = context->initial;
+ if (!mem_grow_words(context->words)) { context->exit_code = -1; context_close(context->worker_channel.handle); return 0; }
+ memcpy(m, context->memory, (size_t)context->words * sizeof(Cell)); free(context->memory); context->memory = 0;
+ stack_control = context->stack; jflag = context->input_flag; jknown = context->input_known;
+ xuse = context->enhancement_count; memcpy(xops, context->operations, sizeof xops); memcpy(enh, context->enhancements, sizeof enh);
+ int sp = context->sp, rp = context->rp; Cell entry = context->entry;
+ if (entry >= HEAP_LO && m[entry - 3] == (entry ^ 0x2B7E1516)) { m[--sp] = entry + 3; entry = m[entry]; }
+ m[rp++] = PC_HALT;
+ int result = run_from((int)entry, sp, rp);
+ if (!context->requested_exit) context->exit_code = context_cancelled() ? 0 : result ? -1 : inmem(final_s) ? (int)m[final_s] : 0;
+ context_close(context->worker_channel.handle);
+ while (context_children) context_join(context_children->token, 1);
+ micro_context_port_free(); m = 0; atomic_fetch_sub(&context_memory_live, 1); context_current = 0; return 0;
+}
+static Cell context_start(Cell entry, Cell channel, int sp, int rp) {
+ if (!inmem(entry) || !inmem(channel) || (entry >= HEAP_LO && (!inmem(entry - 3) || m[entry - 3] != (entry ^ 0x2B7E1516)))) return -1;
+ int fd[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ VmContext *context = calloc(1, sizeof *context); if (!context) { close(fd[0]); close(fd[1]); return -1; }
+ context->entry = entry; context->sp = sp; context->rp = rp; context->stack = stack_control;
+ context->words = mw; context->limit = memory_limit; context->initial = memory_initial;
+ context->input_flag = jflag; context->input_known = jknown; context->enhancement_count = xuse;
+ context->token = atomic_fetch_add(&context_sequence, 1) + 1;
+ context->parent_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1;
+ context->worker_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1;
+ context->parent_channel.fd = fd[0]; context->worker_channel.fd = fd[1]; atomic_init(&context->stop, 0);
+ context->port = malloc(micro_context_port_size()); context->memory = malloc((size_t)mw * sizeof(Cell));
+ if (context->memory) atomic_fetch_add(&context_memory_live, 1);
+ if (!context->port || !context->memory) goto failed;
+ micro_context_port_save(context->port); memcpy(context->memory, m, (size_t)mw * sizeof(Cell)); context->memory[channel] = context->worker_channel.handle;
+ memcpy(context->operations, xops, sizeof xops); memcpy(context->enhancements, enh, sizeof enh);
+ pthread_attr_t attributes; pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 16u << 20);
+ int result = pthread_create(&context->thread, &attributes, context_worker, context); pthread_attr_destroy(&attributes);
+ if (result) goto failed;
+ context->parent_channel.next = context_channels; context_channels = &context->parent_channel;
+ atomic_fetch_add(&context_records_live, 1); m[channel] = context->parent_channel.handle;
+ context->next = context_children; context_children = context; return context->token;
+failed:
+ context_snapshot_free(context); free(context); close(fd[0]); close(fd[1]); return -1;
+}
+static Cell context_join(Cell token, int cancel) {
+ VmContext **link = &context_children;
+ while (*link && (*link)->token != token) link = &(*link)->next;
+ if (!*link) return -1;
+ VmContext *context = *link;
+ if (cancel) { atomic_store_explicit(&context->stop, 1, memory_order_release); if (context->parent_channel.fd >= 0) shutdown(context->parent_channel.fd, SHUT_RDWR); }
+ if (pthread_join(context->thread, 0)) return -1;
+ if (context->parent_channel.fd >= 0) context_close(context->parent_channel.handle);
+ int result = context->exit_code; *link = context->next; context_snapshot_free(context); free(context);
+ atomic_fetch_sub(&context_records_live, 1); return result;
+}
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp) {
+ switch (op) {
+ case 51: return context_start(a, b, sp, rp);
+ case 52: return context_join(a, b != 0);
+ case 53: return getpid();
+ case 54: return (Cell)(uintptr_t)pthread_self();
+ case 55: return a == 0 ? atomic_load(&context_records_live) : a == 1 ? 0 : a == 2 ? atomic_load(&context_memory_live) : -1;
+ case 7:
+ if (context_current) { context_current->exit_code = (int)a; context_current->requested_exit = 1; atomic_store_explicit(&context_current->stop, 1, memory_order_release); return 0; }
+ break;
+ case 2: if (a >= CONTEXT_HANDLE_LO) return context_close(a); break;
+ case 44: case 45: case 46: a = context_descriptor(a); if (a < 0) return -1; break;
+ }
+ return micro_syscall(m, op, a, b, c);
+}
+#else
+static int context_cancelled(void) { return 0; }
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp) { return micro_syscall(m, op, a, b, c); }
+#endif
+
static int run(int p) { return run_from(p, DS0, RS0); }
/* goes on from a run that suspended for input (MICRO_SUSPENDED), once the port
diff --git a/src/micro/port_linux.c b/src/micro/port_linux.c
index e6c8847..9331a0b 100644
--- a/src/micro/port_linux.c
+++ b/src/micro/port_linux.c
@@ -19,6 +19,10 @@
#endif
#else
#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <signal.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
@@ -28,11 +32,16 @@
#include <time.h>
#include "micro_port.h"
#include "bops.h"
+#ifdef ZY_THREADS
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
/* the machine's memory: a block that grows by realloc, zero in the new part. the
base can move on a grow, which micro.c takes into account (it re-reads m). */
-static Cell *lmem;
-static UCell lcap;
+static VM_TLS Cell *lmem;
+static VM_TLS UCell lcap;
Cell *micro_mem_alloc(UCell words)
{
@@ -47,19 +56,24 @@ Cell *micro_mem_alloc(UCell words)
return n;
}
-int micro_getc(void)
-{
- int c = getchar();
- return c == EOF ? -1 : c;
-}
-
-int micro_eof(void)
-{
- int c = getchar();
- if (c == EOF) return 1;
- ungetc(c, stdin);
- return 0;
+#if defined(ZY_THREADS)
+static VM_TLS unsigned char input_bytes[65536];
+static VM_TLS int input_position, input_count, input_ended;
+static int input_fill(int wait) {
+ if (input_position < input_count) return 1;
+ if (input_ended) return 0;
+ if (!wait) { struct pollfd descriptor = {0, POLLIN, 0}; if (poll(&descriptor, 1, 0) <= 0) return 0; }
+ else fflush(stdout);
+ int n = (int)read(0, input_bytes, sizeof input_bytes);
+ if (n <= 0) { input_ended = 1; return 0; }
+ input_position = 0; input_count = n; return 1;
}
+int micro_getc(void) { return input_fill(1) ? input_bytes[input_position++] : -1; }
+int micro_eof(void) { input_fill(0); return input_ended; }
+#else
+int micro_getc(void) { int c = getchar(); return c == EOF ? -1 : c; }
+int micro_eof(void) { int c = getchar(); if (c == EOF) return 1; ungetc(c, stdin); return 0; }
+#endif
void micro_putc(int c) { putchar(c & 255); }
@@ -87,10 +101,10 @@ static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
#ifndef _WIN32
extern char **environ;
#endif
-static int zy_argc;
-static char **zy_argv;
-static int zy_envc;
-static char **zy_envp;
+static VM_TLS int zy_argc;
+static VM_TLS char **zy_argv;
+static VM_TLS int zy_envc;
+static VM_TLS char **zy_envp;
static void zy_args(int argc, char **argv)
{
@@ -126,7 +140,7 @@ static Cell vecstr(Cell *mem, char **v, int count, Cell a, Cell b, Cell c, const
static Cell host_rand(void)
{
- static uint64_t s;
+ static VM_TLS uint64_t s;
if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
if (!s) s = 0x9E3779B97F4A7C15ull;
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
@@ -152,6 +166,84 @@ Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
char path[1024], tmp[512];
int n, i, got;
switch (op) {
+ case 43: { /* clone an isolated execution context with a bidirectional channel */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int fd[2]; pid_t child;
+ if (!inmem(a) || socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ fflush(NULL); child = fork();
+ if (child < 0) { close(fd[0]); close(fd[1]); return -1; }
+ if (!child) { close(fd[0]); mem[a] = fd[1]; return 0; }
+ close(fd[1]); mem[a] = fd[0]; return child;
+#else
+ return -1;
+#endif
+ }
+ case 44: case 45: { /* write/read complete word buffers on a context channel */
+#if !defined(_WIN32) && !defined(__wasm__)
+ size_t done = 0, length; ssize_t got; unsigned char *buffer;
+ if (b < 0 || c < 0 || c > (Cell)micro_mem_words() - b) return -1;
+ buffer = (unsigned char *)(mem + b); length = (size_t)c * sizeof(Cell);
+ while (done < length) {
+ if (op == 44) {
+#ifdef __APPLE__
+ int enabled = 1; setsockopt((int)a, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof enabled);
+ #ifdef MSG_NOSIGNAL
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#else
+ got = send((int)a, buffer + done, length - done, 0);
+#endif
+#else
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#endif
+ } else got = read((int)a, buffer + done, length - done);
+ if (got < 0 && errno == EINTR) continue;
+ if (got <= 0) return -1;
+ done += (size_t)got;
+ }
+ return c;
+#else
+ return -1;
+#endif
+ }
+ case 46: { /* channel readiness, with a millisecond timeout */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct pollfd fd; int ready; fd.fd = (int)a; fd.events = POLLIN; fd.revents = 0;
+ do { ready = poll(&fd, 1, (int)b); } while (ready < 0 && errno == EINTR);
+ return ready > 0 ? !!(fd.revents & (POLLIN | POLLHUP)) : ready;
+#else
+ return -1;
+#endif
+ }
+ case 47: case 49: { /* join a child context, optionally terminating it first */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int status; pid_t joined;
+ if (a <= 0) return -1;
+ if (op == 49) kill((pid_t)a, SIGTERM);
+ do { joined = waitpid((pid_t)a, &status, 0); } while (joined < 0 && errno == EINTR);
+ return joined < 0 ? -1 : WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+#else
+ return -1;
+#endif
+ }
+ case 50: { /* monotonic milliseconds for elapsed-time measurements */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return -1;
+ return (Cell)now.tv_sec * 1000 + now.tv_nsec / 1000000;
+#else
+ return -1;
+#endif
+ }
+ case 48: { /* sleep for a bounded millisecond duration */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec duration;
+ if (a < 0 || a > 86400000) return -1;
+ duration.tv_sec = (time_t)(a / 1000); duration.tv_nsec = (long)(a % 1000) * 1000000;
+ while (nanosleep(&duration, &duration) < 0) if (errno != EINTR) return -1;
+ return 0;
+#else
+ return -1;
+#endif
+ }
case 1:
if (zcstr(mem, a, path, 1024) < 0) return -1;
if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
@@ -269,6 +361,21 @@ Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
int main(int argc, char **argv) {
UCell initial, limit;
+ if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
+ fputs("usage: zym [program arguments] < input\n"
+ "runs the embedded image with the portable vm; compiler images read source from stdin.\n"
+ "input after a backtick is program input; run-only images read program input directly.\n"
+ "program arguments are available through std.argv; stdin has the name <eval>.\n"
+ "options:\n --help, -h show this help and exit\n"
+ "environment:\n"
+ " TINYVM_MEMORY_WORDS initial vm memory in words\n"
+ " TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n", stdout);
+ return 0;
+ }
+ if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') {
+ fprintf(stderr, "micro: unknown option %s; use --help for usage\n", argv[1]);
+ return 2;
+ }
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
@@ -282,3 +389,25 @@ int main(int argc, char **argv) {
}
return micro_run();
}
+
+#ifdef ZY_THREADS
+struct PortSnapshot {
+ unsigned char bytes[65536];
+ int position, count, ended, argc, envc;
+ char **argv, **envp;
+};
+size_t micro_context_port_size(void) { return sizeof(struct PortSnapshot); }
+void micro_context_port_save(void *destination) {
+ struct PortSnapshot *snapshot = destination;
+ memcpy(snapshot->bytes, input_bytes, sizeof input_bytes);
+ snapshot->position = input_position; snapshot->count = input_count; snapshot->ended = input_ended;
+ snapshot->argc = zy_argc; snapshot->argv = zy_argv; snapshot->envc = zy_envc; snapshot->envp = zy_envp;
+}
+void micro_context_port_restore(const void *source) {
+ const struct PortSnapshot *snapshot = source;
+ memcpy(input_bytes, snapshot->bytes, sizeof input_bytes);
+ input_position = snapshot->position; input_count = snapshot->count; input_ended = snapshot->ended;
+ zy_argc = snapshot->argc; zy_argv = snapshot->argv; zy_envc = snapshot->envc; zy_envp = snapshot->envp;
+}
+void micro_context_port_free(void) { free(lmem); lmem = 0; lcap = 0; }
+#endif
diff --git a/src/micro/port_wasm_stream.c b/src/micro/port_wasm_stream.c
index 155cde4..d96ceab 100644
--- a/src/micro/port_wasm_stream.c
+++ b/src/micro/port_wasm_stream.c
@@ -17,7 +17,7 @@ extern void js_msg(int c);
static unsigned char chunk[CHUNK];
/* the input the server has not read yet: q[qp..qn) */
static unsigned char q[QCAP];
-static int qn, qp, closed;
+static int qn, qp, closed, polled;
unsigned char *wasm_chunk(void) { return chunk; }
int wasm_chunk_cap(void) { return (int)sizeof chunk; }
@@ -38,6 +38,10 @@ int wasm_feed(int n)
return 0;
}
+/* 1 while the server is suspended by a poll rather than by a read: the host should resume it
+ after the event loop has run, whether or not input came */
+int wasm_polling(void) { return polled; }
+
/* no more input will come: the next read past the end is -1 */
void wasm_close(void) { closed = 1; }
@@ -83,6 +87,10 @@ Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
if (a == 2) return HEAP_HI;
if (a == 3) return (Cell)micro_mem_words() * 8;
return -1;
+ case 42: /* poll: suspend once so the host can feed what has arrived, then say if input is waiting */
+ if (!polled) { polled = 1; return MICRO_WAIT; }
+ polled = 0;
+ return qp < qn;
case 7: /* exit: the run ends */
return -1;
case 21: /* argc: the server has no command line of its own, so <eval> alone */
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index 3badcc2..ee3594d 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -1,71 +1,77 @@
-// ============================================================================
-// rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers,
-// the heap (size-class free lists) and the mark-sweep collector.
-// ============================================================================
+namespace rt;
+include run;
+
+//: ============================================================================
+//: rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers,
+//: the heap (size-class free lists) and the mark-sweep collector.
+//: ============================================================================
+
+//: how the runtime is laid out, for a reader coming in cold:
+//:
+//: memory map generated constants name the regions used by a build. the
+//: compiler and runtime use the same names and values.
+//: i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two
+//: string forms (char* strings and packed byte strings).
+//: heap a buddy allocator with a 5 word header on every block.
+//: new/free/recycle are built on it. every heap object has at
+//: most one owner (the container that holds it), and freeing
+//: an owner frees what it owns.
+//: growable arrays T[?] handles, built on the heap.
+//: mark-sweep a conservative collector. it is the safety net behind
+//: free: anything the compiler's ownership analysis could not
+//: free gets reclaimed here.
+//: memory debug optional checks (use after free, double free, leaks).
+//: syscalls file and environment access through one gate word.
+//:
+//: everything is a 64-bit word, and an address is a word index, not a byte
+//: index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the
+//: word at that address with @, then outputs it with . (see the vm reference).
+//:
+//: ---- memory map (words) ----
-// how the runtime is laid out, for a reader coming in cold:
-//
-// memory map generated constants name the regions used by a build. the
-// compiler and runtime use the same names and values.
-// i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two
-// string forms (word strings and packed byte strings).
-// heap a buddy allocator with a 5 word header on every block.
-// new/free/recycle are built on it. every heap object has at
-// most one owner (the container that holds it), and freeing
-// an owner frees what it owns.
-// growable arrays []T handles, built on the heap.
-// mark-sweep a conservative collector. it is the safety net behind
-// free: anything the compiler's ownership analysis could not
-// free gets reclaimed here.
-// memory debug optional checks (use after free, double free, leaks).
-// syscalls file and environment access through one gate word.
-//
-// everything is a 64-bit word, and an address is a word index, not a byte
-// index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the
-// word at that address with @, then outputs it with . (see the vm reference).
-//
-// ---- memory map (words) ----
// src/tools/genmap writes the map values for each build between these markers.
// keep the names and order stable so programs can use the same map interface
// across targets and image sizes.
// @map-rt
-
-const word MW = 0x1C5000;
-const word CODE_END = 0x80000;
-const word HEAP_LO = 0x1C5000;
-const word HEAP_HI = 0x225000;
-const int HEAP_GROW = 0x1;
-const word ZFP = 0x90000;
-const word GORG = 0x114100;
-const word FBOT = 0x164100;
-const word FTOP = 0x1A4100;
-const word RS0 = 0x1A4100;
-const word MARK0 = 0x1B4100;
-const int MARKN = 0x8000;
-const word DSLO = 0x1BC100;
-const word DSHI = 0x1C4FF0;
-const int MAGIC = 0x2B7E1516;
+const MW = 0x1D5000;
+const CODE_END = 0x80000;
+const HEAP_LO = 0x1D5000;
+const HEAP_HI = 0x235000;
+const HEAP_GROW = 0x1;
+const ZFP = 0x90000;
+const GORG = 0x114100;
+const FBOT = 0x174100;
+const FTOP = 0x1B4100;
+const RS0 = 0x1B4100;
+const MARK0 = 0x1C4100;
+const MARKN = 0x8000;
+const DSLO = 0x1CC100;
+const DSHI = 0x1D4FF0;
+const MAGIC = 0x2B7E1516;
// @end-map
-// frames grow in stable segments so pointers to a caller's locals stay valid.
+//: frames grow in stable segments so pointers to a caller's locals stay valid.
+
word frame_floor = FBOT, frame_top = FTOP, frame_node;
int frame_chunk = FTOP - FBOT, frame_guard = (FTOP - FBOT) >> 2, frame_busy, gc_active;
-[17]word frame_control;
+word[17] frame_control;
word data_low = DSLO, data_top = DSHI, data_node;
word return_low = RS0, return_top = MARK0 - 8, return_node;
const int F_STACK = 1024;
const int F_INTERNAL = 512;
-// ---- virtual enhancements ----
-// install(desc) hands the core a descriptor through the % opcode. the core reads
-// it and wires the region it describes into the cpu, so the compiler can replace
+//: ---- virtual enhancements ----
+
+// install(desc) passes a descriptor to the core through the % opcode. the core
+// reads it and installs the region it describes in the cpu, so the compiler can replace
// a run of vm code with native code. the descriptor is laid out by the compiler
// for each enhance and o3 region (see install in compiler.zc). the enhance lines
// below are the micro forms of the same thing: a single machine instruction each.
// zdiv, zmod and zshl further down do the same arithmetic in software. this block
// comes first, since the compiler resolves / % and << to these names.
+
// install the descriptor for an optimized region.
int install(int desc) { return asm { desc @ % }; }
// signed division of a by b, as one machine instruction.
@@ -78,9 +84,11 @@ enhance ishl(a, b) micro "a = shl a b";
enhance lfa(k) micro "r1 = ld 589824; k = add r1 k";
// 1 when i is outside 0..n-1 (the unsigned compare also catches negative i), else 0.
enhance outside(i, n) micro "i = ltu i n; i = xor i 1";
-// ---- bit operations and float conversions ----
+//: ---- bit operations and float conversions ----
+
// these use single machine instructions where the cpu has them.
+
// the number of leading zero bits in a.
enhance clz(a) micro "a = clz a";
// the number of trailing zero bits of a.
@@ -106,7 +114,7 @@ enhance zf2s(a) micro "a = f2s a";
// s2f, the software form used where the cpu has none.
enhance zs2f(a) micro "a = s2f a";
-// runtime code segments retain the addresses of earlier functions and globals.
+//: runtime code segments retain the addresses of earlier functions and globals.
word code_blocks;
// distinguish a raw function address from a heap closure's environment block.
int callable_code(word f) {
@@ -116,13 +124,15 @@ int callable_code(word f) {
return 0;
}
-// ---- i/o ----
-// output can be redirected. mode 1 drops what is written and mode 2 appends
-// it to a buffer; mode 3 counts characters without storing them (the server
-// uses these modes while it checks a document,
-// so a parse word that prints can't write into the protocol stream).
-// raw_c always reaches the terminal.
-int outmode; word outbuf; int outn, outcap; word outgrow;
+//: ---- i/o ----
+
+//: output can be redirected. mode 1 drops what is written and mode 2 appends
+//: it to a buffer; mode 3 counts characters without storing them (the server
+//: uses these modes while it checks a document,
+//: so a parse word that prints can't write into the protocol stream).
+//: raw_c always reaches the terminal.
+
+int outmode; char* outbuf; int outn, outcap; word outgrow;
// write one character to the terminal with no translation.
int raw_c(int ch) { asm { ch @ . } return 0; }
// write one character. normally it goes to the terminal; while a capture is
@@ -156,34 +166,35 @@ int put_s(char* s) {
// write the decimal form of a number, with a minus sign for a negative one.
// the smallest integer is handled as its own case.
int put_n(int n) {
- [24]word b; int i = 0;
+ char[24] b; int i = 0;
if (n < 0) { put_c('-'); n = 0 - n; }
if (n < 0) { put_s("9223372036854775808"); return 0; }
while (1) { b[i++] = '0' + n % 10; n = n / 10; if (!n) break; }
while (i) put_c(b[--i]);
return 0;
}
-// the length of a word string, not counting its 0.
+// the length of a zero-terminated character string, not counting its 0.
int len_w(char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
-// true when two word strings hold the same characters.
+// true when two zero-terminated character strings hold the same characters.
int eq_w(char* a, char* b) {
int i = 0;
while (a[i] && a[i] == b[i]) i++;
return a[i] == b[i];
}
-// ---- word helpers ----
-// copy and fill by word. copy_w copies forward, so overlapping ranges are only
-// safe when dst is below src. a long run above the program area goes to the
-// host in one call (ops 18 and 19 of the syscall gate); a host that answers -1
-// (a board, the js vm, a range that overlaps itself) gets the plain loop.
+//: ---- word helpers ----
-const int SYS_FILL = 18, /* wowie! */ SYS_COPY = 19;
+//: copy and fill by word. copy_w copies forward, so overlapping ranges are only
+//: valid when dst is below src. a long run above the program area goes to the
+//: host in one call (ops 18 and 19 of the syscall gate); a host that answers -1
+//: (a board, the js vm, a range that overlaps itself) gets the plain loop.
+
+const int SYS_FILL = 18, SYS_COPY = 19;
// copy n words from src to dst.
-int copy_w(word* dst, word* src, int n) {
+unchecked int copy_w(word*.m dst, word*.m src, int n) {
int i = 0; word g = ZFP + 2;
if (n >= 16 && dst >= MW) {
g[1] = dst; g[2] = src; g[3] = n; g[0] = SYS_COPY;
@@ -193,7 +204,7 @@ int copy_w(word* dst, word* src, int n) {
return 0;
}
// set n words at dst to v.
-int fill_w(word* dst, int v, int n) {
+unchecked int fill_w(word*.m dst, int v, int n) {
int i = 0; word g = ZFP + 2;
if (n >= 16 && dst >= MW) {
g[1] = dst; g[2] = v; g[3] = n; g[0] = SYS_FILL;
@@ -210,6 +221,7 @@ int fill_w(word* dst, int v, int n) {
// and may cross a word. an access that runs off the end of memory loads 0 and
// stores nothing. a byte store never fires the syscall gate; that is a word
// store at SYSGATE. opcodes 612..617, words 1224..1234.
+
int addr_b(int w) { return w << 3; }
// the word address of a byte address b.
int addr_w(int b) { return b >> 3; }
@@ -230,36 +242,42 @@ int st32(int a, int v) { asm { v @ a @ #1234 }; return 0; }
// starting at the next word, and a 0 byte after the last character. literals
// b"..." live in the code and are not heap objects. cat_s, slice_s, n_to_s and
// new_s return a shared heap object with the same layout: many pointers may
-// hold it, free does nothing, and the collector reclaims it. word strings
+// hold it, free does nothing, and the collector reclaims it. char* strings
// (one character per word) are unchanged.
-int len_s(word s) { return s[0]; }
+
+// exposes the length header and packed storage for low-level runtime operations.
+word* bstr_storage(bstr s) { return asm { s @ }; }
+// forms a packed-string handle from storage with a valid length header.
+bstr bstr_handle(word* p) { return asm { p @ }; }
+// the number of bytes in a packed byte string.
+int len_s(bstr s) { return bstr_storage(s)[0]; }
// byte i of a packed byte string.
-int at_s(word s, int i) {
- if (i < 0 || i >= s[0]) return 0;
- return ld8(addr_b(s + 1) + i);
+int at_s(bstr s, int i) {
+ if (i < 0 || i >= len_s(s)) return 0;
+ return ld8(addr_b(bstr_storage(s) + 1) + i);
}
-// a new packed byte string of n bytes, owned by the caller.
-unchecked word new_s(int n) {
- word p = new(1 + ((n + 8) >> 3));
+// a new packed byte string of n bytes, managed by the collector.
+bstr new_s(int n) {
+ word* p = new(1 + ((n + 8) >> 3));
p[0] = n;
- return share(p);
-}
-// store byte v at position i of a word string.
-int sput(word s, int i, int v) { st8(addr_b(s + 1) + i, v); return 0; }
-// a new string with a followed by b, owned by the caller.
-unchecked word cat_s(word a, word b) {
- int n = a[0] + b[0]; word p = new_s(n); int i = 0;
- while (i < a[0]) { sput(p, i, at_s(a, i)); i++; }
+ return bstr_handle(share(p));
+}
+// store byte v at position i of a packed byte string.
+int sput(bstr s, int i, int v) { st8(addr_b(bstr_storage(s) + 1) + i, v); return 0; }
+// a new string with a followed by b, managed by the collector.
+bstr cat_s(bstr a, bstr b) {
+ int n = len_s(a) + len_s(b); bstr p = new_s(n); int i = 0;
+ while (i < len_s(a)) { sput(p, i, at_s(a, i)); i++; }
i = 0;
- while (i < b[0]) { sput(p, a[0] + i, at_s(b, i)); i++; }
+ while (i < len_s(b)) { sput(p, len_s(a) + i, at_s(b, i)); i++; }
sput(p, n, 0);
return p;
}
-// a new string with the characters from i to j of s, owned by the caller.
-unchecked word slice_s(word s, int i, int j) {
- int n; word p; int k = 0;
+// a new string with the characters from i to j of s, managed by the collector.
+bstr slice_s(bstr s, int i, int j) {
+ int n; bstr p; int k = 0;
if (i < 0) i = 0;
- if (j > s[0]) j = s[0];
+ if (j > len_s(s)) j = len_s(s);
if (j < i) j = i;
n = j - i;
p = new_s(n);
@@ -267,36 +285,36 @@ unchecked word slice_s(word s, int i, int j) {
sput(p, n, 0);
return p;
}
-// compare two word strings: negative, 0 or positive, like strcmp.
-int cmp_s(word a, word b) {
- int i = 0, n = a[0], ca, cb;
- if (b[0] < n) n = b[0];
+// compare two packed byte strings: negative, 0 or positive, like strcmp.
+int cmp_s(bstr a, bstr b) {
+ int i = 0, n = len_s(a), ca, cb;
+ if (len_s(b) < n) n = len_s(b);
while (i < n) {
ca = at_s(a, i); cb = at_s(b, i);
if (ca != cb) { if (ca < cb) return 0 - 1; return 1; }
i++;
}
- if (a[0] == b[0]) return 0;
- if (a[0] < b[0]) return 0 - 1;
+ if (len_s(a) == len_s(b)) return 0;
+ if (len_s(a) < len_s(b)) return 0 - 1;
return 1;
}
-// true when two word strings are equal.
-int eq_s(word a, word b) { return cmp_s(a, b) == 0; }
-// true when word string a sorts before b.
-int lt_s(word a, word b) { return cmp_s(a, b) < 0; }
-// true when word string a sorts before b or equals it.
-int le_s(word a, word b) { return cmp_s(a, b) <= 0; }
+// true when two packed byte strings are equal.
+int eq_s(bstr a, bstr b) { return cmp_s(a, b) == 0; }
+// true when packed byte string a sorts before b.
+int lt_s(bstr a, bstr b) { return cmp_s(a, b) < 0; }
+// true when packed byte string a sorts before b or equals it.
+int le_s(bstr a, bstr b) { return cmp_s(a, b) <= 0; }
// write a packed byte string.
-int put_bs(word s) {
+int put_bs(bstr s) {
int i = 0;
- while (i < s[0]) { put_c(at_s(s, i)); i++; }
+ while (i < len_s(s)) { put_c(at_s(s, i)); i++; }
return 0;
}
// the decimal number at the start of a packed byte string.
-int s_to_n(word s) {
+int s_to_n(bstr s) {
int n = 0, i = 0, sign = 1; word c;
- if (s[0] && at_s(s, 0) == '-') { sign = 0 - 1; i = 1; }
- while (i < s[0]) {
+ if (len_s(s) && at_s(s, 0) == '-') { sign = 0 - 1; i = 1; }
+ while (i < len_s(s)) {
c = at_s(s, i);
if (c < '0' || c > '9') break;
n = n * 10 + c - '0';
@@ -305,8 +323,8 @@ int s_to_n(word s) {
return n * sign;
}
// a new packed byte string holding the decimal form of n.
-unchecked word n_to_s(int n) {
- [24]word b; int i = 0, j = 0, neg = 0; word p, t;
+bstr n_to_s(int n) {
+ word[24] b; int i = 0, j = 0, neg = 0; bstr p; char* t;
if (!n) { p = new_s(1); sput(p, 0, '0'); sput(p, 1, 0); return p; }
if (n < 0) { neg = 1; n = 0 - n; }
if (n < 0) {
@@ -324,8 +342,9 @@ unchecked word n_to_s(int n) {
return p;
}
-// ---- arithmetic the hardware doesn't have ----
-[64]word pow2;
+//: ---- arithmetic the hardware doesn't have ----
+
+word[64] pow2;
int zrem;
// unsigned division of a by dv.
int zudiv(int n, int dv) {
@@ -361,35 +380,43 @@ int zmod(int a, int b) {
// a shifted left by b bits, for b under 64.
int zshl(int a, int b) { return a * pow2[b & 63]; }
-// ---- heap ----
-// a buddy allocator. the heap is cut into aligned power-of-two chunks, every
-// block is a power of two words, and a freed block merges with its buddy
-// (the other half of the block they were split from) whenever that one is
-// free too, so freed space can be reused at any size. every block has a 5
-// word header before the pointer the program sees:
-// p[-5] block size
-// p[-4] flags in the low 16 bits, size class in the next 5
-// 0 free (check word cleared) or quarantined (check word still p ^ MAGIC)
-// 1 live 2 releasing 3 marked 4 quarantined but a word still points at it
-// bit 8 (value 8) is orthogonal: the object is shared. free is a no-op,
-// own and release do not follow it, and the sweep still collects it when
-// nothing points at it. bits 0..2 keep the meanings above.
-// p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0)
-// p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw)
-// free blocks sit on a doubly linked list per class, linked through p[0], p[1].
-// free quarantines: the block stays intact until a sweep finds nothing pointing
-// at it. recycle is the fast path for a pointer the program has already
-// dropped: it pushes the block onto its size-class list and does not walk the
-// heap. a two-word node is still 8 words (the smallest block); a three-word
-// node fits in 8 instead of 16.
+//: ---- heap ----
+
+//: a buddy allocator. the heap is cut into aligned power-of-two chunks, every
+//: block is a power of two words, and a freed block merges with its buddy
+//: (the other half of the block they were split from) whenever that one is
+//: free too, so freed space can be reused at any size. every block has a 5
+//: word header before the pointer the program sees:
+//: p[-5] block size
+//: p[-4] flags in the low 16 bits, size class in the next 5
+//: 0 free (check word cleared) or quarantined (check word still p ^ MAGIC)
+//: 1 live 2 releasing 3 marked 4 quarantined but a word still points at it
+//: bit 8 (value 8) is orthogonal: the object is shared. free is a no-op,
+//: own and release do not follow it, and the sweep still collects it when
+//: nothing points at it. bits 0..2 keep the meanings above.
+//: p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0)
+//: p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw)
+//: free blocks sit on a doubly linked list per class, linked through p[0], p[1].
+//: free quarantines: the block stays intact until a sweep finds nothing pointing
+//: at it. recycle is the fast path for a pointer the program has already
+//: dropped: it pushes the block onto its size-class list and does not walk the
+//: heap. a two-word node is still 8 words (the smallest block); a three-word
+//: node fits in 8 instead of 16.
+
const int HDR = 5;
-const int F_SHARE = 8; // p[-4] bit, orthogonal to live / mark / releasing
-const int F_LOW = 65535; // flags live in these bits; the size class is above them
-int hbump; [32]word freel; int gc_runs, gc_freed, gc_auto = 1;
-int hstop; // block walks and new chunks stop here
-int hold_lo, hold_hi; // while nonzero, balloc will not return a block overlapping this range
-int hmax, hcut; // words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow
-int dbg, alloc_line; // memdebug bits, and the line the compiler stamps before new
+//: p[-4] bit, orthogonal to live / mark / releasing.
+const int F_SHARE = 8;
+//: flags live in these bits; the size class is above them.
+const int F_LOW = 65535;
+int hbump; word[32] freel; int gc_runs, gc_freed, gc_auto = 1;
+//: block walks and new chunks stop here.
+int hstop;
+//: while nonzero, balloc will not return a block overlapping this range.
+int hold_lo, hold_hi;
+//: words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow.
+int hmax, hcut;
+//: memdebug bits, and the line the compiler stamps before new.
+int dbg, alloc_line;
// the size of the top-level chunk holding heap offset off, within [0, hstop)
int topsize(int off) {
@@ -462,7 +489,7 @@ int heap_init() {
hold_hi = 0;
hcut = 0;
hmax = 0;
- // a map with a growing heap asks the host how much memory there is. a host
+ // a map with a growing heap queries the host for available memory. a host
// that does not answer (-1) or has no more than the initial heap gets a
// fixed heap
if (HEAP_GROW) { hmax = syscall(9, 0, 0, 0); if (hmax <= HEAP_LO) hmax = 0; }
@@ -513,7 +540,7 @@ int heap_uncut(int addr, int k) {
// take a block of 2^k words. find the smallest non-empty class at or above k,
// unlink its first block and split it down: each half that is not kept goes
// back on its own list. a block that overlaps the held range is marked
-// "releasing" and dropped from the lists for good (the loop then tries again),
+// "releasing" and dropped from the lists permanently (the loop retries),
// so the compiler's type area is never handed out as heap.
word balloc(int k) {
int j; word b; int h, ok;
@@ -536,7 +563,8 @@ word balloc(int k) {
}
return 0;
}
-// ---- a heap that grows ----
+//: ---- a heap that grows ----
+
// the heap is [HEAP_LO, hstop). on a map with HEAP_GROW the memory above
// HEAP_HI belongs to the host (a big zeroed array nobody has touched yet), and
// when the free lists cannot satisfy a request the heap takes more of it. the
@@ -544,6 +572,7 @@ word balloc(int k) {
// chunks (topsize) are right: extending hstop re-cuts them, and every new
// piece goes through bfree, which merges it with a free buddy below it. a
// block that is in use stays where it is, so no pointer moves.
+
// grow so that a block of 2^k words fits: double the heap, or less when the
// host runs out. returns 0 when it cannot.
int heap_grow(int k) {
@@ -553,7 +582,7 @@ int heap_grow(int k) {
need = (h + pow2[k] - 1) / pow2[k] * pow2[k] + pow2[k];
h2 = h + h;
if (h2 < need) h2 = need;
- if (h2 > room) { // the machine is smaller than the heap wants: the host grows it (syscall 9, 4)
+ if (h2 > room) { // the machine is smaller than the requested heap: the host grows it (syscall 9, 4)
hmax = syscall(9, 4, HEAP_LO + h2, 0);
if (hmax <= HEAP_LO) hmax = 0;
room = hmax - HEAP_LO;
@@ -584,7 +613,8 @@ int hfree() {
}
return n;
}
-int fend; // end of the block bfree last made, for the sweep
+//: end of the block bfree last made, for the sweep.
+int fend;
// put block b back, merging it with its buddy as long as it can
int bfree(word b) {
int k = bclass(b), sz = pow2[k], off; word bud;
@@ -617,13 +647,13 @@ int bdrop(word b) {
return 0;
}
// is x the payload address of a block that has been allocated (live or
-// quarantined)? the check word p[-3] == p ^ MAGIC is what makes this safe to
-// ask about any integer: a random value would have to match by chance.
+// quarantined)? the check word p[-3] == p ^ MAGIC validates the pointer before
+// checking it against block metadata; a random value would have to match by chance.
word isobj(word x) { return x >= HEAP_LO + HDR && x < hbump && x[-3] == (x ^ MAGIC) && (x[-4] & 1); }
-// the number of words to skip in p[0..n) before one that could be a pointer
-// to a block (see op 20 in core.c); n when none can be. a long payload of
-// plain numbers costs one host call instead of an isobj per word. a host that
-// does not have the op (-1) is asked once and then never again.
+//: the number of words to skip in p[0..n) before one that could be a pointer
+//: to a block (see op 20 in core.c); n when none can be. a long payload of
+//: plain numbers costs one host call instead of an isobj per word. a host that
+//: does not have the op (-1) is asked once and then never again.
const int SYS_SCAN = 20;
int scan_off;
// walk the n words at p and mark every heap object they point to.
@@ -650,10 +680,11 @@ int reserve_frames(int words) {
}
// reserve stack words without invalidating locals or their borrowed addresses.
int reserve_data(int words) { return syscall(9, 7, words, 0); }
+// grow the return-address stack to hold at least words entries.
int reserve_returns(int words) { return syscall(9, 8, words, 0); }
// the prologue restores oldfp before calling this helper. its own calls use
// the guard space below oldfp; they must not try to grow another segment.
-unchecked word frame_grow(int oldfp, int attempted) {
+word frame_grow(int oldfp, int attempted) {
int size = frame_chunk, slots = oldfp - attempted; word p;
frame_busy = 1;
if (!hstop || slots < 0 || slots > 0x7FFFFFFF - frame_guard - 4) {
@@ -671,7 +702,7 @@ unchecked word frame_grow(int oldfp, int attempted) {
}
// only the final frame in a segment calls this helper. the helper's own
// epilogue still uses that segment; the caller stores the restored pointer.
-unchecked word frame_leave() {
+word frame_leave() {
word p = frame_node; int oldfp = p[0];
frame_busy = 1;
frame_floor = p[1]; frame_top = p[2]; frame_node = p[3];
@@ -689,7 +720,7 @@ int oom() {
// n words, untyped and not zeroed. the type word keeps -(n + 1), so fatten
// can type it later. typed new goes through new_t. o3 new(n) goes through
// new_oz, which zeroes the words that were asked for.
-unchecked int .u new_raw(int n) {
+int .u new_raw(int n) {
int need = n + HDR, k = 3; word b; int p;
if (n > 0x7FFFFFFF - HDR) { oom(); return 0; }
if (n < 0) need = HDR;
@@ -716,29 +747,30 @@ unchecked int .u new_raw(int n) {
}
// n words, untyped, zeroed. the type word stays -(n + 1). new_raw does not
// zero the words that were asked for, so typed new does not zero them twice.
-unchecked int .u new_oz(int n) {
+int .u new_oz(int n) {
int p = new_raw(n);
if (n > 0) fill_w(p, 0, n);
return p;
}
// n words typed t, zeroed. memdebug records the line the compiler stored in alloc_line
-unchecked word .u new_t(int n, int t) {
+word .u new_t(int n, int t) {
word p = new_raw(n);
p[-1] = t;
fill_w(p, 0, p[-5] - HDR);
if (dbg & 1) note_alloc(p, alloc_line);
return p;
}
-unchecked word .u new(int n) { return new_t(n, 0); }
+// allocate n words without a type stamp; the caller owns the fresh object.
+word .u new(int n) { return new_t(n, 0); }
// the compiler works out the size class of a constant-size new at compile
// time (k is the class of n + HDR words), so a repeated allocation of the same
// shape pays for no class search. when the class list has a block, pop it
// directly; a free block already has the right size and an empty payload to
// refill. in a loop that frees its object every pass (an m local) this is the
-// block that was just recycled, so the pair costs about a dozen steps.
+// recycled block, so the pair costs about a dozen steps.
// memdebug, an empty list, or a block inside the held range (balloc's rule
// for the compiler's type area) fall back to the general path.
-unchecked word .u new_k(int n, int t, int k) {
+word .u new_k(int n, int t, int k) {
word b = freel[k], p, nx;
if (!b || dbg || (hold_hi && b < hold_hi && b + pow2[k] > hold_lo)) return new_t(n, t);
nx = b[5];
@@ -781,10 +813,10 @@ int release(word p) {
b[1] = b[1] & ~7; // quarantined. the check word and the bytes stay; the next sweep reclaims it
return 0;
}
-// frees a heap object: p has to be tagged d, so a call is free(d*p). the object is
-// not reused at once. it is kept in quarantine (a stale pointer still reads the old
-// contents), and the next collection reclaims it when nothing points at it. a second
-// free does nothing. a shared object is left alone.
+// releases a heap object and consumes an owned object automatically. an explicit
+// d* recast is also accepted; a borrowed reference cannot be consumed.
+// storage remains quarantined until collection finds no references to it.
+// releasing an already released or shared object has no effect.
unchecked int free(.d p) {
int n;
if (isobj(p) && (p[-4] & F_SHARE)) return 0;
@@ -821,7 +853,7 @@ unchecked int recycle(.d p) {
bdrop(b);
return 0;
}
-// the matching free for an object the compiler knows has no pointer fields
+// the matching free for an object whose type has no pointer fields
// (a typed new of a struct of numbers). it checks the type word and the size
// class still match what the compiler assumed, since the variable may have been
// reassigned; if not, or under memdebug, it takes the general route.
@@ -845,9 +877,9 @@ word own(word c, int p) {
return c;
}
// an opt-in bump arena of n words, made as one block. the handle owns that block.
-// arena_alloc hands out interior pointers into it and never asks the buddy lists,
+// arena_alloc returns interior pointers into it and does not use the buddy lists,
// and arena_reset gives all of it back at once.
-unchecked word .u arena_new(int n) {
+word .u arena_new(int n) {
word h = new(3), b; int cap = n;
if (cap < 1) cap = 1;
b = new(cap);
@@ -858,7 +890,7 @@ unchecked word .u arena_new(int n) {
}
// take n zeroed words from arena a. it returns the address, or 0 when the arena
// does not have n words left.
-unchecked int arena_alloc(word a, int n) {
+int arena_alloc(word a, int n) {
int p, at;
if (!a || n < 0) return 0;
at = a[2];
@@ -877,7 +909,7 @@ int arena_reset(word a) {
// publish p. a shared object has no owner: free does nothing to it, copies of its
// pointer are ordinary words, and the collector reclaims it once every copy is gone.
// returns p.
-unchecked word share(word p) {
+word share(word p) {
if (!isobj(p)) return p;
p[-4] = (p[-4] & ~7) | 1 | F_SHARE;
p[-2] = 0;
@@ -906,24 +938,26 @@ unchecked word triple(int a, int b, int c) {
return share(p);
}
// the first word of a pair made by cons.
-unchecked word car(word p) {
+word car(word p) {
if (dbg & 1) checkptr(p);
return p[0];
}
// the second word of a pair made by cons.
-unchecked word cdr(word p) {
+word cdr(word p) {
if (dbg & 1) checkptr(p);
return p[1];
}
// word i (0, 1 or 2) of a triple made by triple.
-unchecked word tget(word p, int i) {
+word tget(word p, int i) {
if (dbg & 1) checkptr(p);
return p[i];
}
-// ---- growable arrays ----
-// a []T is a handle: [len, cap, items, element size]. items is an object the
+//: ---- growable arrays ----
+
+// a T[?] is a handle: [len, cap, items, element size]. items is an object the
// handle owns; growing swaps it for a bigger one and moves its children over.
+
unchecked word .u dnew(int es, int n, int t) {
word h = new_t(4, t); int c = n; word it;
if (c < 4) c = 4;
@@ -949,7 +983,7 @@ unchecked int dgrow(word h, int need) {
free(old);
return 0;
}
-// the out-of-range report for []T indexing (dat, dpop)
+// the out-of-range report for T[?] indexing (dat, dpop)
int oob(int i, int n) {
put_s("zy: index ");
put_n(i);
@@ -961,7 +995,7 @@ int oob(int i, int n) {
}
// element i of a growable array h. indexing with [] does the same, with the bounds
// check. the bounds check is what oob reports when i is outside the array.
-unchecked word dat(word h, int i) {
+word dat(word h, int i) {
if (i < 0 || i >= h[0]) oob(i, h[0]);
return h[2] + i * h[3];
}
@@ -976,28 +1010,40 @@ unchecked int dpush(word h, .d x) {
}
// remove the last element of a growable array h and give it back. the array
// must not be empty.
-unchecked word dpop(word h) {
+word dpop(word h) {
+ word value;
if (!h[0]) oob(-1, 0);
h[0] = h[0] - 1;
- return h[2][h[0]];
+ value = h[2][h[0]]; h[2][h[0]] = 0;
+ if (isobj(value) && value[-2] == h[2]) own(value, 0);
+ return value;
}
// set the length of a growable array h to n. growing it fills the new elements with
// zero, and shrinking it drops the elements past n.
-unchecked int dresize(word h, int n) {
- int old = h[0] * h[3];
+int dresize(word h, int n) {
+ int old = h[0] * h[3], end, i; word value;
if (n < 0) n = 0;
dgrow(h, n);
- if (n * h[3] > old) fill_w(h[2] + old, 0, n * h[3] - old);
+ end = n * h[3];
+ if (end > old) fill_w(h[2] + old, 0, end - old);
+ else {
+ i = end;
+ while (i < old) {
+ value = h[2][i]; h[2][i] = 0;
+ if (isobj(value) && value[-2] == h[2]) free(value);
+ i++;
+ }
+ }
h[0] = n;
return 0;
}
// packed elements (h[3] is minus their size in bytes): the address is a byte address
-unchecked word datb(word h, int i) {
+word datb(word h, int i) {
if (i < 0 || i >= h[0]) oob(i, h[0]);
return addr_b(h[2]) + i * (0 - h[3]);
}
// add the byte x to the end of a growable byte array h.
-unchecked int dpushb(word h, int x) {
+int dpushb(word h, int x) {
int b = 0 - h[3], a;
dgrow(h, h[0] + 1);
a = addr_b(h[2]) + h[0] * b;
@@ -1006,7 +1052,7 @@ unchecked int dpushb(word h, int x) {
return 0;
}
// remove and return the last byte of a growable byte array h.
-unchecked int dpopb(word h) {
+int dpopb(word h) {
int b = 0 - h[3], a;
if (!h[0]) oob(-1, 0);
h[0] = h[0] - 1;
@@ -1016,7 +1062,7 @@ unchecked int dpopb(word h) {
return ld32(a);
}
// growing zeroes the new elements, including the unused tail of the last word
-unchecked int dresizeb(word h, int n) {
+int dresizeb(word h, int n) {
int b = 0 - h[3], a, e, old = (h[0] * b + 7) >> 3, nw;
if (n < 0) n = 0;
if (n > h[0]) {
@@ -1036,15 +1082,17 @@ int dlen(word h) { return h[0]; }
// the capacity of a growable array: how many elements it can hold before it grows.
int dcap(word h) { return h[1]; }
-// ---- mark-sweep ----
-// conservative and non-moving. roots: the program area (globals live
-// between functions), the genome's globals, the compiler's input stack,
-// the live part of the frame stack and the live part of the data stack.
-// opcode 619 pushes that index. a word below it is not a root.
+//: ---- mark-sweep ----
+
+//: conservative and non-moving. roots: the program area (globals live
+//: between functions), the genome's globals, the compiler's input stack,
+//: the live part of the frame stack and the live part of the data stack.
+//: opcode 619 pushes that index. a word below it is not a root.
+
int ms_top, ms_over, gc_lo, gc_hi;
word ms_buf = MARK0; int markcap = MARKN;
// mark work must grow without starting another collection inside this one.
-unchecked int reserve_mark(int need) {
+int reserve_mark(int need) {
int cap = markcap, auto = gc_auto; word p;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
@@ -1098,15 +1146,15 @@ int gc_frames() {
while (p) { mark(p); markrange(p[0], p[2]); p = p[3]; }
return 0;
}
-// gc_roots is defined in the compiler half (it knows where the compiler's
+// gc_roots is defined in the compiler half (it identifies the compiler's
// own tables live); it calls markrange over every root range
-int gc_roots();
+int run.gc_roots();
// runs a collection: the blocks nothing points at are reclaimed, quarantined blocks
// included, and the number of blocks freed is returned. cycles are reclaimed too.
unchecked int gc() {
word b = 0; int sz = 0, n = 0; // a leftover pointer here would be a root and pin a block
ms_top = 0; ms_over = 0; gc_active = 1;
- gc_roots();
+ run.gc_roots();
drain();
while (ms_over) { // the mark stack overflowed: rescan marked objects
ms_over = 0;
@@ -1162,15 +1210,17 @@ int scrub() {
return 0;
}
-// ---- memory debug ----
-// dbg bits: 1 header checks and allocation sites, 2 poison the payload on free
-// (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined
-// block (the mark walks back at most 64 words to a real header). off unless
-// a program asks. sites are p ^ MAGIC so the collector doesn't see them.
+//: ---- memory debug ----
+
+//: dbg bits: 1 header checks and allocation sites, 2 poison the payload on free
+//: (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined
+//: block (the mark walks back at most 64 words to a real header). off unless
+//: a program requests. sites are p ^ MAGIC so the collector does not treat them as pointers.
+
const int NSITE = 64;
const int POISON = 57005;
int site_drop;
-[64]word site_p; [64]word site_line;
+word[64] site_p; word[64] site_line;
// true when p is a freed block still held in quarantine.
word quar(word p) { return p >= HEAP_LO + HDR && p < hbump && p[-3] == (p ^ MAGIC) && !(p[-4] & 1); }
// the object whose payload contains w, or 0. exact pointers and interiors.
@@ -1248,48 +1298,54 @@ int memreport() {
return n;
}
-// ---- host syscalls ----
-// a store of the op number to SYSGATE runs it. the three words after the gate
-// are the arguments, and the store leaves the result in the gate.
-// 1 open(path, 0 read | 1 write-create) 2 close(fd)
-// 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word
-// 5 getenv(name, buf, cap) 6 time() unix seconds
-// 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap)
-// 40 raw(block) any linux syscall: the block holds the number and six arguments.
-// the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc
+//: ---- host syscalls ----
+
+//: a store of the op number to SYSGATE runs it. the three words after the gate
+//: are the arguments, and the store leaves the result in the gate.
+//: 1 open(path, 0 read | 1 write-create) 2 close(fd)
+//: 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word
+//: 5 getenv(name, buf, cap) 6 time() unix seconds
+//: 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap)
+//: 40 raw(block) any linux syscall: the block holds the number and six arguments.
+//: the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc
+
const int SYSGATE = ZFP + 2;
const int SYS_OPEN = 1, SYS_CLOSE = 2, SYS_READ = 3, SYS_WRITE = 4, SYS_GETENV = 5, SYS_TIME = 6;
+/* terminates the host process with the exit status supplied in argument a. */
+const int SYS_EXIT = 7;
const int SYS_BOPEN = 10, SYS_BREAD = 11, SYS_BWRITE = 12, SYS_BGETENV = 13;
const int SYS_RAW = 40;
-// the command line and the environment. argv holds the program path, which is
-// <eval> when the source came from stdin, so argc is 1 even with no arguments.
-// the string ops take an index and copy one entry across, one character per
-// word like SYS_GETENV: a zero buffer asks for the length alone, so the caller
-// can size the string before it fetches it. src/lib/std.zc builds the arrays.
+//: the command line and the environment. argv holds the program path, which is
+//: <eval> when the source came from stdin, so argc is 1 even with no arguments.
+//: the string ops take an index and copy one entry across, one character per
+//: word like SYS_GETENV: a zero buffer requests the length alone, so the caller
+//: can size the string before it fetches it. src/lib/std.zc builds the arrays.
const int SYS_ARGC = 21, SYS_ARG = 22, SYS_ENVC = 23, SYS_ENV = 24;
// byte-string paths. the address is the first byte (addr_b of the word after
-// the length), not the string object. word-string syscalls are unchanged.
-int bopen(word path, int mode) { return syscall(SYS_BOPEN, addr_b(path + 1), mode, 0); }
+// the length), not the string object. char* string syscalls are unchanged.
+int bopen(bstr path, int mode) { return syscall(SYS_BOPEN, addr_b(bstr_storage(path) + 1), mode, 0); }
// read up to n bytes from fd into the packed byte string s.
-int bread(int fd, word s, int n) { return syscall(SYS_BREAD, fd, addr_b(s + 1), n); }
+int bread(int fd, bstr s, int n) { return syscall(SYS_BREAD, fd, addr_b(bstr_storage(s) + 1), n); }
// write the first n bytes of the packed byte string s to fd.
-int bwrite(int fd, word s, int n) { return syscall(SYS_BWRITE, fd, addr_b(s + 1), n); }
+int bwrite(int fd, bstr s, int n) { return syscall(SYS_BWRITE, fd, addr_b(bstr_storage(s) + 1), n); }
// copy the value of the environment variable name into dst, returning its length.
-int bgetenv(word name, word dst, int n) { return syscall(SYS_BGETENV, addr_b(name + 1), addr_b(dst + 1), n); }
-// ---- c abi ----
-// ops 14..17 reach native code (see the c abi section of core.c and extern in
-// compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte
-// string), cload(path) opens a library so its symbols resolve, and
-// cbmake(entry, sig) is a native function pointer that runs the zc function at
-// entry (cfunc(f) in the compiler builds the signature). a native pointer
-// from cbmake only works while a native call is in progress, and not on the
-// arm64 jit, where it is 0.
+int bgetenv(bstr name, bstr dst, int n) { return syscall(SYS_BGETENV, addr_b(bstr_storage(name) + 1), addr_b(bstr_storage(dst) + 1), n); }
+//: ---- c abi ----
+
+//: ops 14..17 reach native code (see the c abi section of core.c and extern in
+//: compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte
+//: string), cload(path) opens a library so its symbols resolve, and
+//: cbmake(entry, sig) is a native function pointer that runs the zc function at
+//: entry (cfunc(f) in the compiler builds the signature). a native pointer
+//: from cbmake only works while a native call is in progress, and not on the
+//: arm64 jit, where it is 0.
+
const int SYS_CCALL = 14, SYS_CFIND = 15, SYS_CBMAKE = 16, SYS_CLOAD = 17;
// the address of the native symbol name in the loaded libraries, or 0 when it is not
// loaded. see cload.
-int cfind(word name) { return syscall(SYS_CFIND, addr_b(name + 1), 0, 0); }
+int cfind(bstr name) { return syscall(SYS_CFIND, addr_b(bstr_storage(name) + 1), 0, 0); }
// loads the shared library at path, so the symbols it defines can be found with cfind.
-int cload(word path) { return syscall(SYS_CLOAD, addr_b(path + 1), 0, 0); }
+int cload(bstr path) { return syscall(SYS_CLOAD, addr_b(bstr_storage(path) + 1), 0, 0); }
// makes a native function pointer that runs the vm entry with the signature sig,
// so c code can call a zc function through it.
int cbmake(int entry, int sig) { return syscall(SYS_CBMAKE, entry, sig, 0); }
@@ -1301,9 +1357,9 @@ int syscall(int op, word a, word b, word c) {
g[1] = a; g[2] = b; g[3] = c; g[0] = op;
return g[0];
}
-// the whole file as a fresh string, or 0 if it can't be opened
-unchecked word .u readfile(int path) {
- int fd; word buf; int n = 0, got, cap; word nb;
+// reads a file into an owned character string; returns null if opening fails.
+char* .u readfile(char* path) {
+ int fd; char* buf; int n = 0, got, cap; char* nb;
fd = syscall(SYS_OPEN, path, 0, 0);
if (fd < 0) return 0;
cap = 256;
diff --git a/src/runtime/run.zc b/src/runtime/run.zc
index 60193a7..a8f82e2 100644
--- a/src/runtime/run.zc
+++ b/src/runtime/run.zc
@@ -1,27 +1,50 @@
+namespace run;
+include rt;
+
// run-only image: the runtime, a collector root set, and start. there is no
// resident compiler and none of its tables. put this between rt.zc and a
-// program that defines main, and compile with zc0 (the program has to stay in
-// the stage-0 subset: no types, no quote, no parse words).
+// program that defines main. zc0 accepts the bootstrap subset; build.zc uses
+// the self-hosted compiler for typed programs and library receiver methods.
//
// cat build/run/rt.zc src/runtime/run.zc examples/hello.zc | ./zc0 >build/run/genome.h
// the program's entry point, written by the program (see start).
+namespace;
int main();
+namespace run;
+include rt;
// the collector's roots: the code and globals, the program's frames and the
// operand stack. the collector marks from these before it frees anything.
int gc_roots() {
- markrange(0, CODE_END);
- markrange(GORG, GORG[6]);
- gc_frames();
- markrange(dsp(), data_top);
+ rt.markrange(0, rt.CODE_END);
+ rt.markrange(rt.GORG, rt.GORG[6]);
+ rt.gc_frames();
+ rt.markrange(rt.dsp(), rt.data_top);
+ return 0;
+}
+
+// install enhancement descriptors from the image's symbol table before any
+// runtime arithmetic uses their opcodes. kind 6 is an enhancement record.
+int install_enhancements() {
+ word p = rt.GORG[5], name;
+ while (*p) {
+ if (p[0] == 6 && rt.install(p[2] >> 8) < 0) {
+ rt.put_s("run: cannot install image enhancement\n");
+ rt.syscall(rt.SYS_EXIT, 2, 0, 0);
+ }
+ name = p + 3;
+ while (*name) name++;
+ p = name + 1;
+ }
return 0;
}
-// the first code a run-only image executes: it sets up the heap, runs main and halts.
+// the first code a run-only image executes: initialize the runtime, run main and halt.
int start() {
- heap_init();
+ install_enhancements();
+ rt.heap_init();
main();
- halt();
+ rt.halt();
return 0;
}
diff --git a/src/tools/automap.sh b/src/tools/automap.sh
index 8cefe6a..69849ca 100755
--- a/src/tools/automap.sh
+++ b/src/tools/automap.sh
@@ -13,10 +13,16 @@
# zc0 reads the map at build time, so it is built again for each layout.
# GENMAP can name a genmap that is already built.
set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE...' 'builds a genome and memory map sized to the supplied source files.' 'output: OUTDIR/genome.h and OUTDIR/memmap.h' 'environment: CC selects the c compiler; DOCS=1 retains documentation; GENMAP selects genmap.'; exit 0 ;;
+ -*) echo "automap: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
+[[ $# -ge 2 ]] || { echo 'usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE...' >&2; exit 2; }
P=$1; O=$2; shift 2
[ -n "$P" ] && [ -n "$O" ] || { echo "usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE..." >&2; exit 2; }
CC=${CC:-gcc}
+bash src/tools/embed.sh all --check || bash src/tools/embed.sh all
mkdir -p "$O"
ZC0=$O/.zc0
if [ -z "$GENMAP" ]; then
@@ -29,14 +35,14 @@ lay() { # $1 = genome slot, or empty for the preset's own
$CC -O1 -w -fwrapv src/tools/zc0.c -I"$O/map" -Isrc/core -o "$ZC0"
}
build() {
- if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$ZC0"
+ if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" src/core/core.zc "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" src/core/core.zc "$@"; fi | "$ZC0"
}
# with DOCS=1 the genome is made again with zc0 built for docs (ZC0DOCS), so the
# image carries the doc comments of the sources and the program can answer
# docofs(). the image is the same code either way; the docs are stored after it.
docs() {
$CC -O1 -w -fwrapv -DZC0DOCS src/tools/zc0.c -I"$O/map" -Isrc/core -o "$O/.zc0docs"
- if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$O/.zc0docs"
+ if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" src/core/core.zc "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" src/core/core.zc "$@"; fi | "$O/.zc0docs"
}
len() { grep -m1 '#define GLEN' "$1" | awk '{print $3}'; }
lay ""
diff --git a/src/tools/build.zc b/src/tools/build.zc
index 547b70f..b0e3da0 100644
--- a/src/tools/build.zc
+++ b/src/tools/build.zc
@@ -1,13 +1,18 @@
+namespace;
+include rt, run;
+
// compiles the rest of stdin (after the backtick) as a genome image at GORG
// and writes it to stdout as little-endian 64-bit words.
+
+
docs on;
// writes the compiled genome image to stdout as little-endian 64-bit words.
int main() {
- int n = build_image_grow(GORG, 4096), i = 0, w;
+ int n = run.build_image_grow(rt.GORG, 4096), i = 0, w;
while (i < n) {
- w = image_words[i];
- put_c(w); put_c(w >> 8); put_c(w >> 16); put_c(w >> 24);
- put_c(w >> 32); put_c(w >> 40); put_c(w >> 48); put_c(w >> 56);
+ w = run.image_words[i];
+ rt.put_c(w); rt.put_c(w >> 8); rt.put_c(w >> 16); rt.put_c(w >> 24);
+ rt.put_c(w >> 32); rt.put_c(w >> 40); rt.put_c(w >> 48); rt.put_c(w >> 56);
i++;
}
return 0;
diff --git a/src/tools/embed.sh b/src/tools/embed.sh
new file mode 100755
index 0000000..59a377f
--- /dev/null
+++ b/src/tools/embed.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+# embeds source text in the compiler, so the editor can show a built-in's definition and `include std;` needs
+# no file. a text becomes a function that returns it in pieces of at most 3000 bytes (the bootstrap
+# compiler's token buffer is 4 KB), one byte string per piece:
+# bstr NAME_part(int i) { if (i == 0) return b"..."; if (i == 1) return b"..."; ... return 0; }
+#
+# src/tools/embed.sh all embeds src/lib modules in the compiler
+# src/tools/embed.sh all --check exits 1 when an embedded module is out of date
+# src/tools/embed.sh lsp OUT writes OUT: rt_part, compiler_part and server_part, for the language server's image
+set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/tools/embed.sh std|icon|all [--check]' ' src/tools/embed.sh lsp OUT' 'embeds library sources in the compiler, or writes the lsp source providers to OUT.' 'options:' ' --check report stale embedded library sources without changing files' ' --help, -h show this help and exit'; exit 0 ;;
+ -*) echo "embed: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
+cd "$(dirname "$0")/../.."
+export LC_ALL=C
+# parts NAME FILE: the NAME_part function for FILE
+parts() {
+ echo "bstr $1_part(int i) {"
+ awk -v LIMIT=3000 '
+ function flush() { if (cnt > 0) { printf("\tif (i == %d) return b\"%s\";\n", n, buf); n++; buf = ""; cnt = 0 } }
+ {
+ line = $0; sub(/\r$/, "", line); line = line "\n"
+ len = length(line)
+ for (j = 1; j <= len; j++) {
+ if (cnt >= LIMIT) flush()
+ c = substr(line, j, 1)
+ if (c == "\\") e = "\\\\"; else if (c == "\"") e = "\\\""; else if (c == "\t") e = "\\t"; else if (c == "\n") e = "\\n"; else e = c
+ buf = buf e; cnt++
+ }
+ }
+ END { flush() }' "$2"
+ echo " return 0;"
+ echo "}"
+}
+embed() {
+ name=$1; file=$2; check=$3; C=src/compiler/compiler.zc; T=$(mktemp -d)
+ BEGIN_MARK="// ---- embedded $name library (generated by src/tools/embed.sh from $file; do not edit) ----"
+ END_MARK="// ---- end embedded $name library ----"
+ if grep -qF "$BEGIN_MARK" "$C"; then
+ awk -v b="$BEGIN_MARK" '$0 == b { exit } { print }' "$C" > "$T/head"
+ awk -v e="$END_MARK" 'found { print } $0 == e { found = 1; next }' "$C" > "$T/tail"
+ else
+ cp "$C" "$T/head"
+ : > "$T/tail"
+ fi
+ {
+ cat "$T/head"
+ echo "$BEGIN_MARK"
+ parts "$name" "$file"
+ echo "$END_MARK"
+ cat "$T/tail"
+ } > "$T/new"
+ if [ "$check" = "--check" ]; then
+ cmp -s "$T/new" "$C" && result=0 || result=1
+ rm -rf "$T"
+ return "$result"
+ fi
+ cp "$T/new" "$C"
+ rm -rf "$T"
+}
+case "$1" in
+std)
+ embed std src/lib/std.zc "$2"
+ ;;
+icon)
+ embed icon src/lib/icon.zc "$2"
+ ;;
+all)
+ if [ "$2" = "--check" ]; then
+ embed std src/lib/std.zc --check && embed icon src/lib/icon.zc --check
+ else
+ embed std src/lib/std.zc
+ embed icon src/lib/icon.zc
+ fi
+ ;;
+lsp)
+ [ -n "$2" ] || { echo "usage: src/tools/embed.sh lsp OUT" >&2; exit 2; }
+ {
+ echo "// the sources of this image, embedded (generated by src/tools/embed.sh; do not edit)"
+ parts rt src/runtime/rt.zc
+ parts compiler src/compiler/compiler.zc
+ parts server src/lsp/server.zc
+ parts core src/core/core.zc
+ } > "$2"
+ ;;
+*)
+ echo "usage: src/tools/embed.sh all [--check] | std | icon | lsp OUT" >&2; exit 2;;
+esac
diff --git a/src/tools/genmap.c b/src/tools/genmap.c
index dc1971c..8a258e1 100644
--- a/src/tools/genmap.c
+++ b/src/tools/genmap.c
@@ -10,7 +10,7 @@
./genmap --preset run -o build/run
./genmap --preset default --check
- ./genmap --mode compiler --heap-lo 8192 --heap 20480 --types 8192 --genome 245760 --frame 2048 --return 512 --mark 512 --data 3072 --snap-cap 1000 --quote-cap 300 -o build/small
+ ./genmap --mode compiler --heap-lo 8192 --heap 20480 --types 8192 --genome 393216 --frame 2048 --return 512 --mark 512 --data 3072 --snap-cap 1000 --quote-cap 300 -o build/small
./genmap --mode run --words 20480 -o build/run
--preset default puts the code area, the compiler tables, the genome and
@@ -194,7 +194,7 @@ static void preset_small(Spec *s)
s->compiler = 1;
s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000;
s->heap_lo = 8192; s->heap = 20480; s->types = 8192;
- s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072;
+ s->genome = 393216; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072;
tables_tight(s);
s->snap_cap = 1000; s->quote_cap = 300; s->snap = 1000; s->qbuf = 900; s->type_formula = 8192;
}
@@ -215,7 +215,7 @@ static void preset_default(Spec *s)
s->compiler = 1;
s->heap_top = 1; s->code_end = 0x80000; s->types = 65536;
s->heap = 0x60000; s->total = 0x8000000;
- s->genome = 0x50000; s->frame = 0x40000; s->ret = 0x10000; s->mark = 0x8000;
+ s->genome = 0x60000; s->frame = 0x40000; s->ret = 0x10000; s->mark = 0x8000;
s->data = 0x7FF0;
tables_big(s);
}
@@ -250,7 +250,7 @@ static void mode_defaults(Spec *s, int compiler)
if (compiler) {
s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000;
s->heap_lo = 8192; s->heap = 20480; s->types = 8192; s->type_formula = 8192;
- s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072;
+ s->genome = 393216; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072;
tables_tight(s);
} else {
s->heap_top = 1; s->code_end = 512; s->total = 0x8000000;
@@ -682,11 +682,12 @@ static void write_sources(const char *dir, const Spec *s, const Map *m)
static void usage_status(int status)
{
- fprintf(stderr,
+ fprintf(status ? stderr : stdout,
"usage: genmap --preset small|run|default|fixed|lsp [-o DIR] [--check]\n"
" genmap --mode compiler|run [-o DIR] [--words N] [options]\n"
" genmap small|run DIR\n"
"options:\n"
+ " --help, -h show this help and exit\n"
" --code N --total N --heap N --heap-lo N --types N --zfp-pad N --genome N\n"
" --frame N --return N --mark N --data N\n"
" --hash N --pool N --symbols N --locals N --fwd N\n"
@@ -714,7 +715,7 @@ static int take(int argc, char **argv, int *i)
int main(int argc, char **argv)
{
Spec s; Map m;
- if (argc == 2 && !strcmp(argv[1], "--help")) usage_status(0);
+ if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) usage_status(0);
const char *outdir = 0, *preset = 0, *mode = 0;
int check = 0, grow = 0, i, have = 0;
int total = 0, set_total = 0, set_words = 0, set_heap = 0, set_heap_lo = 0, set_types = 0, set_zfp = 0;
diff --git a/src/tools/mkgenome.c b/src/tools/mkgenome.c
index bb2e787..3bf1533 100644
--- a/src/tools/mkgenome.c
+++ b/src/tools/mkgenome.c
@@ -2,13 +2,22 @@
src/tools/build.zc) into genome.h */
#include <stdio.h>
#include <stdint.h>
+#include <string.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include "memmap.h"
-int main(void)
+int main(int argc, char **argv)
{
+ if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
+ fputs("usage: mkgenome [--help|-h] < image.bin > genome.h\n"
+ "converts little-endian 64-bit image words into a c genome header.\n"
+ "the image must match the memory layout selected when mkgenome is built.\n"
+ "options:\n --help, -h show this help and exit\n", stdout);
+ return 0;
+ }
+ if (argc != 1) { fputs("mkgenome: unexpected arguments; use --help for usage\n", stderr); return 2; }
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
diff --git a/src/tools/mklsp.sh b/src/tools/mklsp.sh
index 5edf14a..9bab550 100755
--- a/src/tools/mklsp.sh
+++ b/src/tools/mklsp.sh
@@ -4,11 +4,19 @@
# src/lsp/server.zc. its memory map is sized to that program by src/tools/automap.sh,
# so the genome slot is as big as it needs to be and no bigger.
set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/tools/mklsp.sh [OUTDIR]' 'builds the native language server in OUTDIR/zclsp; defaults to build/lsp.' 'environment: CC selects the c compiler.'; exit 0 ;;
+ -*) echo "mklsp: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
O=${1:-build/lsp}; CC=${CC:-gcc}
# DOCS=1: the image carries the doc comments, so hover can show what a name is for
-DOCS=1 bash src/tools/automap.sh lsp "$O" src/lsp/server.zc >/dev/null
+mkdir -p "$O"
+bash src/tools/embed.sh all --check || bash src/tools/embed.sh all
+# the image carries its own sources (for go to definition into a built-in)
+bash src/tools/embed.sh lsp "$O/embed.zc"
+DOCS=1 bash src/tools/automap.sh lsp "$O" src/lsp/server.zc "$O/embed.zc" >/dev/null
rm -rf "$O/src"; mkdir -p "$O/src"
cp src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$O/memmap.h" "$O/src/"
cp "$O/genome.h" "$O/src/lsp_genome.h"
-$CC -O2 -w -fwrapv -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME "$O/src/core.c" "$O/src/io_linux.c" -I"$O/src" -o "$O/zclsp"
+$CC -O2 -w -fwrapv -pthread -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME "$O/src/core.c" "$O/src/io_linux.c" -I"$O/src" -o "$O/zclsp"
diff --git a/src/tools/mklspwasm.sh b/src/tools/mklspwasm.sh
index 56ba43b..86a902e 100755
--- a/src/tools/mklspwasm.sh
+++ b/src/tools/mklspwasm.sh
@@ -6,6 +6,10 @@
# needs clang with the wasm32 target and wasm-ld; run src/tools/mklsp.sh first.
# src/tools/mklspwasm.sh [outfile.wasm]
set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/tools/mklspwasm.sh [OUTFILE.wasm]' 'builds the wasm language server; defaults to editors/vscode/server/lsp.wasm.' 'requires clang with wasm32 support and wasm-ld.' 'environment: LSP_BUILD_DIR selects the native lsp build; LSP_REUSE_BUILD=1 reuses its image.'; exit 0 ;;
+ -*) echo "mklspwasm: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
OUT=${1:-editors/vscode/server/lsp.wasm}
LSP_BUILD_DIR=${LSP_BUILD_DIR:-build/lsp}
@@ -14,7 +18,7 @@ if [ "${LSP_REUSE_BUILD:-0}" != 1 ]; then bash src/tools/mklsp.sh "$LSP_BUILD_DI
clang --target=wasm32 -O2 -w -fwrapv -nostdlib -fno-builtin -mbulk-memory -DJTYPE=int -DUSE_LSP_GENOME \
-I"$LSP_BUILD_DIR/src" -Isrc/micro src/micro/micro.c src/micro/port_wasm_stream.c \
-Wl,--no-entry -Wl,--export=wasm_chunk -Wl,--export=wasm_chunk_cap -Wl,--export=wasm_feed \
- -Wl,--export=wasm_close -Wl,--export=wasm_start -Wl,--export=wasm_resume \
+ -Wl,--export=wasm_polling -Wl,--export=wasm_close -Wl,--export=wasm_start -Wl,--export=wasm_resume \
-Wl,--export=memory -Wl,--allow-undefined -Wl,--initial-memory=41943040 -Wl,-z,stack-size=65536 \
-o "$OUT"
ls -l "$OUT"
diff --git a/src/tools/mkvsix.sh b/src/tools/mkvsix.sh
index 5ca8ef5..39fb9c4 100755
--- a/src/tools/mkvsix.sh
+++ b/src/tools/mkvsix.sh
@@ -6,6 +6,10 @@
# server build (src/tools/mklsp.sh) runs first if build/lsp is missing.
# src/tools/mkvsix.sh [outfile.vsix]
set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/tools/mkvsix.sh [OUTFILE.vsix]' 'builds and packages the vscode extension; defaults to build/zc-language-support.vsix.' 'requires node, tsc and vsce; includes the wasm server when clang supports wasm32.' 'environment: VSCE selects the extension packager.'; exit 0 ;;
+ -*) echo "mkvsix: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
OUT=${1:-$PWD/build/zc-language-support.vsix}
case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac
diff --git a/src/tools/zc0.c b/src/tools/zc0.c
index 5ef5737..446557b 100644
--- a/src/tools/zc0.c
+++ b/src/tools/zc0.c
@@ -27,6 +27,7 @@ enum { O_Q, O_LD, O_ST, O_PICK, O_DROP, O_SWAP, O_CALL, O_JMP, O_JZ, O_RET,
O_IN, O_EMPTY, O_OUT, O_ADD, O_SUB, O_MUL, O_AND, O_OR, O_XOR, O_SHR, O_LT };
static const char *mn = "q@!$_~cjzr,?.+-*&|^><%";
+static char module_name[64];
static char *src; static int pos, line = 1, tstart, hdr_start;
/* the signature of the function being defined: its header's source text, for the language server (ZC0DOCS) */
static char sgtext[400], sgname[80];
@@ -78,6 +79,7 @@ static void doccomment(void) /* pos is on the first slash */
while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--;
bol = k < 0 || src[k] == '\n';
pos += 2;
+ if (src[pos] == ':') { dn = 0; dlast = cl; docpending = 0; while (src[pos] && src[pos] != '\n') pos++; return; }
if (!bol) { while (src[pos] && src[pos] != '\n') pos++; return; }
if (dn && dlast != cl - 1) dn = 0;
if (src[pos] == ' ') pos++;
@@ -176,6 +178,11 @@ static void next(void)
while (src[pos] && src[pos] != '"' && i < 4095) ts[i++] = (char)esc();
ts[i] = 0; if (src[pos] == '"') pos++; tk = T_BSTR; return;
}
+ if (src[pos] == '.' && (!strcmp(ts,"rt") || !strcmp(ts,"run") || !strcmp(ts,"core") || !strcmp(ts,"lsp") || !strcmp(ts,"std") || !strcmp(ts,"icon"))) {
+ ts[i++] = src[pos++];
+ while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++];
+ ts[i] = 0;
+ }
tk = T_ID; return;
}
if (src[pos] == '\'') { pos++; tv = esc(); if (src[pos++] != '\'') die("bad char literal"); tk = T_NUM; strcpy(ts, "'c'"); return; }
@@ -198,10 +205,24 @@ typedef struct { char name[64]; int kind, sig, defd; int64_t val; } Sym;
static Sym sy[16384]; static int nsy;
typedef struct { int at, sym; } Fix;
static Fix fx[65536]; static int nfx;
-static Sym *find(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; }
+static Sym *find_raw(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; }
+static void qualify(char *dst, size_t size, const char *name) {
+ if (!module_name[0] || strchr(name, '.')) snprintf(dst, size, "%s", name);
+ else if (!strncmp(name, "doc ", 4) || !strncmp(name, "sig ", 4)) snprintf(dst, size, "%.4s%s.%s", name, module_name, name + 4);
+ else snprintf(dst, size, "%s.%s", module_name, name);
+}
+static Sym *find(const char *name) {
+ char full[256]; Sym *s;
+ qualify(full, sizeof full, name); s = find_raw(full); if (s) return s;
+ s = find_raw(name); if (s || strchr(name, '.')) return s;
+ const char *modules[] = { "rt", "run", "core", "lsp", 0 };
+ for (int i = 0; modules[i]; i++) { snprintf(full,sizeof full,"%s.%s",modules[i],name); s=find_raw(full); if(s) return s; }
+ return 0;
+}
static Sym *add(const char *n, int kind, int64_t val)
{
- Sym *s = find(n);
+ char full[256]; qualify(full, sizeof full, n); n = full;
+ Sym *s = find_raw(n);
if (s && s->kind == K_FN && kind == K_FN && !s->defd) { s->val = val; return s; } /* prototype or forward use */
s = &sy[nsy++]; strncpy(s->name, n, 63); s->kind = kind; s->val = val; s->defd = 1; s->sig = 0; return s;
}
@@ -318,11 +339,11 @@ static int64_t cexpr(void)
/* ---- types ----
zc0 has no types: a type is read and thrown away. all it has to know is the
- shape, so that [n]T still reserves n words and the rest reserve one. the
+ shape, so that T[n] still reserves n words and the rest reserve one. the
grammar is the compiler's own (see ptype in compiler.zc):
ptype a type, then any number of (params): int(int, int) is a function type
ptype0 a type with its stars: int*, vec<T>*, point**
- ptype1 a base: [n]T, []T, fixed []T, fn, a name, or a generic instance
+ ptype1 a base: T[n], T[?], fixed T[?], fn, a name, or a generic instance
each returns the words a variable of the type takes: n for a fixed array, 1
for a growable handle or anything scalar. */
static int ptype0(void);
@@ -330,12 +351,7 @@ static int ptype(void);
static int ptype1(void)
{
int n, e;
- if (accept("[")) {
- if (accept("]")) { ptype0(); return 1; } /* []T is a handle */
- n = (int)cexpr(); expect("]");
- e = ptype0();
- return n > 0 ? n * e : e;
- }
+ if (is("[")) { die("array types are postfix: T[N], T[] or T[?]"); return 1; }
if (is("fixed")) { next(); return ptype0(); }
if (is("fn")) { next(); return 1; } /* fn(int): the parameters follow, in ptype */
if (tk != T_ID) { die("a type name"); return 1; }
@@ -350,14 +366,30 @@ static int ptype1(void)
static int ptype0(void)
{
int n = ptype1();
- while (accept("*")) n = 1;
+ while (is("*") || is("[")) {
+ if (accept("*")) n = 1;
+ else {
+ int count = 0, cap = 8, *dims = malloc(cap * sizeof(int));
+ if (!dims) die("cannot allocate array dimensions");
+ while (accept("[")) {
+ int length;
+ if (accept("?")) { expect("]"); length = -2; }
+ else if (accept("]")) length = -1;
+ else { length = (int)cexpr(); expect("]"); }
+ if (count == cap) { cap *= 2; int *more = realloc(dims, cap * sizeof(int)); if (!more) die("cannot grow array dimensions"); dims = more; }
+ dims[count++] = length;
+ }
+ while (count) { int length = dims[--count]; if (length == -2) n = 1; else n = length < 0 ? 0 : n * length; }
+ free(dims);
+ }
+ }
return n;
}
static int ptype(void)
{
int n = ptype0();
while (accept("(")) {
- if (!accept(")")) { do { ptype(); } while (accept(",")); expect(")"); }
+ if (!accept(")")) { do { ptype(); if (accept(".")) ident(); } while (accept(",")); expect(")"); }
n = 1;
}
return n;
@@ -435,6 +467,7 @@ static void bstrlit(void)
}
static int returns_bstr(const char *n)
{
+ if (!strncmp(n, "rt.", 3)) n += 3;
return !strcmp(n, "cat_s") || !strcmp(n, "slice_s") || !strcmp(n, "n_to_s") || !strcmp(n, "new_s");
}
static int args(void) { int n = 0; expect("("); if (!accept(")")) { do { expr(); rval(); n++; } while (accept(",")); expect(")"); } return n; }
@@ -457,6 +490,13 @@ static void primary(void)
if (s && s->kind == K_GARR) { lit(s->val); return; }
if (is("(")) {
int na = args();
+ if (s && s->kind == K_ENH) {
+ if (na != (s->sig & 15)) die("wrong number of enhancement inputs");
+ op(22 + (int)s->val);
+ if (((s->sig >> 4) & 15) == 0) lit(0);
+ else if (((s->sig >> 4) & 15) != 1) die("multiple enhancement outputs need asm");
+ xs = 0; xf = 0; return;
+ }
if (!strcmp(n, "put_s") && na == 1 && xs) fnref("put_bs");
else if (!strcmp(n, "len")) fnref("len_s");
else fnref(n);
@@ -671,7 +711,7 @@ static void fndef(int ret)
char *n = ident(); Sym *s; int np = 0, tags = 0, t, frame, i, helper, busy, done, check, node;
if (dsn && !dnm[0]) strcpy(dnm, n);
char pn[16][64];
- s = find(n);
+ char full_name[256]; qualify(full_name, sizeof full_name, n); s = find_raw(full_name);
if (!s || s->kind != K_FN) { s = add(n, K_FN, 0); s->defd = 0; }
expect("(");
if (!accept(")")) {
@@ -705,7 +745,7 @@ static void fndef(int ret)
nlo = 0; nslot = 0; nepi = 0; infn = 1;
for (i = 0; i < np; i++) { strcpy(lo[nlo].name, pn[i]); lo[nlo].arr = 0; lo[nlo].flt = 0; lo[nlo].str = 0; lo[nlo++].slot = nslot++; }
/* retain the caller's pointer until the frame has its final segment. */
- helper = !find("frame_busy") || !strcmp(s->name, "frame_grow") || !strcmp(s->name, "frame_leave");
+ helper = !find("frame_busy") || !strcmp(s->name, "rt.frame_grow") || !strcmp(s->name, "rt.frame_leave");
lit(ZFP); op(O_LD); if (!helper) { lit(0); op(O_PICK); }
frame = here; lit(0); op(O_SUB); lit(ZFP); op(O_ST);
if (!helper) {
@@ -756,6 +796,12 @@ static void enhdef(void)
}
static void top1(void)
{
+ if (accept("namespace")) {
+ if (is(";")) module_name[0] = 0;
+ else { snprintf(module_name, sizeof module_name, "%s", ident()); }
+ expect(";"); return;
+ }
+ if (accept("include")) { do { ident(); } while (accept(",")); expect(";"); return; }
{
if (accept("unchecked")) { top1(); return; }
if (accept("fn")) { fndef(0); return; }
@@ -790,6 +836,21 @@ static void top1(void)
}
}
+/* section headings organize the source; they do not document a symbol. */
+static int doc_section(const char *text, int length)
+{
+ int start = 0, end, a, b;
+ while (start < length) {
+ end = start; while (end < length && text[end] != '\n') end++;
+ a = start; b = end;
+ while (a < b && isspace((unsigned char)text[a])) a++;
+ while (b > a && isspace((unsigned char)text[b - 1])) b--;
+ if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1;
+ start = end + 1;
+ }
+ return 0;
+}
+
/* one item, with the doc comment above it kept under the name it declares */
static void top(void)
{
@@ -801,7 +862,7 @@ static void top(void)
if (dn && dline == line) { memcpy(dsnap, dbuf, dn); dsn = dn; dline = 0; }
top1();
#ifdef ZC0DOCS /* enabled for images that carry editor documentation */
- if (dsn && dnm[0]) {
+ if (dsn && dnm[0] && !doc_section(dsnap, dsn)) {
int j = here, a, i = 0, k, c; uint64_t w; char nm[80];
lit(0); op(O_JMP); a = here;
emit(dsn);
@@ -834,8 +895,16 @@ static void top(void)
}
}
-int main(void)
+int main(int argc, char **argv)
{
+ if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
+ fputs("usage: zc0 [--help|-h] < source.zc > genome.h\n"
+ "compiles bootstrap source from stdin into a c genome header on stdout.\n"
+ "source must define start(); memory layout is selected when zc0 is built.\n"
+ "options:\n --help, -h show this help and exit\n", stdout);
+ return 0;
+ }
+ if (argc != 1) { fputs("zc0: unexpected arguments; use --help for usage\n", stderr); return 2; }
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
@@ -856,7 +925,7 @@ int main(void)
}
buf[n] = 0; src = buf; next();
/* image header: set the frame pointer, jump to start, then the symbol blob pointer and image end */
- lit(FTOP); lit(ZFP); op(O_ST); fnref("start"); op(O_JMP); emit(0); emit(0);
+ lit(FTOP); lit(ZFP); op(O_ST); fnref(strstr(src, "namespace run;") ? "run.start" : "start"); op(O_JMP); emit(0); emit(0);
top();
for (i = 0; i < nfx; i++) {
Sym *s = &sy[fx[i].sym];
diff --git a/src/tools/zcc b/src/tools/zcc
index 19904ea..3fb8f0c 100755
--- a/src/tools/zcc
+++ b/src/tools/zcc
@@ -22,7 +22,7 @@ while [ $# -gt 0 ]; do
case "$1" in
-o) [ $# -ge 2 ] || { echo "zcc: -o needs a name" >&2; exit 2; }; out="$2"; shift 2 ;;
-k) keep=1; shift ;;
- -h|--help) sed -n '2,15p' "$0"; exit 0 ;;
+ -h|--help) printf '%s\n' 'usage: src/tools/zcc [-o OUT] [-k] FILE.zc' 'compiles a source file into a standalone binary; links statically where supported.' 'options:' ' -o OUT output binary path; defaults to the source basename without .zc' ' -k keep the temporary build directory' ' --help, -h show this help and exit' 'environment: CC selects the c compiler; CFLAGS adds compiler flags.'; exit 0 ;;
-*) echo "zcc: unknown option $1" >&2; exit 2 ;;
*) break ;;
esac
@@ -49,6 +49,6 @@ if ! printf 'int main(void){return 0;}\n' | $CC -x c -static -o "$work/probe" -
static=""
echo "zcc: this toolchain cannot link -static; the binary is dynamic" >&2
fi
-$CC -O2 -w -fwrapv ${CFLAGS:-} $static "$work/run/micro.c" "$work/run/port_linux.c" \
+$CC -O2 -w -fwrapv -pthread -DZY_THREADS ${CFLAGS:-} $static "$work/run/micro.c" "$work/run/port_linux.c" \
-I"$work/run" -o "$out"
echo "zcc: wrote $out ($(wc -c < "$out" | tr -d ' ') bytes)"
diff --git a/src/web/build.sh b/src/web/build.sh
index 48c784e..7443f46 100755
--- a/src/web/build.sh
+++ b/src/web/build.sh
@@ -3,6 +3,10 @@
# WebAssembly. needs clang with the wasm32 target and wasm-ld. the page
# (src/web/index.html) loads it next to src/web/zc.js.
set -e
+case "${1:-}" in
+ --help|-h) printf '%s\n' 'usage: src/web/build.sh [OUTFILE.wasm]' 'builds the browser compiler vm; defaults to src/web/zc.wasm.' 'requires clang with wasm32 support and wasm-ld.'; exit 0 ;;
+ -*) echo "web build: unknown option $1; use --help for usage" >&2; exit 2 ;;
+esac
cd "$(dirname "$0")/../.."
clang --target=wasm32 -O2 -w -fwrapv -nostdlib -fno-builtin -mbulk-memory -DJTYPE=int \
-Isrc/core -Isrc/micro src/micro/micro.c src/web/port_wasm.c \