aboutsummaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 10:06:27 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 10:06:27 +0000
commitc2270cc944424579e4577e67ed17a66512d856b4 (patch)
tree04fc1ede53e0cbf2c2562f724fd539836c450306 /src/compiler
parenttodo: tick the spec; list the bugs its appendix b found (diff)
parentsplit compiler.zc into files by phase (diff)
Merge the compiler split into phase files
also: ca_owners in the language server keeps the owner order in a fixed array instead of a heap list the analyzer rejected.
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/analyzer.zc1236
-rw-r--r--src/compiler/base.zc1384
-rw-r--r--src/compiler/builtins.zc1423
-rw-r--r--src/compiler/check.zc115
-rw-r--r--src/compiler/compiler.zc10458
-rw-r--r--src/compiler/decl.zc1413
-rw-r--r--src/compiler/expr.zc1912
-rw-r--r--src/compiler/image.zc95
-rw-r--r--src/compiler/lex.zc652
-rw-r--r--src/compiler/ops.zc218
-rw-r--r--src/compiler/parts12
-rw-r--r--src/compiler/setup.zc191
-rw-r--r--src/compiler/stmt.zc816
-rw-r--r--src/compiler/types.zc1003
14 files changed, 10470 insertions, 10458 deletions
diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc
new file mode 100644
index 0000000..505ac09
--- /dev/null
+++ b/src/compiler/analyzer.zc
@@ -0,0 +1,1236 @@
+// ---- 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;
+}
+
+// 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(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) { 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;
+ 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) { rt.copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } }
+ return 0;
+}
+int af_warning(int place) {
+ int sv;
+ if (dry || panic || chkabort || stubgen || !live) return 0;
+ sv = tpack(); tunpack(place);
+ if (cmode) dg_open(2);
+ 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 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;
+ 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) {
+ 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;
+ 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) {
+ if (f && !dry && live) { f[1] = 2; f[4] = place; }
+ return 0;
+}
+int af_close(int save) {
+ int i = save; word f;
+ if (dry) return 0;
+ while (i < nloc) {
+ f = laaddr[i]; if (f && f[2] == 1) af_end(f, tpack());
+ f = lafact[i];
+ if (f && fchk && lstate[i] == S_M && lown[i] && !local_captured[i]) af_end(f, tpack());
+ i++;
+ }
+ return 0;
+}
+// the analyzer's model of a local. locals are numbered in declaration order
+// and every fact about one lives in a parallel array indexed by that number
+// (see the data region). for ownership the ones that matter are:
+// lstate what the local holds right now:
+// S_PLAIN nothing tracked (a number, or untracked memory)
+// S_M owns an object; the compiler frees it when the block ends
+// S_U owns one that has to leave the block (return it, make
+// it the block's value, or assign it to the enclosing block)
+// S_D was handed in to be given away, and hasn't been yet
+// S_DEAD the object is gone (given away); any use is an error
+// S_CHILD the object belongs to a container, lowner says which local
+// S_BORROW a .m parameter: usable but not freeable or storable
+// lhome the block depth that owns the object (fdepth where it was declared)
+// lown 1 once the local has owned an object, lused the line of its last use
+// ltag the tag written on the declaration, plus the flag bits (float,
+// byte string, o3 register, hint bits)
+// fchk says the function being compiled is analyzed at all (unchecked turns
+// it off) and live says the code just emitted is reachable. ownership rules
+// return when fchk is off; definite storage warnings also inspect unchecked code.
+// findloc looks a name up among the current function's locals, innermost first.
+// name heads and shadow links keep declaration and use lookup independent of
+// the number of earlier locals. dropping a scope restores the previous heads.
+int local_bucket(int n, int cap) { return ((n ^ (n >> 16)) * 0x9E37) & (cap - 1); }
+int local_key(int n) {
+ int k = local_bucket(n, lkeycap);
+ while (lkeys[k] && lkeys[k] != n) k = (k + 1) & (lkeycap - 1);
+ return k;
+}
+int local_bind(int n, int i) {
+ int k, j, at, cap; word keys, heads;
+ if (lkeyn + lkeyn >= lkeycap) {
+ keys = lkeys; heads = lheads; cap = lkeycap * 2;
+ lkeys = chk_resize(0, 0, cap); lheads = chk_resize(0, 0, cap);
+ j = 0;
+ while (j < lkeycap) {
+ if (keys[j]) { at = local_bucket(keys[j], cap); while (lkeys[at]) at = (at + 1) & (cap - 1); lkeys[at] = keys[j]; lheads[at] = heads[j]; }
+ j++;
+ }
+ lkeycap = cap;
+ }
+ k = local_key(n);
+ if (!lkeys[k]) { lkeys[k] = n; lheads[k] = -1; lkeyn++; }
+ lprev[i] = lheads[k]; lheads[k] = i; return 0;
+}
+int local_drop(int save) {
+ int i = nloc, k;
+ while (i > save) { i--; k = local_key(lname[i]); lheads[k] = lprev[i]; }
+ nloc = save; return 0;
+}
+int findloc(int n) {
+ int k = local_key(n), i;
+ if (!lkeys[k]) return -1;
+ i = lheads[k];
+ while (i >= nloc) i = lprev[i];
+ if (i >= lbase) return i;
+ return -1;
+}
+// declare a local: take the next slot in the frame (fslots) and reset every
+// per-local fact so a reused index never inherits the old variable's state
+int newloc(int n) {
+ int i = nloc;
+ 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;
+ larr[i] = 0;
+ lown[i] = 0;
+ ltype[i] = tword;
+ 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.
+// the table is dropped as scopes close, so a reused slot does not keep the old fact.
+word getlo(int i) {
+ int k = 0;
+ while (k < nfact) {
+ if (floc[k] == i) return flo[k];
+ k++;
+ }
+ return -1;
+}
+// the length of the recorded fact for line i, or -1 when there is none.
+word getln(int i) {
+ int k = 0;
+ while (k < nfact) {
+ if (floc[k] == i) return flen[k];
+ k++;
+ }
+ return -1;
+}
+// record the range lo to lo+ln for line i.
+int putfact(int i, int lo, int ln) {
+ int k = 0;
+ while (k < nfact) { if (floc[k] == i) { flo[k] = lo; flen[k] = ln; return 0; } k++; }
+ if (lo < 0 && ln < 0) return 0;
+ if (nfact >= factcap) {
+ floc = chk_resize(floc, nfact, factcap * 2); flo = chk_resize(flo, nfact, factcap * 2); flen = chk_resize(flen, nfact, factcap * 2);
+ factcap = factcap * 2;
+ }
+ floc[nfact] = i; flo[nfact] = lo; flen[nfact] = ln; nfact++;
+ return 0;
+}
+// drop the recorded facts from line from onward.
+int fdrop(int from) {
+ int k = 0, w = 0;
+ while (k < nfact) {
+ if (floc[k] < from) {
+ if (w != k) { floc[w] = floc[k]; flo[w] = flo[k]; flen[w] = flen[k]; }
+ w++;
+ }
+ k++;
+ }
+ nfact = w;
+ return 0;
+}
+// a loop fact stores three full local indices: the
+// induction variable, its bound and the array the bound is the length of. a
+// loop "for (i = 0; i < len(a); i++)" records (i, bound, a) in lpfact, which
+// is how proven() knows that a[i] needs no bounds check inside it.
+int pfact(int iv, int bd, int ar) { word p = talloc(3); p[0] = iv; p[1] = bd; p[2] = ar; return p; }
+// the induction variable of a loop proof.
+int p_iv(int w) { return w[0]; }
+// the array's bound in a loop proof.
+int p_bd(int w) { return w[1]; }
+// the array a loop proof refers to.
+int p_ar(int w) { return w[2]; }
+// a store to the induction variable, the bound, or the array ends the proof
+int bpoison(int i) {
+ int k = lsp, w;
+ while (k > loopbase) {
+ k--;
+ w = lpfact[k];
+ if (w >= 0 && (p_iv(w) == i || p_bd(w) == i || p_ar(w) == i)) lpfact[k] = -1;
+ }
+ return 0;
+}
+// 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;
+ putfact(i, lo, ln);
+ bpoison(i);
+ return 0;
+}
+// i >= 0 and i < len(a), proved by the loop test and a step of +1
+int proven(int a, int ix) {
+ int k = lsp, w;
+ if (ltype[a][0] != TY_DYN || !ltype[a][3] || tbytes(ltype[a][4])) return 0;
+ while (k > loopbase) {
+ k--;
+ w = lpfact[k];
+ if (w >= 0 && p_iv(w) == ix && p_ar(w) == a) return 1;
+ }
+ return 0;
+}
+// follow child links up to the local that really owns the object
+int lroot(int i) {
+ while (lstate[i] == S_CHILD) i = lowner[i];
+ return i;
+}
+// lend a local owner to global s for the rest of the current block.
+int lend_add(int s, int i) {
+ if (nlend * 3 + 3 > lendcap) { if (!lendcap) lendcap = 48; else lendcap = lendcap * 2; lendrec = chk_resize(lendrec, nlend * 3, lendcap); }
+ lendrec[nlend * 3] = s; lendrec[nlend * 3 + 1] = i; lendrec[nlend * 3 + 2] = fdepth; nlend++;
+ return 0;
+}
+// remove entry k, keeping the others in order.
+int lend_remove(int k) {
+ if (k + 1 < nlend) rt.copy_w(lendrec + k * 3, lendrec + k * 3 + 3, (nlend - k - 1) * 3);
+ nlend--;
+ return 0;
+}
+// an assignment to global s in the block that lent it ends the lend.
+int lend_clear(int s) {
+ int k = nlend;
+ while (k > 0) { k--; if (lendrec[k * 3] == s && lendrec[k * 3 + 2] == fdepth && lendrec[k * 3 + 1] >= lbase) lend_remove(k); }
+ return 0;
+}
+// report local i still lent to the global of entry k.
+int lend_err(int k, char* msg) {
+ int i = lendrec[k * 3 + 1];
+ if (!fchk || !live || panic || dry) return 0;
+ errat(); rt.put_s(lname[i]); rt.put_s(": is lent to global "); rt.put_s(sname[lendrec[k * 3]]); rt.put_s(msg); errend();
+ return 0;
+}
+// local i is about to be given away or released: no global may still borrow it.
+int lend_check(int i) {
+ int k = 0;
+ while (k < nlend) {
+ if (lendrec[k * 3 + 1] == i && i >= lbase) { lend_err(k, "; assign the global again before giving the local away"); lend_remove(k); }
+ else k++;
+ }
+ return 0;
+}
+// leaving every block at depth dep or deeper: a lend made there must have ended.
+int lend_exit(int dep) {
+ int k = 0;
+ while (k < nlend) {
+ if (lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) lend_err(k, "; assign the global again before the block that lent it ends");
+ k++;
+ }
+ return 0;
+}
+// forget the lends made at depth dep or deeper, or by locals from index from onward.
+int lend_drop(int dep, int from) {
+ int k = 0;
+ while (k < nlend) {
+ if ((lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) || lendrec[k * 3 + 1] >= from) lend_remove(k);
+ else k++;
+ }
+ return 0;
+}
+// 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);
+ lend_check(i);
+ 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 reference count for diagnostics.
+int luse(int i) {
+ lused[i] = tline;
+ if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1;
+ return 0;
+}
+// touch records a use of a tracked local and rejects dead objects. a d object
+// read in an inner block moves there, where the block owns it as m.
+int touch(int i) {
+ int r;
+ luse(i);
+ ltag[i] = ltag[i] | LREAD;
+ if (!fchk) return 0;
+ r = lroot(i);
+ luse(r);
+ if (lstate[r] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; }
+ // a d object that shows up in a deeper block is given to that block
+ if (lstate[r] == S_D && lhome[r] < fdepth) { lhome[r] = fdepth; lstate[r] = S_M; lown[r] = 1; }
+ return 0;
+}
+// an expression of class c used as a plain value (a lend, an operand). a new
+// object can't be used that way, since nothing would own it.
+int plainuse(int c) {
+ if (c == C_FRESH) cerr("a new object can't be used as a plain value; bind it to a name first");
+ if (c >= C_VAR) touch(c - C_VAR);
+ return C_PLAIN;
+}
+// the size class of an n word object, for a constant-size new
+int szclass(int n) {
+ int k = 3;
+ 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) {
+ 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]));
+ callname(s_recycle_leaf);
+ }
+ else callname(s_recycle); op(O_DROP); lit(0); laddr(lslot[i]); op(O_ST); return 0;
+}
+// leaving every block deeper than or equal to dep: free what is owned, and
+// complain about what should have left already. final means the block really
+// ends here rather than an early exit.
+int exit_vars(int dep, int final) {
+ int i = nloc, st;
+ if (!fchk || !live) return 0;
+ lend_exit(dep);
+ while (i > lbase) {
+ i--;
+ if (lhome[i] < dep) continue;
+ st = lstate[i];
+ if (st == S_M) {
+ 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)");
+ }
+ else if (st == S_D) cerrn(i, "d object was never given away");
+ }
+ return 0;
+}
+// an m object has to be used somewhere in the block that owns it, on any path
+// block ends.
+int unused_check(int from) {
+ while (from < nloc) {
+ 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, 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;
+}
+// report the locals declared from token from onward that were never used.
+int local_hints(int from) {
+ int i = from, f;
+ if (!cmode || dry || panic || chkabort || stubgen) return 0;
+ while (i < nloc) {
+ f = ltag[i];
+ 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] && !rt.eq_w(lname[i], "_")) { // _ discards its value
+ if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL);
+ else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) 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++;
+ }
+ return 0;
+}
+// block_close runs the analyzer's end-of-block work in order: hints for the
+// block's variables, o3 registers go back to the pool, an m object that was
+// never used is an error, exit_vars frees what is owned and complains about
+// what should have left, aliases whose owner dies are marked dead, then the
+// block's locals are dropped. unused_check comes first because an unused m
+// object is an error, not a free.
+int block_close(int save) {
+ int i = save, k;
+ af_close(save);
+ local_hints(save);
+ if (o3on) {
+ while (i < nloc) {
+ k = (ltag[i] >> 8) & 7;
+ if ((ltag[i] & 16) && k && no3free < 8) {
+ if (getflt(i)) k = 0 - k;
+ o3free[no3free] = k;
+ no3free++;
+ }
+ i++;
+ }
+ }
+ i = lbase;
+ unused_check(save);
+ exit_vars(fdepth, 1);
+ lend_drop(fdepth, save);
+ alias_end(fdepth, save);
+ while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; }
+ local_drop(save);
+ fdrop(save);
+ td_end(fdepth);
+ return 0;
+}
+
+// an alias in an outer block whose owner is about to disappear dies with it
+int alias_end(int dep, int save) {
+ int i, r;
+ if (!fchk) return 0;
+ i = lbase;
+ while (i < save) {
+ if (isalias(i)) {
+ r = lroot(i);
+ if (r >= save || lhome[r] >= dep || lstate[r] == S_DEAD) lstate[i] = S_DEAD;
+ }
+ i++;
+ }
+ return 0;
+}
+// snapshots: [lbase] [nloc] then state, owner, home per local
+// save the states of all current locals; returns the snapshot offset
+int snap_push() {
+ int b = snaptop, i = lbase; word p;
+ af_forget();
+ reserve_R_SNAP(b + 2 + 3 * (nloc - lbase));
+ p = R_SNAP + b;
+ p[0] = lbase; p[1] = nloc;
+ p = p + 2;
+ while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; }
+ snaptop = p - R_SNAP;
+ return b;
+}
+// drop the snapshot at base b.
+int snap_pop(int b) { snaptop = b; return 0; }
+// restore the local states saved in snapshot b.
+int snap_restore(int b) {
+ word p = R_SNAP + b; int i = p[0], e = p[1];
+ af_forget();
+ if (e > nloc) e = nloc;
+ p = p + 2;
+ while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; }
+ return 0;
+}
+// true for a plain or dead local state, which merge to dead.
+int empty(int s) { return s == S_PLAIN || s == S_DEAD; }
+// compare the current states with a snapshot: -1 if they agree, else the
+// first local that differs. plain and dead agree and merge to dead.
+int snap_cmp(int b) {
+ word p = R_SNAP + b; int i = p[0], e = p[1], s;
+ if (e > nloc) e = nloc;
+ p = p + 2;
+ while (i < e) {
+ s = lstate[i];
+ if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; }
+ else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i;
+ p = p + 3; i++;
+ }
+ return -1;
+}
+// the state names used in join and loop messages
+int statename(int s) {
+ if (s == S_PLAIN) return "empty";
+ if (s == S_M) return "m";
+ if (s == S_U) return "u";
+ if (s == S_D) return "d";
+ if (s == S_DEAD) return "given away";
+ if (s == S_CHILD) return "owned by a container";
+ return "borrowed";
+}
+// 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(); 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.
+
+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.
+word jst(word p, int i) {
+ if (i >= p[1]) return S_PLAIN;
+ return p[2 + 3 * (i - p[0])];
+}
+// the owner recorded in a jump's state for local i.
+word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; }
+// the jump's marker for local i, in the same record as jow.
+word jhm(word p, int i) { return p[4 + 3 * (i - p[0])]; }
+// the states at a break, with the jump to patch: [site] then a snapshot
+word exit_rec(int site) {
+ word p; int i = lbase; word q;
+ reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop;
+ p[0] = site; p[1] = lbase; p[2] = nloc;
+ q = p + 3;
+ while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; q = q + 3; i++; }
+ extop = q - R_EXIT;
+ return p;
+}
+// jps is the set of paths about to be joined. jadd adds one, jsame compares
+// what two paths say about a local, and join folds them all into the current
+// states (reporting any local that disagrees)
+int jadd(int p) {
+ if (njps >= jpscap) { jps = chk_resize(jps, njps, jpscap * 2); jpscap = jpscap * 2; }
+ jps[njps] = p;
+ njps++;
+ return 0;
+}
+// true when the states of two jumps agree for local i.
+word jsame(int p, int q, int i) {
+ int s = jst(p, i), t = jst(q, i);
+ if (empty(s) && empty(t)) return 1;
+ return s == t && jhm(p, i) == jhm(q, i) && (s != S_CHILD || jow(p, i) == jow(q, i));
+}
+// join the paths in jps over the locals [lbase, n0) into the current states
+int join(int n0, int what) {
+ int i = lbase, j, p, s, s0, dead, okdead, bad;
+ af_forget();
+ if (!njps) return 0;
+ while (i < n0) {
+ p = jps[0]; s0 = jst(p, i);
+ dead = 0; okdead = 1; bad = -1;
+ j = 0;
+ while (j < njps) {
+ s = jst(jps[j], i);
+ if (s == S_DEAD || s == S_D) dead = 1;
+ if (s == S_U || s == S_BORROW) okdead = 0;
+ if (!jsame(p, jps[j], i)) bad = j;
+ j++;
+ }
+ lstate[i] = s0; lowner[i] = jow(p, i); lhome[i] = jhm(p, i);
+ if (bad < 0) {
+ if (empty(s0)) {
+ j = 0;
+ while (j < njps) {
+ if (jst(jps[j], i) == S_DEAD) lstate[i] = S_DEAD;
+ j++;
+ }
+ }
+ } else if (dead && okdead) lstate[i] = S_DEAD;
+ else if (fchk && !panic && !dry) {
+ 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++;
+ }
+ return 0;
+}
+// on path p: count (and with doit, emit) the frees for what the join made dead but p still owns
+int fixups(int p, int n0, int doit) {
+ int i = lbase, n = 0, s;
+ if (!fchk) return 0;
+ while (i < n0) {
+ s = jst(p, i);
+ if (lstate[i] == S_DEAD && (s == S_M || s == S_D)) { n++; if (doit) freevar(i); }
+ i++;
+ }
+ return n;
+}
+// a jump back to the top of a loop: whatever the loop owns has to be as it was
+int backedge(int s0) {
+ word p = R_SNAP + s0; int i = p[0], e = p[1], s;
+ if (!fchk || !live) return 0;
+ if (e > nloc) e = nloc;
+ while (i < e) {
+ s = lstate[i];
+ if (empty(s) && empty(jst(p, i))) { if (jst(p, i) == S_DEAD) lstate[i] = S_DEAD; }
+ else if (s != jst(p, i) || lhome[i] != jhm(p, i) || (s == S_CHILD && lowner[i] != jow(p, i))) {
+ if (s == S_DEAD || s == S_D) {
+ cerrn(i, "is given away inside a loop that comes back around to it; give it away on a path that leaves the loop");
+ }
+ else if (!panic && !dry) {
+ errat();
+ 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++;
+ }
+ return 0;
+}
+// joins two paths that end at the same point, like the branches of if/else:
+// path a jumped (through the hole at ja) and path b falls through to here
+// (the then path's snapshot is sa; livea / livebv say whether each path can
+// reach the join at all, since a path that returned is not part of it)
+int join2(int sa, int livea, int ja, int livebv, int n0, int what) {
+ int sb, nt, cj;
+ sb = snap_push();
+ njps = 0; if (livea) jadd(R_SNAP + sa); if (livebv) jadd(R_SNAP + sb);
+ join(n0, what);
+ if (livebv) fixups(R_SNAP + sb, n0, 1);
+ nt = 0; if (livea) nt = fixups(R_SNAP + sa, n0, 0);
+ if (nt) { cj = hole(); op(O_JMP); setlit(ja, here); fixups(R_SNAP + sa, n0, 1); setlit(cj, here); }
+ else setlit(ja, here);
+ live = livea || livebv;
+ 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 ----
+
+// recast: x as u / x as d / x as m
+int recast(int c, int t) {
+ int i, st;
+ if (c == C_FRESH && t == S_M) return C_PLAIN; // m*f(): the unnamed result is let go on purpose, so it can be discarded
+ if (!fchk) return c;
+ if (c < C_VAR) { cerr("only a named owned object can be recast; this expression has no owner"); return c; }
+ i = c - C_VAR;
+ st = lstate[i];
+ luse(i);
+ if (st == S_CHILD) cerrn(i, "belongs to a container; recast the container instead");
+ else if (st == S_BORROW) cerrn(i, "is borrowed and can't be recast");
+ else if (st == S_PLAIN) cerrn(i, "isn't an owned object");
+ else if (st == S_DEAD) cerrn(i, "used after it was given away");
+ else if (t == S_M && st != S_M) cerrn(i, "can't be recast back to m");
+ else lstate[i] = t;
+ return c;
+}
+// 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");
+ } else {
+ xgive = 1;
+ }
+ return 0;
+ }
+ 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
+ if (st == S_D) {
+ cerrn(i, "is d; pass it to a d parameter (lending it would lose it)");
+ return 0;
+ }
+ touch(i);
+ 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");
+ else cerrn(i, "is borrowed and can't be given away");
+ return 0;
+}
+// child, owner on the stack (owner on top). write owner into the child's
+// header and leave the child. a shared child keeps the owner it has. a null
+// child's address is out of range and the store does not land.
+// o3 code can't call own(), so this is own() inlined: skip the store when the
+// child is shared (flag bit 8) and otherwise write the owner into the child's
+// header word p[-2]
+int o3own() {
+ int sk, st;
+ lit(1); op(O_PICK); lit(4); op(O_SUB); op(O_LD); lit(8); op(16);
+ sk = hole(); op(O_JZ);
+ op(O_DROP);
+ st = hole(); op(O_JMP);
+ setlit(sk, here);
+ lit(1); op(O_PICK); lit(2); op(O_SUB); op(O_SWAP); op(O_SWAP); op(O_ST);
+ setlit(st, here);
+ return 0;
+}
+// storing a child into container local p: emit the call that records the
+// 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) {
+ 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);
+ op(O_LD);
+ }
+ if (o3on && !mdbg) o3own();
+ else callname(s_own);
+ if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); }
+ return 0;
+}
+// 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, 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) {
+ cerrn(j, "used after it was given away");
+ return C_PLAIN;
+ }
+ if (k == LV_LOCAL) {
+ st = lstate[i];
+ 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 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; }
+ else lstate[i] = S_PLAIN;
+ return C_PLAIN;
+ }
+ if (!empty(st)) {
+ cerrn(i, "still owns an object; overwriting it would leak it");
+ return C_PLAIN;
+ }
+ if (rc == C_FRESH) {
+ 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[j] >= lhome[i]
+ && !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;
+ end_owner(j);
+ luse(j); ev_put(j, EV_UP, i);
+ if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
+ return C_PLAIN;
+ }
+ if (js == S_U) {
+ cerrn(j, "is u; it can only be handed to a variable of the enclosing block, or returned");
+ }
+ else cerrn(j, "is an owned object and can't be copied into another variable");
+ return C_PLAIN;
+ }
+ if (k == LV_ELEM) {
+ r = lroot(i);
+ st = lstate[r];
+ touch(i);
+ if (rc == C_FRESH) { emitown(i); return C_PLAIN; }
+ if (j < 0) return C_PLAIN;
+ js = lstate[j];
+ if (js == S_CHILD) cerrn(j, "already belongs to a container");
+ else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored");
+ else if (st == S_BORROW) {
+ if (js == S_U) {
+ cerrn(j, "is u; it has to leave through the block above, not into a borrowed object");
+ }
+ 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); 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 (k == LV_GLOBAL) lend_clear(i);
+ if (j >= 0 && k == LV_GLOBAL && xlend == j && lstate[j] == S_M && lown[j]) { xlend = -1; touch(j); lend_add(i, j); return C_PLAIN; }
+ xlend = -1;
+ if (j >= 0) {
+ 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;
+}
+// return c: an owned object (d, or u after u*x) goes up with the return, and the
+// function's return is owned: declared with .u, or inferred from this return when
+// the function has no .u (its callers compiled after this point see it). an m
+// 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;
+ 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; }
+ if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does
+ if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) {
+ 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) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); }
+ else cerrn(i, "has to be u to be returned");
+ }
+ return 0;
+ }
+ if (c == C_FRESH) cerr("returns a new object; give the function a .u return type");
+ else if (i >= 0) cerrn(i, "is owned and can't be returned from a function whose return type isn't .u");
+ return 0;
+}
+// the last expression of a block expression
+int handup(int c) {
+ int i;
+ if (!fchk) return C_PLAIN;
+ 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); 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;
+}
+// throw away an expression result, which is an error for a fresh object.
+int discard(int c) {
+ if (c == C_FRESH) cerr("a new object (or a u result) can't be thrown away");
+ else if (c >= C_VAR) touch(c - C_VAR);
+ xbor = -1;
+ return 0;
+}
+
+// ---- 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;
+}
+
diff --git a/src/compiler/base.zc b/src/compiler/base.zc
new file mode 100644
index 0000000..17ec80b
--- /dev/null
+++ b/src/compiler/base.zc
@@ -0,0 +1,1384 @@
+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) ----
+
+// 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 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.
+
+// 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;
+// 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 = 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.
+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;
+word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
+
+// 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;
+char** uname = uname_BASE;
+word useq = useq_BASE;
+word ulen = ulen_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 ----
+
+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
+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)
+const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2;
+// lvalue kinds
+const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4;
+// 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
+const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604;
+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 ----
+
+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
+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
+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
+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 = 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;
+// a global that borrows a local owner through g = m*x. each entry is the global's
+// symbol, the local, and the block depth of the store; the lend ends when the same
+// global is assigned again in that block, and is an error if the block can end first.
+word lendrec; int nlend, lendcap, xlend = -1;
+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
+
+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;
+int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm;
+int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave;
+int s_copy_w;
+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) ----
+
+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, TY_BSTR = 13;
+const int NTL = 1024; // the first NTL words list every record
+// 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, 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
+
+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
+
+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
+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;
+// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the
+// compile context it was made in), newest last. a block's end unlinks them from their names.
+word tdstk; int ntd, tdcap;
+word readonly_types;
+int dty, dtag; // result of dtype()
+int s_typedef;
+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 ----
+
+// 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; 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; 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; 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; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for
+int s_this;
+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;
+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.
+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)
+const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
+const int NEWTYPED = 0x800000;
+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
+// 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 + 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;
+ 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.
+int dg_open(int sev) {
+ dg_close();
+ if (dgpos + 205 > dgcap) {
+ int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2;
+ dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap;
+ }
+ if (dgprev && tpend >= 0) {
+ dgbuf[dgpos + 1] = tpline;
+ dgbuf[dgpos + 2] = tpend - 1;
+ dgbuf[dgpos + 3] = 1;
+ }
+ else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; }
+ dgbuf[dgpos] = sev;
+ // 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;
+}
+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 = 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;
+ rt.outmode = dgmode;
+ return 0;
+}
+// a hint is a finding that isn't an error: the program still compiles, and
+// only check mode reports it. hint_begin(place) opens the record at a packed
+// place (see tpack) and says whether to write the text; dg_close ends it.
+// one hint per source position. a growing set keeps large documents linear
+// instead of rescanning every previous diagnostic for each local.
+int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); }
+int dg_dup() {
+ int key = ((toff + 1) << 24) | tline, i, k, cap; word old;
+ if (dgseen_n + dgseen_n >= dgseen_cap) {
+ old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap);
+ i = 0;
+ while (i < dgseen_cap) {
+ if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; }
+ i++;
+ }
+ dgseen_cap = cap;
+ }
+ i = dg_hash(key, dgseen_cap);
+ while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); }
+ dgseen[i] = key; dgseen_n++; return 0;
+}
+// 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) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; }
+ tunpack(sv);
+ return ok;
+}
+// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in
+// check mode) and counts the error; errend ends the line and gives up after
+// 20; giveup prints the total and halts (check mode: sets chkabort, which
+// makes the lexer return end of file so the parse unwinds). fatal reports and
+// stops at once; serr just reports; die reports and also skips input to the
+// next ; or } (panic) so one mistake does not become a screen of errors.
+int errat() {
+ if (cmode) {
+ dg_open(1);
+ } else {
+ rt.put_s("zc: line ");
+ rt.put_n(tline);
+ rt.put_s(": ");
+ }
+ nerr++;
+ return 0;
+}
+// finish an error line; the compiler stops after 20 errors.
+int errend() {
+ if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; }
+ 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; }
+ 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(char* msg) {
+ errat(); rt.put_s(msg);
+ if (cmode) { dg_close(); chkabort = 1; return 0; }
+ rt.put_c(10); rt.halt();
+ return 0;
+}
+// report a syntax error at the current token, once per recovery.
+int serr(char* msg) {
+ if (!panic && !dry) {
+ errat();
+ rt.put_s(msg);
+ errend();
+ }
+ return 0;
+}
+// report an error that names something, then the message.
+int serrn(char* name, char* msg) {
+ if (!panic && !dry) {
+ errat();
+ 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(char* msg) {
+ serr(msg);
+ panic = 1;
+ if (tk == T_EOF) giveup();
+ next();
+ return 0;
+}
+// what the parser is looking at, for the "found" part of a message
+int tokshow() {
+ 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(char* msg) {
+ if (!panic && !dry) {
+ errat();
+ rt.put_s(msg); rt.put_s(", found "); tokshow();
+ errend();
+ }
+ panic = 1;
+ if (tk == T_EOF) giveup();
+ next();
+ return 0;
+}
+// die with a name shown before the message.
+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.
+const int LSW = 1;
+word lsets;
+int lsmark(int set, int n) {
+ if (lsets) n[-2] = n[-2] | (1 << set);
+ return 0;
+}
+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 = 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, 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;
+ fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap);
+ fieldnames = chk_resize(fieldnames, fieldn, fieldcap);
+ fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap);
+ }
+ fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++;
+ return 0;
+}
+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;
+}
+// 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.
+int token_char(int n, int c) {
+ if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; }
+ tbuf[n] = c; return n + 1;
+}
+int doc_char(int c) {
+ if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; }
+ dbuf[dn] = c; dn++; return 0;
+}
+int doc_name_room(int need) {
+ if (need <= docnamecap) return 0;
+ while (docnamecap < need) docnamecap = docnamecap * 2;
+ dnmb = chk_resize(dnmb, 0, docnamecap); return 0;
+}
+// reserve compiler columns at execution time. indices remain valid after a copy.
+int reserve_symbols(int need) {
+ int cap = symcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ sname = chk_resize(sname, nsym, cap);
+ skind = chk_resize(skind, nsym, cap);
+ sval = chk_resize(sval, nsym, cap);
+ ssig = chk_resize(ssig, nsym, cap);
+ sflag = chk_resize(sflag, nsym, cap);
+ 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) {
+ int cap = loccap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ lname = chk_resize(lname, nloc, cap);
+ lslot = chk_resize(lslot, nloc, cap);
+ lhome = chk_resize(lhome, nloc, cap);
+ lstate = chk_resize(lstate, nloc, cap);
+ lowner = chk_resize(lowner, nloc, cap);
+ lused = chk_resize(lused, nloc, cap);
+ larr = chk_resize(larr, nloc, cap);
+ lown = chk_resize(lown, nloc, cap);
+ 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;
+}
+int reserve_fixes(int need) {
+ int cap = fixcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ fsite = chk_resize(fsite, fixcap, cap);
+ fnext = chk_resize(fnext, fixcap, cap);
+ fixcap = cap; return cap;
+}
+const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS;
+word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS;
+int reserve_R_SNAP(int need) {
+ int cap = R_SNAP_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap;
+ return cap;
+}
+int reserve_R_QBUF(int need) {
+ int cap = R_QBUF_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap;
+ return cap;
+}
+int reserve_R_SRC(int need) {
+ int cap = R_SRC_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap;
+ return cap;
+}
+int reserve_R_SEQ(int need) {
+ int cap = R_SEQ_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap;
+ return cap;
+}
+int reserve_R_CTX(int need) {
+ int cap = R_CTX_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap;
+ return cap;
+}
+int reserve_R_LOOP(int need) {
+ int cap = R_LOOP_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap;
+ if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; }
+ return cap;
+}
+int reserve_R_EXIT(int need) {
+ int cap = R_EXIT_cap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap;
+ return cap;
+}
+int reserve_binary(int need) {
+ int cap = bincap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ bname = chk_resize(bname, nbin, cap);
+ bprec = chk_resize(bprec, nbin, cap);
+ bseq = chk_resize(bseq, nbin, cap);
+ blen = chk_resize(blen, nbin, cap);
+ bincap = cap; return cap;
+}
+int reserve_unary(int need) {
+ int cap = uncap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ uname = chk_resize(uname, nun, cap);
+ useq = chk_resize(useq, nun, cap);
+ ulen = chk_resize(ulen, nun, cap);
+ uncap = cap; return cap;
+}
+int reserve_assignment(int need) {
+ int cap = asgcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ aname = chk_resize(aname, nasg, cap);
+ abin = chk_resize(abin, nasg, cap);
+ asgcap = cap; return cap;
+}
+int reserve_operators(int need) {
+ int cap = kopcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ kops = chk_resize(kops, nkop, cap);
+ kopcap = cap; return cap;
+}
+// write a record for the name at byte offset off. ref is what a local names (its
+// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1.
+int rs_put(int off, int kind, int ref, int sym) {
+ int at;
+ if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0;
+ if (rsn >= rscap) {
+ rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W);
+ rstmp = chk_resize(0, 0, rscap * 2 * RS_W);
+ rscap = rscap * 2;
+ }
+ at = rsn * RS_W;
+ rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym;
+ 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
+// whole document (a function used before its definition is defined by now).
+int rs_fix() {
+ int i = 0;
+ while (i < rsn) {
+ 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;
+}
+// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the
+// document declares (not a built-in) and 32 when that symbol has a definition
+int rs_sym(int s) {
+ int v = skind[s];
+ if (s >= ubase) v = v | 16;
+ if (sflag[s] & F_DEF) v = v | 32;
+ return v;
+}
+// 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) {
+ 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
+// document costs n log n, whatever order its names come in.
+int rs_sort() {
+ int w = 1, lo, mid, hi, i, j, k, n = rsn;
+ if (n < 2) return 0;
+ while (w < n) {
+ lo = 0;
+ while (lo < n) {
+ mid = lo + w; if (mid > n) mid = n;
+ hi = lo + w * 2; if (hi > n) hi = n;
+ i = lo; j = mid; k = lo;
+ while (i < mid && j < hi) {
+ if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; }
+ else { rs_move(rsrec, i, rstmp, k); i++; }
+ k++;
+ }
+ while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; }
+ while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; }
+ lo = hi;
+ }
+ i = 0;
+ while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; }
+ w = w * 2;
+ }
+ return 0;
+}
+// analyzer errors only count in checked, reachable code
+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, 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 ----
+
+// 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(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; char* p; char** old;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ old = namehash;
+ namehash = chk_resize(0, 0, cap);
+ hashcap = cap;
+ while (i < previous) {
+ p = old[i];
+ if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; }
+ i++;
+ }
+ return cap;
+}
+// return the interned copy of a spelling. new chunks preserve every old handle.
+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] && rt.eq_w(p, s)) return p;
+ h = (h + 1) & (hashcap - 1);
+ }
+ n = rt.len_w(s);
+ if (pooltop + n + 3 > poolend) {
+ if (poolchunk < n + 3) poolchunk = n + 3;
+ else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2;
+ p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p;
+ pooltop = p + 1; poolend = pooltop + poolchunk;
+ }
+ pooltop[0] = 0; pooltop[1] = 0;
+ p = pooltop + 2;
+ rt.copy_w(p, s, n + 1);
+ pooltop = pooltop + n + 3;
+ namehash[h] = p; namecount++;
+ return p;
+}
+
+// ---- 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.
+// 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;
+ sval[i] = 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_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_GVAR || skind[i] == K_GARR) 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;
+}
+// modules reported missing in this unit, so their names are not reported again as undefined
+word missing_imports;
+// true when name is qualified by a module this unit was already told to include.
+int missing_import_name(char* name) {
+ int i = 0; word p = missing_imports;
+ while (name[i] && name[i] != '.') i++;
+ if (!name[i]) return 0;
+ while (p) {
+ char* module = p[1]; int k = 0;
+ while (k < i && module[k] == name[k]) k++;
+ if (k == i && !module[k]) 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();
+ word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link;
+ // the unit already fails; resolving the name anyway keeps later diagnostics about the real code
+ }
+ }
+ 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]; }
+ while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them
+ return 0;
+}
+// look a name up from a plain string.
+int lookups(char* s) { return lookup(intern(s)); }
+
+// patch nodes
+int fixnew(int site, int link) {
+ int i = fixfree;
+ if (i) fixfree = fnext[i];
+ else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); }
+ fsite[i] = site; fnext[i] = link;
+ return i;
+}
+// patch every lit in a chain to point at v, and recycle the nodes
+int fixall(int head, int v) {
+ int n;
+ while (head) {
+ setlit(fsite[head], v);
+ n = fnext[head];
+ fnext[head] = fixfree;
+ fixfree = head;
+ head = n;
+ }
+ return 0;
+}
+
+// ---- 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
+// queries use to type an expression without producing code.
+// reserve contiguous words before recording an address or writing a data block.
+// new runtime segments are linked by jumps; earlier code and globals stay put.
+int reserve_code(int need) {
+ word p; int cap, top;
+ if (dry) return 0;
+ if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation");
+ if (image_active) {
+ if (need <= hlimit - here) return hlimit - here;
+ cap = image_capacity;
+ while (cap < here - image_org + need) {
+ if (cap > (0x7fffffff - image_org) / 2) fatal("image too big");
+ cap = cap * 2;
+ }
+ image_buffer = chk_resize(image_buffer, here - image_org, cap);
+ image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap;
+ return hlimit - here;
+ }
+ if (need <= hlimit - here - 2) return hlimit - here - 2;
+ 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] - 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;
+ return hlimit - here - 2;
+}
+// a region handed to an optimizer must fit in one contiguous segment.
+int code_contiguous(int lo, int hi) {
+ 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;
+}
+int emit(int w) {
+ if (dry) return 0;
+ if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1);
+ *(here + hdelta) = w;
+ here++;
+ return 0;
+}
+// a vm code word with the low bit set pushes the value in its upper bits
+// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps.
+int lit(int v) {
+ if ((v >> 62) != 0 && (v >> 62) != -1) {
+ reserve_code(5);
+ emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15);
+ emit((v & 0xFFFFFFFF) * 2 + 1); op(17);
+ } else emit(v + v + 1);
+ return 0;
+}
+// emit one vm opcode.
+int op(int k) { emit(k + k); return 0; }
+// fill the literal placeholder at address at with value v.
+int setlit(int at, int v) {
+ if (dry) return 0;
+ if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large");
+ *(at + hdelta) = v + v + 1;
+ return 0;
+}
+// emit a jump to the address t.
+int jmp(int t) {
+ lit(t);
+ op(O_JMP);
+ return 0;
+}
+// a lit that will be patched later; returns its address
+int hole() {
+ int a;
+ reserve_code(1); a = here;
+ lit(rt.MW - 1);
+ return a;
+}
+// forward references. calling a function that is not defined yet creates a
+// symbol with F_DEF clear whose value is the place of its first use (for the
+// "never defined" error). each call site emits a placeholder literal and adds
+// 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(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(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(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(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);
+ return 0;
+}
+// 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(rt.ZFP);
+ op(O_LD);
+ if (k) { lit(k); op(O_ADD); }
+ return 0;
+}
+// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the
+// jit specializes on the literal k
+word vaddr(int i) {
+ int s;
+ if (ltag[i] & 16) {
+ s = lookup(s_lfa);
+ if (s >= 0 && skind[s] == K_ENH) {
+ lit(lslot[i]);
+ op(XOP0 + sval[s]);
+ return 0;
+ }
+ }
+ 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 ----
+
+// 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
+// region to the core through install(). the core compiles the whole region
+// with those slots held in registers and writes them back to the frame when
+// control leaves it. there are 7 registers, so locals past that stay in
+// memory.
+int o3reset() {
+ no3free = 0;
+ o3nreg = 1;
+ no3sl = 0;
+ return 0;
+}
+// a freed register keeps its class. a float home is a d register and an int
+// home is an x register, and one var index cannot be both, so a number that
+// was a float is only handed out again as a float.
+int o3take(int fp) {
+ int i = 0, k, w = -1;
+ while (i < no3free) {
+ k = o3free[i];
+ if (fp && k < 0) w = i;
+ if (!fp && k > 0) w = i;
+ i++;
+ }
+ if (w >= 0) {
+ k = o3free[w];
+ no3free--;
+ o3free[w] = o3free[no3free];
+ if (k < 0) return 0 - k;
+ return k;
+ }
+ if (o3nreg < 8) { o3nreg++; return o3nreg - 1; }
+ return 0;
+}
+// true when a type is a plain number that o3 can keep in a register.
+int o3plain(word t) {
+ if (!t) return 0;
+ if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1;
+ return 0;
+}
+// move a local into an o3 register when the region can keep it there.
+int o3promote(int i) {
+ int r, fp;
+ ltag[i] = ltag[i] - (ltag[i] & 1792);
+ if (!o3on || larr[i] || no3sl >= 32) return 0;
+ fp = isfl(ltype[i]);
+ r = o3take(fp);
+ if (!r) return 0;
+ ltag[i] = ltag[i] + r * 256;
+ o3sl[no3sl] = lslot[i];
+ if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call
+ if (fp) r = 0 - r; // the install word is negative when the home is a d register
+ o3rg[no3sl] = r; no3sl++;
+ return 0;
+}
+// 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) {
+ int k;
+ if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0;
+ k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k);
+ newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1);
+ o3cache[i] = k;
+ return 0;
+}
+// reload the o3 register cache of a local from its memory slot.
+int o3refresh(int i) {
+ int k = o3cache[i];
+ if (!k) return 0;
+ vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST);
+ vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST);
+ return 0;
+}
+// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart]
+// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds.
+int o3region(int lo, int hi, word sl, word rg, int n) {
+ int i = 0;
+ if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0;
+ 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;
+ rt.install(o3dsc);
+ return 0;
+}
+// o3 blocks go to the cpu once their function is done, so every jump in them is final
+int o3flush() {
+ int i = 0; word p;
+ o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */
+ while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; }
+ no3pend = 0;
+ return 0;
+}
+// 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(char* s) {
+ int j = hole(), a;
+ op(O_JMP);
+ reserve_code(rt.len_w(s) + 1); a = here;
+ while (*s) { emit(*s); s++; }
+ emit(0);
+ setlit(j, here);
+ lit(a);
+ return 0;
+}
+
diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc
new file mode 100644
index 0000000..53d8597
--- /dev/null
+++ b/src/compiler/builtins.zc
@@ -0,0 +1,1423 @@
+// 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();
+ next();
+ if (is_id(s_on)) { mdbg = 1; f = 5; }
+ else if (is_id(s_scrub)) { mdbg = 1; f = 7; }
+ else if (is_id(s_off)) { mdbg = 0; f = 0; }
+ else die("memdebug on, memdebug scrub or memdebug off");
+ next();
+ expect(s_semi);
+ if (infn) { lit(f); callname(s_memset); op(O_DROP); }
+ else mdbg_flags = f;
+ return 0;
+}
+// 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(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
+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;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ pm_name = chk_resize(pm_name, npm, cap); pm_en = chk_resize(pm_en, npm, cap);
+ pm_tag = chk_resize(pm_tag, npm, cap); pm_nf = chk_resize(pm_nf, npm, cap);
+ pm_val = chk_resize(pm_val, npm, cap); pm_exp = chk_resize(pm_exp, npm, cap);
+ pm_fnm = chk_resize(pm_fnm, npm * 8, cap * 8); pm_ft = chk_resize(pm_ft, npm * 8, cap * 8); pm_off = chk_resize(pm_off, npm * 8, cap * 8);
+ pmcap = cap; return cap;
+}
+// grow the tagged-enum index independently of its member rows.
+int reserve_tagged_enums(int need) {
+ int cap = pecap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ pe_st = chk_resize(pe_st, npe, cap); pe_first = chk_resize(pe_first, npe, cap);
+ pe_n = chk_resize(pe_n, npe, cap); pe_name = chk_resize(pe_name, npe, cap);
+ pecap = cap; return cap;
+}
+// a field's c-like spelling in a generated struct
+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(char* n) {
+ int gi = npm - 1;
+ while (gi >= 0) { if (pm_name[gi] == n && pm_en[gi] >= 0) return gi; gi--; }
+ return -1;
+}
+// the tagged enum a type is a pointer to, plus one; 0 for any other type
+int ptagged(word t) {
+ int e = 0;
+ if (!t || t[0] != TY_PTR) return 0;
+ while (e < npe) { if (t[4] == pe_st[e]) return e + 1; e++; }
+ return 0;
+}
+// enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A)
+// 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() {
+ 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(); }
+ expect(s_lb);
+ while (!is_op(s_rb)) {
+ if (tk == T_EOF) { die("missing }"); break; }
+ reserve_enum_members(npm + 1);
+ gi = npm; npm++;
+ n = name();
+ pm_name[gi] = n; pm_tag[gi] = cnt; pm_nf[gi] = 0; pm_en[gi] = -1; pm_val[gi] = 0; pm_exp[gi] = 0;
+ if (is_op(s_lb)) {
+ tagged = 1; next();
+ while (!is_op(s_rb)) {
+ if (tk == T_EOF) { die("missing }"); break; }
+ if (pm_nf[gi] >= 8) { fatal("too many fields in a member"); break; }
+ if (is_id(s_var)) { next(); ft = tword; }
+ else { rettype(); ft = dty; if (dtag) serr("a payload field has no tag"); }
+ if (ft != tint && ft != tfloat && ft != tword) serr("a payload field is int, float or let");
+ fname = name();
+ nf = gi * 8 + pm_nf[gi];
+ pm_fnm[nf] = fname; pm_ft[nf] = ft; pm_nf[gi]++;
+ expect(s_semi);
+ }
+ expect(s_rb);
+ } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; }
+ cnt++;
+ if (!accept(s_comma)) break;
+ if (is_op(s_rb)) break;
+ }
+ expect(s_rb);
+ expect(s_semi);
+ if (tagged) return enum_tagged(en, first, cnt);
+ if (en) { et = tenum(en); addsym(en, K_TYPE, et); }
+ for (gi = first; gi < npm; gi++) {
+ if (pm_exp[gi]) v = pm_val[gi];
+ sk = addsym(pm_name[gi], K_CONST, v);
+ stype[sk] = et;
+ if (en) { sk = addsym(methname(en, pm_name[gi]), K_CONST, v); stype[sk] = et; }
+ v++;
+ }
+ return 0;
+}
+// a tagged enum: one struct, with the tag as word 0 and the fields of every
+// member after it, each named member_field. a member's constructor is a static
+// 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(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; }
+ for (gi = first; gi < npm; gi++) if (pm_exp[gi]) serr("a tagged enum's members take no explicit values");
+ ebn = 0; ebuf[0] = 0;
+ ebs("struct "); ebs(en); ebs(" { int tag; ");
+ for (gi = first; gi < npm; gi++) {
+ for (j = 0; j < pm_nf[gi]; j++) {
+ nf = gi * 8 + j;
+ pm_off[nf] = w; w++;
+ ebs(ftext(pm_ft[nf])); ebs(" "); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs("; ");
+ }
+ }
+ for (gi = first; gi < npm; gi++) {
+ ebs("static "); ebs(en); ebs("*.u "); ebs(pm_name[gi]); ebs("(");
+ for (j = 0; j < pm_nf[gi]; j++) {
+ nf = gi * 8 + j;
+ if (j) ebs(", ");
+ if (pm_ft[nf] == tint) ebs("int "); else if (pm_ft[nf] == tfloat) ebs("float ");
+ ebs(pm_fnm[nf]);
+ }
+ 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("; ");
+ }
+ ebs("return u*o; } ");
+ }
+ ebs("}");
+ 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();
+ 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;
+ for (gi = first; gi < npm; gi++) {
+ pm_en[gi] = npe;
+ sk = addsym(pm_name[gi], K_CONST, pm_tag[gi]);
+ stype[sk] = 0;
+ }
+ npe++;
+ return 0;
+}
+// a switch over a tagged enum that leaves out a member, with no default, is a
+// warning, on the line of the switch (sl). e is the enum, cov the bit set of
+// the tags the switch covers.
+int cover_warn(int e, int cov, int sl) {
+ int k, gi, first = 1;
+ if (dry || panic || cmode) return 0;
+ for (k = 0; k < pe_n[e]; k++) {
+ if (k < 63 && ((cov >> k) & 1)) continue;
+ 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;
+ rt.put_s(pm_name[gi]);
+ }
+ if (!first) rt.put_c(10);
+ return 0;
+}
+// ---- include ----
+
+// the text of an embedded library, one character to a word and ending in 0, as readfile gives a file.
+// it is unpacked from the byte string the first time it is asked for.
+char* bstr_text(bstr p) {
+ int n = rt.len_s(p), i = 0; char* b = m*rt.new(n + 2);
+ while (i < n) { b[i] = rt.at_s(p, i); i++; }
+ b[n] = 0;
+ return b;
+}
+// the embedded std module.
+char* std_text() {
+ if (!std_buf) std_buf = bstr_text(std_source());
+ return std_buf;
+}
+// the embedded icon module.
+char* icon_text() {
+ if (!icon_buf) icon_buf = bstr_text(icon_source());
+ 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 pp, sv; char* path; char* buf;
+ stmtonly();
+ next();
+ 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 = 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;
+}
+// embed "path" as [type] name; declares name with the bytes of the file at path, read
+// when the unit is compiled. the path is found the way an include's is. with no type, or
+// bstr, name is a byte string of the file; char* gives one character to a word; a number
+// type takes the bytes little-endian (a float or f32 needs exactly its size); T[] and
+// T[n] lay the bytes out as the array's elements, packed for the sized types, one byte
+// to an element for char[]. at the top level name is a global, in a function a local.
+int kw_embed() {
+ int pp, off, n, t = 0, i, w, count, k, a, j, v, sz, skip = -1; char* path; word data;
+ stmtonly();
+ next();
+ pp = tpack();
+ if (tk != T_STR) { die("embed takes a path in quotes: embed \"file\" as name;"); return 0; }
+ path = tv; next();
+ if (!is_id(s_as)) { die("expected as: embed \"file\" as [type] name;"); return 0; }
+ next();
+ peekat(0);
+ if (!(tk == T_ID && pk_tk == T_OP && pk_tv == s_semi)) t = ptype();
+ off = toff; n = name();
+ expect(s_semi);
+ data = m*embed_open(path);
+ if (!data) { embed_fail(pp, "can't read the file named by embed"); data = chk_resize(0, 0, 1); }
+ sz = data[0];
+ if (!t) t = tbstr;
+ // in a function the data sits in the code stream, so the code jumps over it
+ if (infn && (t == tbstr || t == tptr(tchar) || t[0] == TY_ARR)) { reserve_code(2); skip = hole(); op(O_JMP); }
+ if (t == tbstr || t == tptr(tchar)) {
+ if (t == tbstr) { a = embed_bstr(data); }
+ else { reserve_code(sz + 1); a = here; i = 0; while (i < sz) { emit(data[1 + i]); i++; } emit(0); }
+ if (skip >= 0) setlit(skip, here);
+ return embed_bind(n, off, t, a, 0);
+ }
+ if (t[0] == TY_ARR) {
+ w = embed_width(t[4]);
+ if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
+ if (sz % w) { embed_fail(pp, "the embedded file's size is not a whole number of array elements"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
+ count = sz / w;
+ if (t[3] >= 0 && count > t[3]) { embed_fail(pp, "the embedded file is larger than the array it is declared as"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
+ if (t[3] < 0) t = tarr(t[4], count);
+ k = tsz(t); reserve_code(k); a = here; i = 0;
+ if (t[4][0] == TY_CHAR) { while (i < k) { if (i < sz) emit(data[1 + i]); else emit(0); i++; } }
+ else {
+ while (i < k) {
+ v = 0; j = 0;
+ while (j < 8) { if (i * 8 + j < sz) v = v | (data[1 + i * 8 + j] << (j * 8)); j++; }
+ emit(v); i++;
+ }
+ }
+ if (skip >= 0) setlit(skip, here);
+ return embed_bind(n, off, t, a, 1);
+ }
+ w = embed_width(t);
+ if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); return embed_bind(n, off, tword, 0, 2); }
+ if (sz > w) { embed_fail(pp, "the embedded file is larger than the type it is declared as"); return embed_bind(n, off, tword, 0, 2); }
+ if (isfl(t) && sz != w) { embed_fail(pp, "an embedded float takes exactly as many bytes as its type"); return embed_bind(n, off, tword, 0, 2); }
+ v = 0; i = 0;
+ while (i < sz) { v = v | (data[1 + i] << (i * 8)); i++; }
+ if (isfl(t) && w == 4) v = rt.s2f(v);
+ else v = cnorm(t, v);
+ return embed_bind(n, off, t, v, 2);
+}
+// report msg at the embed declaration that starts at the token packed in pp. the
+// caller still declares the name, as a plain 0, so its uses are not reported too.
+int embed_fail(int pp, char* msg) {
+ int sv = tpack();
+ tunpack(pp); serr(msg); tunpack(sv);
+ return 0;
+}
+// the bytes one element of type t takes in an embedded file: the packed size of a
+// sized type or f32, 1 for char, 8 for the other number types, 0 when t is not a number.
+int embed_width(word t) {
+ if (tbytes(t)) return tbytes(t);
+ if (t[0] == TY_CHAR) return 1;
+ if (t[0] == TY_INT || t[0] == TY_WORD || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 8;
+ return 0;
+}
+// the bytes of block data ([n, b0, b1, ...]) as byte string storage in the code stream.
+int embed_bstr(word data) {
+ int n = data[0], a, i = 0, w, k;
+ reserve_code((n >> 3) + 2); a = here;
+ emit(n);
+ while (i < n) {
+ w = 0; k = 0;
+ while (k < 8 && i < n) { w = w | (data[1 + i] << (k * 8)); k++; i++; }
+ emit(w);
+ }
+ if (!(n & 7)) emit(0);
+ return a;
+}
+// declare name of type t. kind 0: a is the address of the data, which becomes the value;
+// kind 1: a is an array's storage; kind 2: a is the value itself. a global keeps the
+// value in its slot; a local is initialized from it, and a local array copies the storage.
+int embed_bind(int n, int off, word t, int a, int kind) {
+ int s, i;
+ if (!infn) {
+ if (kind == 1) { s = addsym(n, K_GARR, a); }
+ else { reserve_code(1); s = addsym(n, K_GVAR, here); emit(a); }
+ rs_put(off, RS_SYM, rs_sym(s), s); stype[s] = t;
+ lt_note(off, t);
+ return 0;
+ }
+ i = newloc(n); lrsoff[i] = off; rs_note(RS_LOCAL, off);
+ ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t;
+ if (kind == 1) {
+ larr[i] = 1; fslots = fslots + tsz(t); capture_cell(i, 0);
+ vaddr(i); lit(a); lit(tsz(t)); callname(s_copy_w); op(O_DROP);
+ } else {
+ fslots++; if (isfl(t)) setflt(i, 1); capture_cell(i, 0);
+ lit(a); vaddr(i); op(O_ST);
+ setstr(i, t == tbstr);
+ }
+ lt_note(off, t);
+ return 0;
+}
+// the bytes of an embedded file as [n, b0, b1, ...], found next to the including file,
+// then in the workspace folder, then as written, like an include. 0 when none opens.
+word.u embed_open(char*.m path) {
+ if (path[0] != '/' && incdir) { char* combined = inc_cat(incdir, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; }
+ if (path[0] != '/' && increoot) { char* combined = inc_cat(increoot, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; }
+ word b = rt.readbytes(path);
+ return b;
+}
+// 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();
+ if (is_id(s_on)) {
+ tc_on = 1;
+ } else if (is_id(s_off)) tc_on = 0; else die("typecheck on or typecheck off");
+ next(); expect(s_semi);
+ return 0;
+}
+// docs on; keeps the doc comments of what follows in the image, docs off; stops
+int kw_docs() {
+ next();
+ if (is_id(s_on)) {
+ docs_on = 1;
+ } else if (is_id(s_off)) docs_on = 0; else die("docs on or docs off");
+ 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(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;
+ nm = intern(dnmb);
+ i = nm[-1] - 1;
+ while (i >= 0) {
+ if (skind[i] == K_DOC) return i;
+ i = snext[i] - 1;
+ }
+ return -1;
+}
+// the packed documentation of a name (a byte string) or 0
+bstr docofs(char* nm) {
+ int s = docsym(nm);
+ if (s < 0) return 0;
+ return sval[s];
+}
+// the documentation of a function, by address
+bstr docof(int f) {
+ int i = nsym;
+ while (i) {
+ i--;
+ if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) return docofs(sname[i]);
+ }
+ return 0;
+}
+// documentation uses stable packed objects. symbols keep them alive until an
+// image copies their bytes after its code; ordinary docofs calls work too.
+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] | 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++; }
+ p[1 + ((i - 1) >> 3)] = w;
+ }
+ 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(char* n) {
+ int i = 0, w, k, c, b, nw;
+ if (!dsn || !n || dry || ginst || doc_section(dsnap, dsn)) return 0;
+ b = doc_pack(dsnap, dsn);
+ 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;
+ addsym(intern(dnmb), K_DOC, b);
+ 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)
+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); rt.put_c(' '); } else rt.put_s("fn ");
+ rt.put_s(n); rt.put_c('(');
+ while (i < np) {
+ if (i) rt.put_s(", ");
+ if (typed) { tprint(pty[i]); rt.put_c(' '); }
+ rt.put_s(pn[i]); i++;
+ }
+ rt.put_c(')'); return 0;
+}
+// attach a complete signature, measured before capture.
+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 = 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 = rt.outn;
+ rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
+ b = doc_pack(sgb, sl);
+ 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 ----
+
+// 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
+// their first argument and remembers which local it was, so the o3 and
+// bounds-proof machinery can see it.
+int tfindfield(word t, char* n) {
+ int i = 0;
+ tresolve(t);
+ while (i < t[3]) {
+ if (t[4][i * 3] == n) return i;
+ i++;
+ }
+ return -1;
+}
+// sizeof(T or expr), typeof(T or expr): the expression form is compiled dry, so it never runs
+int typearg() {
+ int t, sb, c, ob;
+ expect(s_lp);
+ if (tstart() && !is_op(s_dot)) t = ptype();
+ else {
+ sb = snap_push(); ob = xbor; dry++;
+ c = expr(); rval();
+ t = xty;
+ dry--; xbor = ob; snap_restore(sb); snap_pop(sb);
+ }
+ expect(s_rp);
+ return t;
+}
+// len(a), cap(a), push(a, x), pop(a), resize(a, n): growable arrays.
+// 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;
+ }
+ 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) {
+ 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 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;
+ xstr = 0;
+ xfloat = 0;
+ xlenloc = -1;
+ xstamp = 0;
+ return 0;
+ }
+ if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); }
+ else { tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); }
+ xty = tint;
+ xlenloc = -1; xstamp = 0;
+ 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 array_cap(word t) {
+ xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0;
+ expect(s_rp);
+ 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 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; xcls = C_PLAIN;
+ return 0;
+ }
+ 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;
+ lit(0); op(O_PICK); op(O_LD);
+ k = hole(); op(O_JZ);
+ 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); 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);
+ callname(s_dpop);
+ setlit(e, here);
+ return 0;
+}
+// push of a plain one-word element in o3 code, inline: when len < cap the word goes
+// straight into the items and len moves up, otherwise dpush grows the array
+int pushfast() {
+ int k, e;
+ lit(1); op(O_PICK); op(O_LD);
+ lit(2); op(O_PICK); lit(1); op(O_ADD); op(O_LD);
+ op(O_LT);
+ k = hole(); op(O_JZ);
+ lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD);
+ lit(2); op(O_PICK); op(O_LD); op(O_ADD);
+ op(O_ST);
+ lit(0); op(O_PICK); op(O_LD); lit(1); op(O_ADD); lit(1); op(O_PICK); op(O_ST);
+ op(O_DROP); lit(0);
+ e = hole(); op(O_JMP);
+ setlit(k, here);
+ callname(s_dpush);
+ setlit(e, here);
+ return 0;
+}
+// compile pop(a): remove and return the last element of a growable array.
+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 && !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 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);
+ 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; 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;
+ next();
+ t = typearg();
+ lit(tsz(t));
+ xty = tint;
+ return 0;
+}
+// compile typeof(e): the type of an expression, as a value.
+int kw_typeof() {
+ int t;
+ next();
+ t = typearg();
+ lit(t);
+ xty = tword;
+ return 0;
+}
+// ---- 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
+// ordinary zc function, so a call to it is a call like any other. the function
+// fills a block of words that sits in the code stream (the symbol's name, the
+// signature, a cache slot, then room for the arguments) and stores its arguments there,
+// and gate op 14 makes the native call (see the c abi section of core.c).
+// a signature is 4 bits per kind: the result, then each argument:
+// 0 void 1 long 2 double 3 byte pointer 4 int 5 float 6 word pointer
+// 7 unsigned int 8 short 9 unsigned short 10 char 11 unsigned char
+// 12 str, a zc byte string (b"..."), given to c as the address of its bytes
+// a double or float is a zc float. any pointer is a byte address (what
+// addr_b(s + 1) gives for a byte string), except "word *", which is a word
+// address (a zc object or array). native results come back the same way.
+// append the string s to the text being generated for an extern stub.
+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(char* s) {
+ while (*s) { eb_char(*s); s++; }
+ ebuf[ebn] = 0;
+ return 0;
+}
+// append the decimal digits of n to the generated text.
+int ebn10(int n) {
+ if (n >= 10) ebn10(n / 10);
+ eb_char(48 + n % 10);
+ ebuf[ebn] = 0;
+ return 0;
+}
+// true for a c type keyword, like long, char or double.
+int ctypeword(int w) {
+ 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, char* nm) {
+ int i = 0, n; word ft;
+ tresolve(st);
+ n = st[1];
+ if (st[0] != TY_STRUCT || st[5] || n < 1 || n > 15 || st[3] != n) { serrn(nm, "can't be passed by value to a native call: it needs one word fields, at most 15 words"); return 0 - 1; }
+ while (i < n) {
+ ft = st[4][i * 3 + 1];
+ if (tbytes(ft) || tsz(ft) != 1 || ft[0] == TY_STRUCT || ft[0] == TY_ARR || st[4][i * 3 + 2] != i) { serrn(nm, "can't be passed by value to a native call: every field has to be one word (long, double or a pointer)"); return 0 - 1; }
+ i++;
+ }
+ return n;
+}
+// the native signature kind of the c type at the current token, or -1 after an error.
+int ckind(int skipname) {
+ 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 (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 (rt.eq_w(w, "struct")) sw = tv; next(); }
+ other = 1; seen = 1;
+ continue;
+ }
+ if (seen && !ctypeword(w)) break;
+ 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();
+ }
+ if (!seen) { die("expected a c type"); return 0 - 1; }
+ while (is_op(s_star)) {
+ ptr = 1; next();
+ while (is_id(intern("const"))) next();
+ }
+ if (skipname && tk == T_ID) next();
+ if (ptr) {
+ if (other && other != 1 && rt.eq_w(other, "word")) return 6;
+ return 3;
+ }
+ if (dbl) return 2;
+ if (flt) return 5;
+ if (vd) return 0;
+ if (chr) { if (uns) return 11; return 10; }
+ if (shrt) { if (uns) return 9; return 8; }
+ if (lng) return 1;
+ if (ints || uns) { if (uns) return 7; return 4; }
+ if (other == 1) {
+ if (!sw) { serr("a struct has to be passed by pointer"); return 0 - 1; }
+ sk = lookup(sw);
+ if (sk < 0 || skind[sk] != K_TYPE || sval[sk][0] != TY_STRUCT) { serrn(sw, "isn't a zc struct: a native call takes a zc struct by value, declared with word fields"); return 0 - 1; }
+ nw = cstruct_ok(sval[sk], sw);
+ if (nw < 0) return 0 - 1;
+ ckst = sval[sk]; ckn = sw;
+ 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 (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.
+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);
+ // a struct result: the stub makes it (new) and passes it as the first item; the
+ // call block's word 2 says how many words it has and which of them are floats
+ if (rk >= 13) {
+ sret = 1; rst = ckst; rn = ckn;
+ if (rst[1] > 7) { serr("a native call can return at most 7 words"); rk = 0 - 1; }
+ }
+ fname = name();
+ cname = fname;
+ expect(s_lp);
+ if (!is_op(s_rp)) {
+ while (1) {
+ k = ckind(1);
+ if (k < 0) { bad = 1; break; }
+ if (k == 0 && np == 0 && is_op(s_rp)) break; // (void)
+ if (k == 0) { serr("a parameter can't be void"); bad = 1; break; }
+ if (np >= 15) { serr("a native call takes at most 15 arguments"); bad = 1; break; }
+ ks[np] = k; kst[np] = ckst; ksn[np] = ckn; np++;
+ if (!accept(s_comma)) break;
+ }
+ }
+ expect(s_rp);
+ if (accept(s_eq)) {
+ if (tk != T_STR) { die("expected the c name as a string"); return 0; }
+ cname = tv;
+ next();
+ }
+ expect(s_semi);
+ if (bad || rk < 0 || panic) return 0;
+ if (sret && np > 14) { serr("a native call that returns a struct takes at most 14 arguments"); return 0; }
+ rsd = 0;
+ if (sret) {
+ rsd = rst[1]; g = 0;
+ while (g < rst[1]) {
+ if (rst[4][g * 3 + 1] == tfloat) rsd = rsd | (2 << (4 + 4 * g)); else rsd = rsd | (1 << (4 + 4 * g));
+ g++;
+ }
+ }
+ // the block, then the name as packed bytes, then the block's own address
+ // (the word before the stub's entry, which is how a call site finds it):
+ // [0] the name's byte address, [1] the signature, [2] a lookup cache the
+ // host fills in, [3..17] the arguments, [18..] the name
+ // the arguments' nibbles: one per argument. a struct is a group: code 13 (in
+ // registers if they fit) or 14 (in memory), its length in eightbytes, then one
+ // class nibble per eightbyte (2 for a float word, 1 otherwise). the group is one
+ // stack item: the struct's address
+ sig = rk;
+ pos = 1; i = 0;
+ while (i < np) {
+ if (ks[i] < 13) {
+ sig = sig | (ks[i] << (4 * pos)); pos++;
+ } else {
+ st = kst[i]; len = st[1];
+ if (pos + 1 + len > 15) { serr("the arguments of this native call are too many words"); return 0; }
+ sig = sig | (ks[i] << (4 * pos)); pos++;
+ sig = sig | (len << (4 * pos)); pos++;
+ g = 0;
+ while (g < len) {
+ if (st[4][g * 3 + 1] == tfloat) sig = sig | (2 << (4 * pos)); else sig = sig | (1 << (4 * pos));
+ pos++; g++;
+ }
+ }
+ i++;
+ }
+ j = hole();
+ op(O_JMP);
+ 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
+ i = 3;
+ while (i < 18) { emit(0); i++; }
+ n = 0;
+ while (cname[n]) n++;
+ i = 0;
+ while (i < n) {
+ w = 0; k = 0;
+ while (k < 8 && i < n) { w = w | (cname[i] << (k * 8)); k++; i++; }
+ emit(w);
+ }
+ if (!(n & 7)) emit(0);
+ emit(a);
+ setlit(j, here);
+ // the stub, compiled as if it had been written in the source. it stays
+ // 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(rn); ebs("*.u "); }
+ else if (rk == 5) ebs("f32 "); else if (ckindfloat(rk)) ebs("float "); else ebs("int ");
+ ebs(fname); ebs("(");
+ i = 0;
+ while (i < np) {
+ if (i) ebs(", ");
+ if (ks[i] >= 13) { ebs(ksn[i]); ebs(" p"); } else if (ks[i] == 5) ebs("f32 p"); else if (ckindfloat(ks[i])) ebs("float p"); else ebs("int p");
+ ebn10(i);
+ i++;
+ }
+ ebs(") { let b = "); ebn10(a); ebs(";");
+ // 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(" core.syscall(14, b, 0, 0); b[3] = 0; return r; }");
+ else {
+ ebs(" return core.syscall(14, b, 0, 0)");
+ if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float");
+ ebs("; }");
+ }
+ 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();
+ 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;
+}
+// the c kind of a zc type, for cfunc
+int ckindof(word t) {
+ if (t == tfloat) return 2;
+ if (t == tf32) return 5;
+ if (t[0] == TY_INT && t[5]) { // i8 u8 i16 u16 i32 u32 pass and come back at their width
+ if (t[5] == 1) { if (t[6] & 1) return 10; return 11; }
+ if (t[5] == 2) { if (t[6] & 1) return 8; return 9; }
+ if (t[6] & 1) return 4;
+ return 7;
+ }
+ if (t == tvoid) return 0;
+ if (t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_DYN) return 6;
+ return 1;
+}
+// cfunc(f): a native function pointer that runs the zc function f, with the
+// c signature its declared types give (float is a double, f32 a c float, i8..u32 their width, a pointer is a word
+// address, anything else a long). the pointer can be given to c code, which
+// may call it while a native call from zc is running.
+int kw_cfunc() {
+ int s; word t; int sig = 1, i, nm;
+ next();
+ expect(s_lp);
+ if (tk != T_ID) { die("cfunc needs the name of a function"); return 0; }
+ 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];
+ if (t && t[0] == TY_FN) {
+ sig = ckindof(t[4]);
+ i = 0;
+ if (t[3] > 14) serrn(tv, "has more than 14 parameters");
+ while (i < t[3] && i < 14) { sig = sig | (ckindof(t[7 + i]) << (4 * (i + 1))); i++; }
+ } else {
+ i = (ssig[s] >> 1) & 31;
+ while (i > 0) { i--; sig = sig | (1 << (4 * (i + 1))); }
+ }
+ expr(); rval();
+ lit(sig);
+ callname(intern("cbmake"));
+ expect(s_rp);
+ xty = tint; xfloat = 0;
+ return 0;
+}
+// intern the built-in types and the spellings the parser compares against.
+// the s_ names are plain globals holding interned handles, so a check like
+// 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);
+ tint = tnew(TY_INT, 1, intern("int"), 0);
+ tchar = tnew(TY_CHAR, 1, intern("char"), 0);
+ 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);
+ addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string;
+ // f32: single precision. a variable holds the rounded value as a double; an element
+ // of an array or pointer of f32 is packed two to a word, like the sized integers
+ f32p = tnew(TY_FLOAT, 1, intern("f32"), 0);
+ tf32 = tnew(TY_FLOAT, 1, f32p[2], 0);
+ tf32[5] = 4; tf32[4] = f32p; f32p[5] = 4; f32p[6] = 2;
+ addsym(tf32[2], K_TYPE, tf32);
+ tsized("i8", 1, 1); addsym(intern("byte"), K_TYPE, tsized("u8", 1, 0));
+ tsized("i16", 2, 1); tsized("u16", 2, 0); tsized("i32", 4, 1); tsized("u32", 4, 0);
+ s_lt = intern("<");
+ s_gt = intern(">");
+ s_shr = intern(">>");
+ s_le = intern("<=");
+ s_ge = intern(">=");
+ s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+");
+ s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); 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("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("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("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("^");
+ numops[6] = intern("<<");
+ numops[7] = s_shr;
+ nnumops = 8;
+ return 0;
+}
+// true for an operator that works on numbers.
+int isnumop(int o) {
+ int i = 0;
+ while (i < nnumops) {
+ if (numops[i] == o) return 1;
+ i++;
+ }
+ return 0;
+}
+// the type of a binary operation, checking it when the typechecker is on
+// 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 rhs_type) {
+ int o = bname[b];
+ 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 || 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, 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 || 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(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 && 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 || 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 ----
+
+// 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.
+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 ----
+
+// 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
+// function that isn't o3, the call site where non-o3 code calls an o3
+// function whose result has pointers, and the end of an o3 block. a child
+// store of a value new(T) just allocated, or that an o3 function returned,
+// does not call fatten: the header is already that type. a call or return
+// from o3 to o3 does not fatten. fatten(p, t) stamps the type, zeroes the
+// slack, and follows every typed pointer. it returns p.
+// a type whose objects can never hold a pointer: numbers, chars, bools, and
+// arrays and plain structs of them. word, generics and anything unknown say no
+// two questions about a type's shape, both answered structurally:
+// isleaf can an object of this type never hold a heap pointer? the
+// allocator uses it to pick the recycle_leaf fast path (no children
+// to walk, so the block goes straight back on its size-class list).
+// hasptr can it hold one? fatten uses it to decide whether stamping a
+// type has to follow pointers.
+word isleaf(word t) {
+ int k = t[0], i, n;
+ if (k == TY_INT || k == TY_CHAR || k == TY_BOOL || k == TY_FLOAT) return 1;
+ if (k == TY_ARR) return isleaf(t[4]);
+ if (k == TY_STRUCT && !t[5]) {
+ n = tnfields(t);
+ i = 0;
+ while (i < n) {
+ if (!isleaf(tftype(t, i))) return 0;
+ i++;
+ }
+ return 1;
+ }
+ return 0;
+}
+// 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 || k == TY_BSTR) return 1;
+ if (k == TY_ARR) return hasptr(t[4]);
+ if (k == TY_STRUCT && !t[5]) {
+ n = tnfields(t);
+ i = 0;
+ while (i < n) {
+ if (hasptr(tftype(t, i))) return 1;
+ i++;
+ }
+ }
+ return 0;
+}
+// stamp p with its type t, then the pointers it holds.
+int fatten(int p, int t) { fat1(p, t, 32); return p; }
+// stamp p with type t if it has no type yet, then follow its pointers. dl is a
+// depth limit so a cycle through growable arrays ends. a raw header holds
+// -(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 || !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]);
+ n = p[0];
+ i = 0;
+ while (i < n) { fatin(p[2] + i * es, t[4], dl - 1); i++; }
+ }
+ return 0;
+ }
+ if (t[0] != TY_PTR || p[-1] >= 0) return 0;
+ e = t[4]; n = 0 - p[-1] - 1; es = tsz(e);
+ if (es < 1) es = 1;
+ if (e == tword || e[0] == TY_VAR) p[-1] = 0;
+ else if (n > es) p[-1] = tarr(e, 0);
+ else p[-1] = e;
+ 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;
+}
+// fatten the value stored at address a, which has type t: a pointer or handle
+// is fattened itself, an array or struct is walked element by element
+word fatin(word a, word t, int dl) {
+ int k = t[0], i, n, o, ft, es;
+ if (k == TY_PTR || k == TY_DYN) return fat1(a[0], t, dl);
+ if (k == TY_ARR) {
+ if (!hasptr(t[4])) return 0;
+ n = t[3];
+ es = tsz(t[4]);
+ i = 0;
+ while (i < n) { fatin(a + i * es, t[4], dl); i++; }
+ return 0;
+ }
+ if (k == TY_STRUCT && !t[5]) {
+ n = tnfields(t);
+ i = 0;
+ while (i < n) {
+ o = tfoff(t, i);
+ ft = tftype(t, i);
+ if (o >= 0) fatin(a + o, ft, dl);
+ i++;
+ }
+ }
+ return 0;
+}
+// reflection accessors over type records (see the layout under types). they
+// resolve a generic instance first, so asking about vec<int> works.
+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.
+word tfoff(word t, int i) { tresolve(t); return t[4][i * 3 + 2]; }
+// the return type of a function type.
+word tret(word t) { return t[4]; }
+// the number of parameters of a function type.
+word tnparams(word t) { return t[3]; }
+// the type of parameter i of a function type.
+word tparam(word t, int i) { return t[7 + i]; }
+// the tag of parameter i: 0 untagged, 1 .m, 2 .d.
+word tparamtag(word t, int i) { return ((t[5] >> 6) >> (i + i)) & 3; } // 0 untagged (m), 1 .m, 2 .d
+// true when a function type returns an owned object (.u).
+word tretu(word t) { return t[5] & 1; }
+// the number of type arguments a generic struct instance has.
+word tnargs(word t) {
+ if (t[0] != TY_STRUCT || !t[5]) return 0;
+ return t[6][0];
+}
+// 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 ----
+
+word sigof(int f) {
+ 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];
+ n = (ssig[s] >> 1) & 31; k = 0;
+ while (k < n) { ws[k] = tword; k++; }
+ return tfn(tword, n, ws, ssig[s]);
+}
+// the function type of the function named nm, or 0 when there is none.
+word sigofs(char* nm) {
+ int s = lookups(nm);
+ if (s < 0 || skind[s] != K_FN) return 0;
+ return sigof(sval[s]);
+}
+
diff --git a/src/compiler/check.zc b/src/compiler/check.zc
new file mode 100644
index 0000000..a22bcdd
--- /dev/null
+++ b/src/compiler/check.zc
@@ -0,0 +1,115 @@
+// ---- 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
+// keywords), so the document's parse words, operators and types change what
+// the rest of it means exactly as they would in a real build. the symbols
+// the document made stay in the tables afterwards for the highlighter.
+// chk_mark is the last function of the compiler proper, and LSP_SERVER (in
+// src/lsp/server.zc) the first constant of the server: the server's names are
+// hidden from the document, the runtime's and the compiler's are not.
+int chk_mark() { return 0; }
+// reset check mode for a new document.
+int chk_reset() {
+ 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 = 1; dn = 0; dline = 0; docpending = 0; dsect = 0; dsn = 0; dnm = 0;
+ pq_n = 0;
+ pendot = 0;
+ qdone = 0;
+ ch = 0;
+ chkind = 0;
+ line = 1;
+ tk = 0;
+ tv = 0;
+ tglue = 0;
+ tline = 1;
+ toff = 0 - 1; toklen = 0; tpend = 0 - 1; tpline = 1;
+ infn = 0;
+ fdepth = 0;
+ fslots = 0;
+ fchk = 0;
+ fret = 0;
+ live = 0;
+ bxdepth = 0;
+ epichain = 0;
+ fframe = 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;
+ nfact = 0; fortest = 0; forstep = 0; argloc = 0; arrloc = 0; njps = 0; extop = 0; nnumops = 0;
+ nenh = 0; panic = 0; nerr = 0; dgprev = 0; nclo = 0; newsz = 0 - 1; nomagic = 0; callpos = 0;
+ grec = 0; gnew = 0 - 1; gt_n = 0; gi_n = 0; ginst = 0; gtab = 0;
+ return 0;
+}
+// src: the document, 4 chars to a word and a 0 at the end. buf, cap: where
+// the records go. sets: 4 * LSW words for the name sets. lim: code may grow up
+// to here. every block the caller holds is a heap object reachable from its
+// globals, so the check's garbage is collected and nothing is cut off.
+// 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) 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 = 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(rt.GORG[5], 1);
+ zc_tables();
+ types_init();
+ here = 0; hdelta = 0; hlimit = lim; ptop = 0;
+ ubase = nsym; vislo = 0;
+ prescan(3, src);
+ src_push(3, src, 0, 0);
+ next();
+ unit_top();
+ rs_fix();
+ unit_end(ubase);
+ if (ifx) if_emit();
+ src_pop();
+ rs_sort();
+ chk_end();
+ return 0;
+}
+// walk a document's tokens with the lexer as the check left it. hl_begin
+// loads the first token into tk, tv, toff and toklen; next() moves on; hl_end
+// restores the previous state.
+// the syntax highlighter's view of the lexer. it re-lexes the document after
+// the check, so the tokens it sees are cut exactly where the compiler's were
+// (parse words that changed the operators included).
+int hl_begin(int src) {
+ chkabort = 0; isp = -1; pq_n = 0; pendot = 0; qdone = 0; panic = 0;
+ dry++;
+ src_push(3, src, 0, 0);
+ next();
+ return 0;
+}
+// end a highlight pass: leave the source and the dry run.
+int hl_end() {
+ src_pop();
+ dry--;
+ isp = -1;
+ pq_n = 0;
+ return 0;
+}
+// the source of the std and icon libraries, embedded when the compiler is built, so
+// `include std;` and `include icon;` need no file at run time.
+embed "src/lib/std.zc" as bstr std_embedded;
+embed "src/lib/icon.zc" as bstr icon_embedded;
+bstr std_source() { return std_embedded; }
+bstr icon_source() { return icon_embedded; }
diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc
deleted file mode 100644
index ac504d7..0000000
--- a/src/compiler/compiler.zc
+++ /dev/null
@@ -1,10458 +0,0 @@
-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) ----
-
-// 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 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.
-
-// 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;
-// 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 = 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.
-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;
-word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
-
-// 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;
-char** uname = uname_BASE;
-word useq = useq_BASE;
-word ulen = ulen_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 ----
-
-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
-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)
-const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2;
-// lvalue kinds
-const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4;
-// 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
-const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604;
-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 ----
-
-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
-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
-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
-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 = 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;
-// a global that borrows a local owner through g = m*x. each entry is the global's
-// symbol, the local, and the block depth of the store; the lend ends when the same
-// global is assigned again in that block, and is an error if the block can end first.
-word lendrec; int nlend, lendcap, xlend = -1;
-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
-
-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;
-int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm;
-int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave;
-int s_copy_w;
-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) ----
-
-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, TY_BSTR = 13;
-const int NTL = 1024; // the first NTL words list every record
-// 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, 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
-
-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
-
-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
-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;
-// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the
-// compile context it was made in), newest last. a block's end unlinks them from their names.
-word tdstk; int ntd, tdcap;
-word readonly_types;
-int dty, dtag; // result of dtype()
-int s_typedef;
-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 ----
-
-// 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; 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; 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; 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; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for
-int s_this;
-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;
-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.
-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)
-const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
-const int NEWTYPED = 0x800000;
-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
-// 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 + 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;
- 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.
-int dg_open(int sev) {
- dg_close();
- if (dgpos + 205 > dgcap) {
- int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2;
- dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap;
- }
- if (dgprev && tpend >= 0) {
- dgbuf[dgpos + 1] = tpline;
- dgbuf[dgpos + 2] = tpend - 1;
- dgbuf[dgpos + 3] = 1;
- }
- else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; }
- dgbuf[dgpos] = sev;
- // 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;
-}
-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 = 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;
- rt.outmode = dgmode;
- return 0;
-}
-// a hint is a finding that isn't an error: the program still compiles, and
-// only check mode reports it. hint_begin(place) opens the record at a packed
-// place (see tpack) and says whether to write the text; dg_close ends it.
-// one hint per source position. a growing set keeps large documents linear
-// instead of rescanning every previous diagnostic for each local.
-int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); }
-int dg_dup() {
- int key = ((toff + 1) << 24) | tline, i, k, cap; word old;
- if (dgseen_n + dgseen_n >= dgseen_cap) {
- old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap);
- i = 0;
- while (i < dgseen_cap) {
- if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; }
- i++;
- }
- dgseen_cap = cap;
- }
- i = dg_hash(key, dgseen_cap);
- while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); }
- dgseen[i] = key; dgseen_n++; return 0;
-}
-// 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) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; }
- tunpack(sv);
- return ok;
-}
-// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in
-// check mode) and counts the error; errend ends the line and gives up after
-// 20; giveup prints the total and halts (check mode: sets chkabort, which
-// makes the lexer return end of file so the parse unwinds). fatal reports and
-// stops at once; serr just reports; die reports and also skips input to the
-// next ; or } (panic) so one mistake does not become a screen of errors.
-int errat() {
- if (cmode) {
- dg_open(1);
- } else {
- rt.put_s("zc: line ");
- rt.put_n(tline);
- rt.put_s(": ");
- }
- nerr++;
- return 0;
-}
-// finish an error line; the compiler stops after 20 errors.
-int errend() {
- if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; }
- 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; }
- 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(char* msg) {
- errat(); rt.put_s(msg);
- if (cmode) { dg_close(); chkabort = 1; return 0; }
- rt.put_c(10); rt.halt();
- return 0;
-}
-// report a syntax error at the current token, once per recovery.
-int serr(char* msg) {
- if (!panic && !dry) {
- errat();
- rt.put_s(msg);
- errend();
- }
- return 0;
-}
-// report an error that names something, then the message.
-int serrn(char* name, char* msg) {
- if (!panic && !dry) {
- errat();
- 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(char* msg) {
- serr(msg);
- panic = 1;
- if (tk == T_EOF) giveup();
- next();
- return 0;
-}
-// what the parser is looking at, for the "found" part of a message
-int tokshow() {
- 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(char* msg) {
- if (!panic && !dry) {
- errat();
- rt.put_s(msg); rt.put_s(", found "); tokshow();
- errend();
- }
- panic = 1;
- if (tk == T_EOF) giveup();
- next();
- return 0;
-}
-// die with a name shown before the message.
-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.
-const int LSW = 1;
-word lsets;
-int lsmark(int set, int n) {
- if (lsets) n[-2] = n[-2] | (1 << set);
- return 0;
-}
-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 = 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, 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;
- fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap);
- fieldnames = chk_resize(fieldnames, fieldn, fieldcap);
- fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap);
- }
- fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++;
- return 0;
-}
-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;
-}
-// 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.
-int token_char(int n, int c) {
- if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; }
- tbuf[n] = c; return n + 1;
-}
-int doc_char(int c) {
- if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; }
- dbuf[dn] = c; dn++; return 0;
-}
-int doc_name_room(int need) {
- if (need <= docnamecap) return 0;
- while (docnamecap < need) docnamecap = docnamecap * 2;
- dnmb = chk_resize(dnmb, 0, docnamecap); return 0;
-}
-// reserve compiler columns at execution time. indices remain valid after a copy.
-int reserve_symbols(int need) {
- int cap = symcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- sname = chk_resize(sname, nsym, cap);
- skind = chk_resize(skind, nsym, cap);
- sval = chk_resize(sval, nsym, cap);
- ssig = chk_resize(ssig, nsym, cap);
- sflag = chk_resize(sflag, nsym, cap);
- 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) {
- int cap = loccap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- lname = chk_resize(lname, nloc, cap);
- lslot = chk_resize(lslot, nloc, cap);
- lhome = chk_resize(lhome, nloc, cap);
- lstate = chk_resize(lstate, nloc, cap);
- lowner = chk_resize(lowner, nloc, cap);
- lused = chk_resize(lused, nloc, cap);
- larr = chk_resize(larr, nloc, cap);
- lown = chk_resize(lown, nloc, cap);
- 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;
-}
-int reserve_fixes(int need) {
- int cap = fixcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- fsite = chk_resize(fsite, fixcap, cap);
- fnext = chk_resize(fnext, fixcap, cap);
- fixcap = cap; return cap;
-}
-const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS;
-word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS;
-int reserve_R_SNAP(int need) {
- int cap = R_SNAP_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap;
- return cap;
-}
-int reserve_R_QBUF(int need) {
- int cap = R_QBUF_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap;
- return cap;
-}
-int reserve_R_SRC(int need) {
- int cap = R_SRC_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap;
- return cap;
-}
-int reserve_R_SEQ(int need) {
- int cap = R_SEQ_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap;
- return cap;
-}
-int reserve_R_CTX(int need) {
- int cap = R_CTX_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap;
- return cap;
-}
-int reserve_R_LOOP(int need) {
- int cap = R_LOOP_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap;
- if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; }
- return cap;
-}
-int reserve_R_EXIT(int need) {
- int cap = R_EXIT_cap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap;
- return cap;
-}
-int reserve_binary(int need) {
- int cap = bincap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- bname = chk_resize(bname, nbin, cap);
- bprec = chk_resize(bprec, nbin, cap);
- bseq = chk_resize(bseq, nbin, cap);
- blen = chk_resize(blen, nbin, cap);
- bincap = cap; return cap;
-}
-int reserve_unary(int need) {
- int cap = uncap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- uname = chk_resize(uname, nun, cap);
- useq = chk_resize(useq, nun, cap);
- ulen = chk_resize(ulen, nun, cap);
- uncap = cap; return cap;
-}
-int reserve_assignment(int need) {
- int cap = asgcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- aname = chk_resize(aname, nasg, cap);
- abin = chk_resize(abin, nasg, cap);
- asgcap = cap; return cap;
-}
-int reserve_operators(int need) {
- int cap = kopcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- kops = chk_resize(kops, nkop, cap);
- kopcap = cap; return cap;
-}
-// write a record for the name at byte offset off. ref is what a local names (its
-// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1.
-int rs_put(int off, int kind, int ref, int sym) {
- int at;
- if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0;
- if (rsn >= rscap) {
- rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W);
- rstmp = chk_resize(0, 0, rscap * 2 * RS_W);
- rscap = rscap * 2;
- }
- at = rsn * RS_W;
- rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym;
- 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
-// whole document (a function used before its definition is defined by now).
-int rs_fix() {
- int i = 0;
- while (i < rsn) {
- 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;
-}
-// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the
-// document declares (not a built-in) and 32 when that symbol has a definition
-int rs_sym(int s) {
- int v = skind[s];
- if (s >= ubase) v = v | 16;
- if (sflag[s] & F_DEF) v = v | 32;
- return v;
-}
-// 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) {
- 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
-// document costs n log n, whatever order its names come in.
-int rs_sort() {
- int w = 1, lo, mid, hi, i, j, k, n = rsn;
- if (n < 2) return 0;
- while (w < n) {
- lo = 0;
- while (lo < n) {
- mid = lo + w; if (mid > n) mid = n;
- hi = lo + w * 2; if (hi > n) hi = n;
- i = lo; j = mid; k = lo;
- while (i < mid && j < hi) {
- if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; }
- else { rs_move(rsrec, i, rstmp, k); i++; }
- k++;
- }
- while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; }
- while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; }
- lo = hi;
- }
- i = 0;
- while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; }
- w = w * 2;
- }
- return 0;
-}
-// analyzer errors only count in checked, reachable code
-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, 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 ----
-
-// 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(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; char* p; char** old;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- old = namehash;
- namehash = chk_resize(0, 0, cap);
- hashcap = cap;
- while (i < previous) {
- p = old[i];
- if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; }
- i++;
- }
- return cap;
-}
-// return the interned copy of a spelling. new chunks preserve every old handle.
-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] && rt.eq_w(p, s)) return p;
- h = (h + 1) & (hashcap - 1);
- }
- n = rt.len_w(s);
- if (pooltop + n + 3 > poolend) {
- if (poolchunk < n + 3) poolchunk = n + 3;
- else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2;
- p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p;
- pooltop = p + 1; poolend = pooltop + poolchunk;
- }
- pooltop[0] = 0; pooltop[1] = 0;
- p = pooltop + 2;
- rt.copy_w(p, s, n + 1);
- pooltop = pooltop + n + 3;
- namehash[h] = p; namecount++;
- return p;
-}
-
-// ---- 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.
-// 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;
- sval[i] = 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_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_GVAR || skind[i] == K_GARR) 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;
-}
-// modules reported missing in this unit, so their names are not reported again as undefined
-word missing_imports;
-// true when name is qualified by a module this unit was already told to include.
-int missing_import_name(char* name) {
- int i = 0; word p = missing_imports;
- while (name[i] && name[i] != '.') i++;
- if (!name[i]) return 0;
- while (p) {
- char* module = p[1]; int k = 0;
- while (k < i && module[k] == name[k]) k++;
- if (k == i && !module[k]) 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();
- word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link;
- // the unit already fails; resolving the name anyway keeps later diagnostics about the real code
- }
- }
- 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]; }
- while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them
- return 0;
-}
-// look a name up from a plain string.
-int lookups(char* s) { return lookup(intern(s)); }
-
-// patch nodes
-int fixnew(int site, int link) {
- int i = fixfree;
- if (i) fixfree = fnext[i];
- else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); }
- fsite[i] = site; fnext[i] = link;
- return i;
-}
-// patch every lit in a chain to point at v, and recycle the nodes
-int fixall(int head, int v) {
- int n;
- while (head) {
- setlit(fsite[head], v);
- n = fnext[head];
- fnext[head] = fixfree;
- fixfree = head;
- head = n;
- }
- return 0;
-}
-
-// ---- 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
-// queries use to type an expression without producing code.
-// reserve contiguous words before recording an address or writing a data block.
-// new runtime segments are linked by jumps; earlier code and globals stay put.
-int reserve_code(int need) {
- word p; int cap, top;
- if (dry) return 0;
- if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation");
- if (image_active) {
- if (need <= hlimit - here) return hlimit - here;
- cap = image_capacity;
- while (cap < here - image_org + need) {
- if (cap > (0x7fffffff - image_org) / 2) fatal("image too big");
- cap = cap * 2;
- }
- image_buffer = chk_resize(image_buffer, here - image_org, cap);
- image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap;
- return hlimit - here;
- }
- if (need <= hlimit - here - 2) return hlimit - here - 2;
- 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] - 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;
- return hlimit - here - 2;
-}
-// a region handed to an optimizer must fit in one contiguous segment.
-int code_contiguous(int lo, int hi) {
- 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;
-}
-int emit(int w) {
- if (dry) return 0;
- if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1);
- *(here + hdelta) = w;
- here++;
- return 0;
-}
-// a vm code word with the low bit set pushes the value in its upper bits
-// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps.
-int lit(int v) {
- if ((v >> 62) != 0 && (v >> 62) != -1) {
- reserve_code(5);
- emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15);
- emit((v & 0xFFFFFFFF) * 2 + 1); op(17);
- } else emit(v + v + 1);
- return 0;
-}
-// emit one vm opcode.
-int op(int k) { emit(k + k); return 0; }
-// fill the literal placeholder at address at with value v.
-int setlit(int at, int v) {
- if (dry) return 0;
- if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large");
- *(at + hdelta) = v + v + 1;
- return 0;
-}
-// emit a jump to the address t.
-int jmp(int t) {
- lit(t);
- op(O_JMP);
- return 0;
-}
-// a lit that will be patched later; returns its address
-int hole() {
- int a;
- reserve_code(1); a = here;
- lit(rt.MW - 1);
- return a;
-}
-// forward references. calling a function that is not defined yet creates a
-// symbol with F_DEF clear whose value is the place of its first use (for the
-// "never defined" error). each call site emits a placeholder literal and adds
-// 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(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(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(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(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);
- return 0;
-}
-// 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(rt.ZFP);
- op(O_LD);
- if (k) { lit(k); op(O_ADD); }
- return 0;
-}
-// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the
-// jit specializes on the literal k
-word vaddr(int i) {
- int s;
- if (ltag[i] & 16) {
- s = lookup(s_lfa);
- if (s >= 0 && skind[s] == K_ENH) {
- lit(lslot[i]);
- op(XOP0 + sval[s]);
- return 0;
- }
- }
- 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 ----
-
-// 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
-// region to the core through install(). the core compiles the whole region
-// with those slots held in registers and writes them back to the frame when
-// control leaves it. there are 7 registers, so locals past that stay in
-// memory.
-int o3reset() {
- no3free = 0;
- o3nreg = 1;
- no3sl = 0;
- return 0;
-}
-// a freed register keeps its class. a float home is a d register and an int
-// home is an x register, and one var index cannot be both, so a number that
-// was a float is only handed out again as a float.
-int o3take(int fp) {
- int i = 0, k, w = -1;
- while (i < no3free) {
- k = o3free[i];
- if (fp && k < 0) w = i;
- if (!fp && k > 0) w = i;
- i++;
- }
- if (w >= 0) {
- k = o3free[w];
- no3free--;
- o3free[w] = o3free[no3free];
- if (k < 0) return 0 - k;
- return k;
- }
- if (o3nreg < 8) { o3nreg++; return o3nreg - 1; }
- return 0;
-}
-// true when a type is a plain number that o3 can keep in a register.
-int o3plain(word t) {
- if (!t) return 0;
- if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1;
- return 0;
-}
-// move a local into an o3 register when the region can keep it there.
-int o3promote(int i) {
- int r, fp;
- ltag[i] = ltag[i] - (ltag[i] & 1792);
- if (!o3on || larr[i] || no3sl >= 32) return 0;
- fp = isfl(ltype[i]);
- r = o3take(fp);
- if (!r) return 0;
- ltag[i] = ltag[i] + r * 256;
- o3sl[no3sl] = lslot[i];
- if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call
- if (fp) r = 0 - r; // the install word is negative when the home is a d register
- o3rg[no3sl] = r; no3sl++;
- return 0;
-}
-// 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) {
- int k;
- if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0;
- k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k);
- newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1);
- o3cache[i] = k;
- return 0;
-}
-// reload the o3 register cache of a local from its memory slot.
-int o3refresh(int i) {
- int k = o3cache[i];
- if (!k) return 0;
- vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST);
- vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST);
- return 0;
-}
-// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart]
-// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds.
-int o3region(int lo, int hi, word sl, word rg, int n) {
- int i = 0;
- if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0;
- 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;
- rt.install(o3dsc);
- return 0;
-}
-// o3 blocks go to the cpu once their function is done, so every jump in them is final
-int o3flush() {
- int i = 0; word p;
- o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */
- while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; }
- no3pend = 0;
- return 0;
-}
-// 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(char* s) {
- int j = hole(), a;
- op(O_JMP);
- reserve_code(rt.len_w(s) + 1); a = here;
- while (*s) { emit(*s); s++; }
- emit(0);
- setlit(j, here);
- lit(a);
- return 0;
-}
-
-// ---- 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
-// a backtick ends the source: it is how a program tells the compiler where its
-// own text stops and the program's input begins. but only a backtick the
-// lexer would read as a token counts. srcend is fed every character of a
-// stdin source in order and follows just enough of the lexer's rules to know
-// whether it is inside a // comment, a /* */ comment, a string or a char
-// literal (a backslash escapes the next character in the last two). it
-// returns 1 for a backtick outside all of them. readsrc and nextch both use
-// it, so a backtick in a comment or a string is just a character.
-int srcend(int c) {
- if (qst == 0) {
- if (c == 96) return 1;
- if (c == 34) qst = 3;
- else if (c == 39) qst = 4;
- else if (c == '*' && qp == '/') { qst = 2; qp = 0; return 0; }
- else if (c == '/' && qp == '/') { qst = 1; qp = 0; return 0; }
- qp = c;
- return 0;
- }
- if (qst == 1) {
- if (c == 10) { qst = 0; qp = 0; }
- return 0;
- }
- if (qst == 2) {
- if (c == '/' && qp == '*') { qst = 0; qp = 0; return 0; }
- qp = c;
- return 0;
- }
- if (qesc) { qesc = 0; return 0; }
- if (c == 92) { qesc = 1; return 0; }
- if ((qst == 3 && c == 34) || (qst == 4 && c == 39)) { qst = 0; qp = 0; }
- return 0;
-}
-// 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 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 || 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;
- if (ch) chptr++; else ch = -1;
- }
- if (ch == 10) line++;
- return 0;
-}
-// the place of a token travels with its line: line in the low 24 bits, then
-// the length (16 bits), the offset + 1 (21 bits) and its glue (2 bits, so a
-// replayed token list still tells u*x from a product)
-int tpack() { return tline | (toklen << 24) | ((toff + 1) << 40) | ((tglue & 3) << 61); }
-// restore the token position that tpack saved in a word.
-int tunpack(int x) {
- tline = x & 0xFFFFFF;
- toklen = (x >> 24) & 0xFFFF;
- toff = ((x >> 40) & 0x1FFFFF) - 1;
- tglue = (x >> 61) & 3;
- return 0;
-}
-// chars consumed so far in the source being read: the index of the char in ch
-// (or of the end, once the source is used up). a stdin source reports -1.
-int cpos() {
- if (chkind == 3) { if (ch < 0) return chptr; return chptr - 1; }
- if (chkind == 2) { if (ch < 0) return chptr - chbase; return chptr - chbase - 1; }
- return 0 - 1;
-}
-// push a new input source. the current token, char state and lookahead are
-// 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
-// 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.
-int src_push(int kind, word obj, int trans, int owned) {
- word f; int i;
- isp++;
- reserve_R_SRC((isp + 1) * 32);
- f = R_SRC + isp * 32;
- f[0] = kind; f[1] = obj; f[2] = 0; f[3] = 0; f[4] = trans; f[11] = owned;
- f[5] = tk; f[6] = tv; f[7] = tglue; f[8] = ch; f[9] = chkind; f[10] = chptr;
- f[12] = line; f[13] = tpack(); f[14] = pq_n; f[31] = chbase;
- i = 0;
- while (i < pq_n) {
- f[15 + i * 4] = pq_tk[i];
- f[16 + i * 4] = pq_tv[i];
- f[17 + i * 4] = pq_gl[i];
- f[18 + i * 4] = pq_ln[i];
- i++;
- }
- pq_n = 0;
- if (kind == 1) f[3] = obj[0];
- if (kind == 0) { chkind = 0; qdone = 0; qst = 0; qp = 0; qesc = 0; line = 1; nextch(); }
- if (kind == 2) { chkind = 2; chptr = obj; chbase = obj; line = 1; nextch(); }
- if (kind == 3) { chkind = 3; chbase = obj; chptr = 0; line = 1; nextch(); }
- return 0;
-}
-// leave the current source and restore the one beneath it
-int src_pop() {
- word f = R_SRC + isp * 32; int i = 0;
- tk = f[5]; tv = f[6]; tglue = f[7]; ch = f[8]; chkind = f[9]; chptr = f[10];
- line = f[12]; tunpack(f[13]); pq_n = f[14]; chbase = f[31];
- while (i < pq_n) {
- pq_tk[i] = f[15 + i * 4];
- pq_tv[i] = f[16 + i * 4];
- pq_gl[i] = f[17 + i * 4];
- pq_ln[i] = f[18 + i * 4];
- i++;
- }
- isp--;
- if (f[11]) rt.free(f[1]);
- f[1] = 0; // a dead source pointer would pin a quarantined block
- return 0;
-}
-// the lexer's character classes. an operator is built one character at a time
-// for as long as the text so far is still a prefix of some known operator
-// (opprefix), so a program that adds an operator with add_binop changes how
-// the next source is split into tokens without any other bookkeeping.
-// the operator characters are + - * / % = < > ! & | ^ ~ ? : . @ # $ . those below
-// 64 are a bit mask indexed from 33, the rest are four compares
-int isopch(int c) {
- if (c < 33 || c > 126) return 0;
- if (c < 64) return (0x7a00763d >> (c - 33)) & 1;
- return c == 64 || c == 94 || c == 124 || c == 126;
-}
-// is buf[0..n) followed by c the start of some operator the lexer knows?
-int opprefix(int n, int c) {
- int i = 0; word k; int j;
- while (i < nkop) {
- k = kops[i];
- j = 0;
- while (j < n && k[j] == tbuf[j]) j++;
- if (j == n && k[n] == c) return 1;
- i++;
- }
- return 0;
-}
-// true for a decimal digit.
-int isdig(int c) { return c >= '0' && c <= '9'; }
-// true for a letter or an underscore.
-int isal(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; }
-// a letter, digit or _ : the characters after the first one of a name
-int isidc(int c) {
- if (c >= 'a') return c <= 'z';
- if (c >= 'A') return c <= 'Z' || c == '_';
- return c >= '0' && c <= '9';
-}
-// the value of a hex digit, or a value past 15 when c is not one.
-int hexv(int c) {
- if (isdig(c)) return c - '0';
- if (c >= 'a' && c <= 'f') return c - 'a' + 10;
- if (c >= 'A' && c <= 'F') return c - 'A' + 10;
- return -1;
-}
-// one possibly escaped character of a string or char literal
-int escch() {
- int c = ch;
- nextch();
- if (c != 92) return c;
- c = ch;
- nextch();
- if (c == 'n') return 10;
- if (c == 't') return 9;
- if (c == 'r') return 13;
- if (c == '0') return 0;
- if (c == 'e') return 27;
- return c;
-}
-// finish lexing an operator whose first n characters are in tbuf
-int oplex(int n) {
- while (ch >= 0 && isopch(ch) && opprefix(n, ch)) { n = token_char(n, ch); nextch(); }
- tbuf[n] = 0;
- tk = T_OP; tv = intern(tbuf);
- return 0;
-}
-// per-local type flags kept in ltag. these are tiny accessors; the whole bit
-// layout is: 0..3 the u/m/d tag, 4 o3, 5 float, 6 byte string, 8..10 the o3
-// register, 12.. the hint bits (see LASG and friends).
-// bit 5 of ltag: this local holds a float. the low bits are the u/m/d tag and bit 4 is o3.
-word getflt(int i) { return (ltag[i] >> 5) & 1; }
-// mark local i as holding a float, or clear the mark.
-int setflt(int i, int f) {
- if (f) ltag[i] = ltag[i] | 32;
- else ltag[i] = ltag[i] - (ltag[i] & 32);
- return 0;
-}
-// bit 6 of ltag: this local holds a byte string. bit 5 is the float.
-word getstr(int i) { return (ltag[i] >> 6) & 1; }
-// mark local i as holding a string, or clear the mark.
-int setstr(int i, int f) {
- if (f) ltag[i] = ltag[i] | 64;
- else ltag[i] = ltag[i] - (ltag[i] & 64);
- return 0;
-}
-// 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 bstrdata(char* s) {
- int a, n = 0, i = 0, w, k;
- while (s[n]) n++;
- reserve_code((n >> 3) + 2); a = here;
- emit(n);
- while (i < n) {
- w = 0; k = 0;
- while (k < 8 && i < n) { w = w | (s[i] << (k * 8)); k++; i++; }
- emit(w);
- }
- if (!(n & 7)) emit(0);
- return a;
-}
-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
-// opcodes (OP_FADD ...). an int operand is converted on the fly, and a
-// comparison leaves a plain 0/1 behind. xfloat says whether the expression
-// just compiled is a float.
-int fbits() { return asm { tbuf @ #1222 }; }
-// collect an exponent (e+3) onto the literal in tbuf; n is the length so far
-int takeexp(int n) {
- if (ch != 'e' && ch != 'E') return n;
- n = token_char(n, ch);
- nextch();
- if (ch == '+' || ch == '-') { n = token_char(n, ch); nextch(); }
- if (!isdig(ch)) { die("bad float"); return n; }
- while (isdig(ch)) { n = token_char(n, ch); nextch(); }
- return n;
-}
-// the literal in tbuf[0..n) is a float: hand it back as a T_FLT token
-int floatok(int n) {
- tbuf[n] = 0;
- tk = T_FLT;
- tv = fbits();
- return 0;
-}
-// the binary operators that have a float form
-int isfop(int o) {
- if (o == s_plus || o == s_minus || o == s_star || o == s_slash) return 1;
- if (o == s_lt || o == s_gt || o == s_le || o == s_ge || o == s_eqeq || o == s_ne) return 1;
- return 0;
-}
-// emit float arithmetic or comparison o for operands that are floats (lf, rf).
-// an int left operand sits under the right one, so it is converted with
-// swap / i2f / swap. > and >= are < and <= with the operands swapped, and !=
-// is == followed by not.
-int femit(int o, int lf, int rf) {
- if (!lf) { op(O_SWAP); op(OP_I2F); op(O_SWAP); }
- if (!rf) op(OP_I2F);
- if (o == s_plus) { op(OP_FADD); xfloat = 1; return 0; }
- if (o == s_minus) { op(OP_FSUB); xfloat = 1; return 0; }
- if (o == s_star) { op(OP_FMUL); xfloat = 1; return 0; }
- if (o == s_slash) { op(OP_FDIV); xfloat = 1; return 0; }
- if (o == s_eqeq) { op(OP_FEQ); xfloat = 0; return 0; }
- if (o == s_ne) { op(OP_FEQ); lit(1); op(18); xfloat = 0; return 0; }
- if (o == s_lt) { op(OP_FLT); xfloat = 0; return 0; }
- if (o == s_gt) { op(O_SWAP); op(OP_FLT); xfloat = 0; return 0; }
- if (o == s_le) { op(OP_FLE); xfloat = 0; return 0; }
- if (o == s_ge) { op(O_SWAP); op(OP_FLE); xfloat = 0; return 0; }
- xfloat = 0;
- return 0;
-}
-// true when a type is a float, the same as isfl.
-word isfloat(int t) { return isfl(t); }
-// float, f32, and f32's packed twin: everything computed as a double
-word isfl(word t) { return t && t[0] == TY_FLOAT; }
-// ±0 is false, like 0.0. every other float, including NaN, is true. the vm's
-// jz stays an integer test, so a float is compared with +0 first.
-int ftest() {
- if (!xfloat) return 0;
- op(OP_FLIT); emit(0); op(OP_FEQ); lit(1); op(18);
- xfloat = 0; xty = tbool;
- return 0;
-}
-// 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; }
- // an f32 variable holds a value rounded to single precision; a packed
- // element is rounded when pkst stores it
- if (dst == tf32 && xty != tf32 && !ispk(xty)) { xenh("zf2s"); xenh("zs2f"); xty = tf32; }
- return 0;
- }
- if (!xfloat) {
- if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst);
- return 0;
- }
- if (isnum(dst)) { op(OP_F2I); xfloat = 0; xty = dst; if (tbytes(dst)) tnorm(dst); return 0; }
- if (dst == tword) xfloat = 0;
- return 0;
-}
-// sized integers. a value stored into a variable, field, parameter or array
-// element of type t is cut to size first: unsigned types are masked and signed
-// ones are masked and then sign-extended (xor the sign bit, subtract it).
-// samesz says a value of type b is already in range for type a.
-word samesz(word a, word b) { return tbytes(a) && tbytes(b) == tbytes(a) && (a[6] & 1) == (b[6] & 1); }
-// narrow the integer on the stack to the width of its type.
-int tnorm(word t) {
- int b = tbytes(t), s;
- if (!b || t[0] != TY_INT) return 0;
- lit((1 << (8 * b)) - 1); op(16);
- if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); }
- return 0;
-}
-// [byte address] -> [value]: load a packed element of type t (the twin)
-int pkld(word t) {
- int b = t[5];
- if (b == 1) op(612); else if (b == 2) op(614); else op(616);
- pkfix(t);
- return 0;
-}
-// a zero extended packed element on the stack becomes the value of type t
-int pkfix(word t) {
- int b = t[5], s;
- if (t[0] == TY_FLOAT) { xenh("zs2f"); return 0; } // f32: the bits widen to a double
- if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); }
- return 0;
-}
-// [value byte-address] -> []: store a packed element of type t
-int pkst(word t) {
- int b = t[5];
- if (t[0] == TY_FLOAT) { op(O_SWAP); xenh("zf2s"); op(O_SWAP); op(617); return 0; } // f32: round the double to single bits
- if (b == 1) op(613); else if (b == 2) op(615); else op(617);
- return 0;
-}
-// an integer address and a field name: the name has to belong to one struct.
-// field lookup on a bare address, for untyped code such as p.x where p is a
-// plain word. if exactly one struct in the type area has a field called n, that
-// is the one meant; with two or more it is ambiguous (the caller reports it).
-int fieldscan(int n) {
- int i = 0; word t; int k, hit = 0;
- fldst = 0; fldi = -1;
- while (i < ntl) {
- t = typelist[i];
- if (t[0] == TY_STRUCT && t[3] >= 0) {
- k = 0;
- while (k < t[3]) {
- if (t[4][k * 3] == n) {
- if (hit) return 2;
- hit = 1; fldst = t; fldi = k;
- }
- k++;
- }
- }
- i++;
- }
- return hit;
-}
-// a // comment, with ch on its second slash. one that starts a line joins the
-// 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, 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 (dlast != cl - 1) { dn = 0; dsect = 0; }
- if (ch == 32) nextch();
- start = dn;
- while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); }
- 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;
-}
-// 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, 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) {
- if (bol) {
- while (ch == 32 || ch == 9) nextch();
- if (ch == '*') { nextch(); if (ch == '/') { cl = line; nextch(); break; } if (ch == 32) nextch(); }
- bol = 0;
- }
- c = ch; nextch();
- if (c == '*' && ch == '/') { cl = line; nextch(); break; }
- doc_char(c);
- if (c == 10) bol = 1;
- }
- 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
-// tk / tv with its place in tline / toff. numbers (decimal, hex, floats),
-// names, char and string literals (b"..." is a byte string), and operators
-// all start here. tglue is 1 when the token touches the one before it with no
-// 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);
- return 0;
- }
- tglue = 1;
- nl = tline != line;
- while (1) {
- while (ch == 32 || ch == 9 || ch == 10 || ch == 13) { if (ch == 10) nl = 1; nextch(); tglue = 0; }
- if (ch != '/') break;
- nextch();
- if (ch == '/') {
- if (docs_on) doccomment(nl); else while (ch >= 0 && ch != 10) nextch();
- 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(); }
- nextch(); tglue = 0; continue;
- }
- tline = line; toff = cpos() - 1; tbuf[0] = '/'; oplex(1); return 0;
- }
- tline = line; toff = cpos();
- if (docpending) {
- dline = 0;
- if (dn && (tline == dlast || tline == dlast + 1)) dline = tline;
- docpending = 0;
- }
- if (ch < 0) { tk = T_EOF; tv = 0; return 0; }
- if (isdig(ch)) {
- v = 0; n = 0;
- if (ch == '0') {
- nextch();
- if (ch == 'x' || ch == 'X') {
- nextch();
- while ((dg = hexv(ch)) >= 0) { v = v * 16 + dg; nextch(); }
- tk = T_NUM; tv = v; return 0;
- }
- tbuf[0] = '0'; n = 1;
- }
- while (isdig(ch)) { n = token_char(n, ch); v = v * 10 + ch - '0'; nextch(); }
- if (ch == 'e' || ch == 'E') { n = takeexp(n); return floatok(n); }
- if (ch == '.') {
- nextch();
- if (isdig(ch) || ch == 'e' || ch == 'E') {
- n = token_char(n, '.');
- while (isdig(ch)) { n = token_char(n, ch); nextch(); }
- return floatok(takeexp(n));
- }
- if (isal(ch)) { pendot = 1; tk = T_NUM; tv = v; return 0; } // 1.field
- n = token_char(n, '.');
- return floatok(n);
- }
- tk = T_NUM; tv = v; return 0;
- }
- if (isal(ch)) {
- while (isidc(ch)) { n = token_char(n, ch); nextch(); }
- tbuf[n] = 0;
- if (n == 1 && tbuf[0] == 'b' && ch == 34) {
- n = 0; nextch();
- while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
- nextch();
- 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) {
- nextch();
- tv = escch();
- if (ch != 39) die("bad character literal");
- nextch();
- tk = T_NUM;
- tglue = 3;
- return 0;
- }
- if (ch == 34) {
- nextch();
- while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
- nextch();
- tbuf[n] = 0;
- tk = T_STR; tv = intern(tbuf); return 0;
- }
- if (ch == '.') {
- nextch();
- if (isdig(ch)) {
- n = 0; tbuf[0] = '.'; n = 1;
- while (isdig(ch)) { n = token_char(n, ch); nextch(); }
- return floatok(takeexp(n));
- }
- tbuf[0] = '.';
- if (isopch(ch) && opprefix(1, ch)) return oplex(1);
- tbuf[1] = 0;
- tk = T_OP; tv = intern(tbuf);
- return 0;
- }
- tbuf[0] = ch;
- nextch();
- if (isopch(tbuf[0])) return oplex(1);
- tbuf[1] = 0;
- tk = T_OP; tv = intern(tbuf);
- return 0;
-}
-// 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;
-}
-// one token of lookahead: peek() fills pk_*, next() takes it first
-int next() {
- int i = 1;
- ntok++;
- if (toff >= 0) { tpend = toff + toklen; tpline = tline; }
- if (pq_n) {
- tk = pq_tk[0]; tv = pq_tv[0]; tglue = pq_gl[0]; tunpack(pq_ln[0]);
- while (i < pq_n) {
- pq_tk[i - 1] = pq_tk[i];
- pq_tv[i - 1] = pq_tv[i];
- pq_gl[i - 1] = pq_gl[i];
- pq_ln[i - 1] = pq_ln[i];
- i++;
- }
- pq_n--;
- if (grec) grec_add();
- return 0;
- }
- next0();
- if (grec) grec_add();
- return 0;
-}
-// the k-th token after the current one (k from 0 to 3), into pk_tk and pk_tv
-int peekat(int k) {
- int a, b, c, e;
- while (pq_n <= k) {
- a = tk; b = tv; c = tglue; e = tpack();
- next0();
- pq_tk[pq_n] = tk; pq_tv[pq_n] = tv; pq_gl[pq_n] = tglue; pq_ln[pq_n] = tpack(); pq_n++;
- tk = a; tv = b; tglue = c; tunpack(e);
- }
- pk_tk = pq_tk[k]; pk_tv = pq_tv[k];
- return 0;
-}
-// look at the next token without consuming it.
-int peek() { return peekat(0); }
-// the lowest level of "next token": from a token list if one is the current
-// source, otherwise from the lexer. in check mode after an abort it returns
-// end of file forever, which unwinds every parsing loop without any special
-// case in them.
-int next0() {
- word f, q; int i;
- 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];
- if (i < f[3]) {
- q = f[1];
- tk = q[1 + i * 3];
- tv = q[2 + i * 3];
- tunpack(q[3 + i * 3]);
- f[2] = i + 1;
- return 0;
- }
- if (!f[4]) { tk = T_EOF; tv = 0; return 0; }
- src_pop(); // a fed list ran out: continue with the token that followed it
- return 0;
-}
-// token tests. s is an interned spelling, so these are pointer compares.
-int is_op(int s) { return tk == T_OP && tv == s; }
-// true when the current token is the name s.
-int is_id(int s) { return tk == T_ID && tv == s; }
-// consume the current token if it is the operator or name s, and say whether it did.
-int accept(int s) {
- if ((tk == T_OP || tk == T_ID) && tv == s) { next(); return 1; }
- return 0;
-}
-// accept(s) consumes the token if it is s. expect reports "expected s" at the
-// end of the previous token (dgprev) when it is missing, and goes into panic.
-int expect(int s) {
- if (!accept(s)) {
- if (!panic && !dry) {
- dgprev = 1;
- errat();
- dgprev = 0;
- rt.put_s("expected '"); rt.put_s(s); rt.put_s("', found "); tokshow();
- errend();
- }
- panic = 1;
- if (tk == T_EOF) giveup();
- }
- return 0;
-}
-// read an identifier and return its interned handle
-int name() {
- int n = tv;
- if (tk != T_ID) diefound("expected a name");
- nameoff = toff;
- next();
- return n;
-}
-
-// ---- 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(char* s) {
- int start = seqtop, n, neg, v, k, c;
- while (1) {
- while (*s == 32) s++;
- if (!*s) break;
- n = 0;
- while (s[n] && s[n] != 32) { n = token_char(n, s[n]); }
- tbuf[n] = 0;
- s = s + n;
- reserve_R_SEQ(seqtop + 2);
- neg = tbuf[0] == '-' && isdig(tbuf[1]);
- if (isdig(tbuf[neg])) {
- v = 0; k = neg;
- while (tbuf[k]) { v = v * 10 + tbuf[k] - '0'; k++; }
- if (neg) v = 0 - v;
- if ((v >> 62) != 0 && (v >> 62) != -1) { R_SEQ[seqtop] = SEQWORD; R_SEQ[seqtop + 1] = v; seqtop = seqtop + 2; }
- else { R_SEQ[seqtop] = v + v + 1; seqtop++; }
- } else if (n == 1 && (c = mnemidx(tbuf[0])) >= 0) {
- R_SEQ[seqtop] = c + c; seqtop++;
- } else {
- R_SEQ[seqtop] = SEQREF; R_SEQ[seqtop + 1] = intern(tbuf); seqtop = seqtop + 2;
- }
- }
- return start;
-}
-// the index of an assembler mnemonic in the table, or -1 when there is none.
-int mnemidx(int c) {
- int i = 0;
- while (mnem[i]) {
- if (mnem[i] == c) return i;
- i++;
- }
- return -1;
-}
-// emit n words of a stored sequence, resolving its references.
-int emitseq(int a, int n) {
- int e = a + n, s;
- while (a < e) {
- if (R_SEQ[a] == SEQWORD) { lit(R_SEQ[a + 1]); a = a + 2; }
- else if (R_SEQ[a] == SEQREF) {
- s = lookup(R_SEQ[a + 1]);
- if (s < 0) s = fwdfn(R_SEQ[a + 1]);
- if (skind[s] == K_FN) {
- fnref(s);
- } else if (skind[s] == K_ENH) op(XOP0 + sval[s]); else lit(sval[s]);
- a = a + 2;
- } else { emit(R_SEQ[a]); a++; }
- }
- return 0;
-}
-// add an operator name to the table of known operators, once.
-int addkop(int p) {
- int i = 0;
- while (i < nkop) {
- if (kops[i] == p) return 0;
- i++;
- }
- reserve_operators(nkop + 1);
- kops[nkop] = p;
- nkop++;
- return 0;
-}
-// find a binary operator by its name.
-int binfind(int p) {
- int i = nbin;
- while (i) {
- i--;
- if (bname[i] == p) return i;
- }
- return -1;
-}
-// find a unary operator by its name.
-int unfind(int p) {
- int i = nun;
- while (i) {
- i--;
- if (uname[i] == p) return i;
- }
- return -1;
-}
-// find an assignment operator by its name.
-int asgfind(int p) {
- int i = nasg;
- while (i) {
- i--;
- if (aname[i] == p) return i;
- }
- return -1;
-}
-// 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(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;
- // && (-1) and || (-2) short-circuit: they bind below |, and && above ||, as in c
- if (prec < 0) { bprec[i] = 3 + prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; }
- bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i];
- addkop(p);
- // and its compound assignment, x op= y
- n = rt.len_w(nm);
- while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; }
- 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) {
- if (asgfind(a) < 0) { reserve_assignment(nasg + 1); aname[nasg] = a; nasg++; }
- abin[asgfind(a)] = i;
- addkop(a);
- }
- return i;
-}
-// register a unary operator and the sequence it expands to.
-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];
- addkop(p);
- return i;
-}
-// register a keyword with its handler and 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;
-}
-
-// 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(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) { 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;
- 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) { rt.copy_w(rsrec + i * RS_W + 4, f, 5); return 0; } }
- return 0;
-}
-int af_warning(int place) {
- int sv;
- if (dry || panic || chkabort || stubgen || !live) return 0;
- sv = tpack(); tunpack(place);
- if (cmode) dg_open(2);
- 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 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;
- 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) {
- 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;
- 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) {
- if (f && !dry && live) { f[1] = 2; f[4] = place; }
- return 0;
-}
-int af_close(int save) {
- int i = save; word f;
- if (dry) return 0;
- while (i < nloc) {
- f = laaddr[i]; if (f && f[2] == 1) af_end(f, tpack());
- f = lafact[i];
- if (f && fchk && lstate[i] == S_M && lown[i] && !local_captured[i]) af_end(f, tpack());
- i++;
- }
- return 0;
-}
-// the analyzer's model of a local. locals are numbered in declaration order
-// and every fact about one lives in a parallel array indexed by that number
-// (see the data region). for ownership the ones that matter are:
-// lstate what the local holds right now:
-// S_PLAIN nothing tracked (a number, or untracked memory)
-// S_M owns an object; the compiler frees it when the block ends
-// S_U owns one that has to leave the block (return it, make
-// it the block's value, or assign it to the enclosing block)
-// S_D was handed in to be given away, and hasn't been yet
-// S_DEAD the object is gone (given away); any use is an error
-// S_CHILD the object belongs to a container, lowner says which local
-// S_BORROW a .m parameter: usable but not freeable or storable
-// lhome the block depth that owns the object (fdepth where it was declared)
-// lown 1 once the local has owned an object, lused the line of its last use
-// ltag the tag written on the declaration, plus the flag bits (float,
-// byte string, o3 register, hint bits)
-// fchk says the function being compiled is analyzed at all (unchecked turns
-// it off) and live says the code just emitted is reachable. ownership rules
-// return when fchk is off; definite storage warnings also inspect unchecked code.
-// findloc looks a name up among the current function's locals, innermost first.
-// name heads and shadow links keep declaration and use lookup independent of
-// the number of earlier locals. dropping a scope restores the previous heads.
-int local_bucket(int n, int cap) { return ((n ^ (n >> 16)) * 0x9E37) & (cap - 1); }
-int local_key(int n) {
- int k = local_bucket(n, lkeycap);
- while (lkeys[k] && lkeys[k] != n) k = (k + 1) & (lkeycap - 1);
- return k;
-}
-int local_bind(int n, int i) {
- int k, j, at, cap; word keys, heads;
- if (lkeyn + lkeyn >= lkeycap) {
- keys = lkeys; heads = lheads; cap = lkeycap * 2;
- lkeys = chk_resize(0, 0, cap); lheads = chk_resize(0, 0, cap);
- j = 0;
- while (j < lkeycap) {
- if (keys[j]) { at = local_bucket(keys[j], cap); while (lkeys[at]) at = (at + 1) & (cap - 1); lkeys[at] = keys[j]; lheads[at] = heads[j]; }
- j++;
- }
- lkeycap = cap;
- }
- k = local_key(n);
- if (!lkeys[k]) { lkeys[k] = n; lheads[k] = -1; lkeyn++; }
- lprev[i] = lheads[k]; lheads[k] = i; return 0;
-}
-int local_drop(int save) {
- int i = nloc, k;
- while (i > save) { i--; k = local_key(lname[i]); lheads[k] = lprev[i]; }
- nloc = save; return 0;
-}
-int findloc(int n) {
- int k = local_key(n), i;
- if (!lkeys[k]) return -1;
- i = lheads[k];
- while (i >= nloc) i = lprev[i];
- if (i >= lbase) return i;
- return -1;
-}
-// declare a local: take the next slot in the frame (fslots) and reset every
-// per-local fact so a reused index never inherits the old variable's state
-int newloc(int n) {
- int i = nloc;
- 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;
- larr[i] = 0;
- lown[i] = 0;
- ltype[i] = tword;
- 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.
-// the table is dropped as scopes close, so a reused slot does not keep the old fact.
-word getlo(int i) {
- int k = 0;
- while (k < nfact) {
- if (floc[k] == i) return flo[k];
- k++;
- }
- return -1;
-}
-// the length of the recorded fact for line i, or -1 when there is none.
-word getln(int i) {
- int k = 0;
- while (k < nfact) {
- if (floc[k] == i) return flen[k];
- k++;
- }
- return -1;
-}
-// record the range lo to lo+ln for line i.
-int putfact(int i, int lo, int ln) {
- int k = 0;
- while (k < nfact) { if (floc[k] == i) { flo[k] = lo; flen[k] = ln; return 0; } k++; }
- if (lo < 0 && ln < 0) return 0;
- if (nfact >= factcap) {
- floc = chk_resize(floc, nfact, factcap * 2); flo = chk_resize(flo, nfact, factcap * 2); flen = chk_resize(flen, nfact, factcap * 2);
- factcap = factcap * 2;
- }
- floc[nfact] = i; flo[nfact] = lo; flen[nfact] = ln; nfact++;
- return 0;
-}
-// drop the recorded facts from line from onward.
-int fdrop(int from) {
- int k = 0, w = 0;
- while (k < nfact) {
- if (floc[k] < from) {
- if (w != k) { floc[w] = floc[k]; flo[w] = flo[k]; flen[w] = flen[k]; }
- w++;
- }
- k++;
- }
- nfact = w;
- return 0;
-}
-// a loop fact stores three full local indices: the
-// induction variable, its bound and the array the bound is the length of. a
-// loop "for (i = 0; i < len(a); i++)" records (i, bound, a) in lpfact, which
-// is how proven() knows that a[i] needs no bounds check inside it.
-int pfact(int iv, int bd, int ar) { word p = talloc(3); p[0] = iv; p[1] = bd; p[2] = ar; return p; }
-// the induction variable of a loop proof.
-int p_iv(int w) { return w[0]; }
-// the array's bound in a loop proof.
-int p_bd(int w) { return w[1]; }
-// the array a loop proof refers to.
-int p_ar(int w) { return w[2]; }
-// a store to the induction variable, the bound, or the array ends the proof
-int bpoison(int i) {
- int k = lsp, w;
- while (k > loopbase) {
- k--;
- w = lpfact[k];
- if (w >= 0 && (p_iv(w) == i || p_bd(w) == i || p_ar(w) == i)) lpfact[k] = -1;
- }
- return 0;
-}
-// 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;
- putfact(i, lo, ln);
- bpoison(i);
- return 0;
-}
-// i >= 0 and i < len(a), proved by the loop test and a step of +1
-int proven(int a, int ix) {
- int k = lsp, w;
- if (ltype[a][0] != TY_DYN || !ltype[a][3] || tbytes(ltype[a][4])) return 0;
- while (k > loopbase) {
- k--;
- w = lpfact[k];
- if (w >= 0 && p_iv(w) == ix && p_ar(w) == a) return 1;
- }
- return 0;
-}
-// follow child links up to the local that really owns the object
-int lroot(int i) {
- while (lstate[i] == S_CHILD) i = lowner[i];
- return i;
-}
-// lend a local owner to global s for the rest of the current block.
-int lend_add(int s, int i) {
- if (nlend * 3 + 3 > lendcap) { if (!lendcap) lendcap = 48; else lendcap = lendcap * 2; lendrec = chk_resize(lendrec, nlend * 3, lendcap); }
- lendrec[nlend * 3] = s; lendrec[nlend * 3 + 1] = i; lendrec[nlend * 3 + 2] = fdepth; nlend++;
- return 0;
-}
-// remove entry k, keeping the others in order.
-int lend_remove(int k) {
- if (k + 1 < nlend) rt.copy_w(lendrec + k * 3, lendrec + k * 3 + 3, (nlend - k - 1) * 3);
- nlend--;
- return 0;
-}
-// an assignment to global s in the block that lent it ends the lend.
-int lend_clear(int s) {
- int k = nlend;
- while (k > 0) { k--; if (lendrec[k * 3] == s && lendrec[k * 3 + 2] == fdepth && lendrec[k * 3 + 1] >= lbase) lend_remove(k); }
- return 0;
-}
-// report local i still lent to the global of entry k.
-int lend_err(int k, char* msg) {
- int i = lendrec[k * 3 + 1];
- if (!fchk || !live || panic || dry) return 0;
- errat(); rt.put_s(lname[i]); rt.put_s(": is lent to global "); rt.put_s(sname[lendrec[k * 3]]); rt.put_s(msg); errend();
- return 0;
-}
-// local i is about to be given away or released: no global may still borrow it.
-int lend_check(int i) {
- int k = 0;
- while (k < nlend) {
- if (lendrec[k * 3 + 1] == i && i >= lbase) { lend_err(k, "; assign the global again before giving the local away"); lend_remove(k); }
- else k++;
- }
- return 0;
-}
-// leaving every block at depth dep or deeper: a lend made there must have ended.
-int lend_exit(int dep) {
- int k = 0;
- while (k < nlend) {
- if (lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) lend_err(k, "; assign the global again before the block that lent it ends");
- k++;
- }
- return 0;
-}
-// forget the lends made at depth dep or deeper, or by locals from index from onward.
-int lend_drop(int dep, int from) {
- int k = 0;
- while (k < nlend) {
- if ((lendrec[k * 3 + 2] >= dep && lendrec[k * 3 + 1] >= lbase) || lendrec[k * 3 + 1] >= from) lend_remove(k);
- else k++;
- }
- return 0;
-}
-// 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);
- lend_check(i);
- 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 reference count for diagnostics.
-int luse(int i) {
- lused[i] = tline;
- if ((ltag[i] >> 56) < 100) ltag[i] = ltag[i] + LCNT1;
- return 0;
-}
-// touch records a use of a tracked local and rejects dead objects. a d object
-// read in an inner block moves there, where the block owns it as m.
-int touch(int i) {
- int r;
- luse(i);
- ltag[i] = ltag[i] | LREAD;
- if (!fchk) return 0;
- r = lroot(i);
- luse(r);
- if (lstate[r] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; }
- // a d object that shows up in a deeper block is given to that block
- if (lstate[r] == S_D && lhome[r] < fdepth) { lhome[r] = fdepth; lstate[r] = S_M; lown[r] = 1; }
- return 0;
-}
-// an expression of class c used as a plain value (a lend, an operand). a new
-// object can't be used that way, since nothing would own it.
-int plainuse(int c) {
- if (c == C_FRESH) cerr("a new object can't be used as a plain value; bind it to a name first");
- if (c >= C_VAR) touch(c - C_VAR);
- return C_PLAIN;
-}
-// the size class of an n word object, for a constant-size new
-int szclass(int n) {
- int k = 3;
- 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) {
- 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]));
- callname(s_recycle_leaf);
- }
- else callname(s_recycle); op(O_DROP); lit(0); laddr(lslot[i]); op(O_ST); return 0;
-}
-// leaving every block deeper than or equal to dep: free what is owned, and
-// complain about what should have left already. final means the block really
-// ends here rather than an early exit.
-int exit_vars(int dep, int final) {
- int i = nloc, st;
- if (!fchk || !live) return 0;
- lend_exit(dep);
- while (i > lbase) {
- i--;
- if (lhome[i] < dep) continue;
- st = lstate[i];
- if (st == S_M) {
- 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)");
- }
- else if (st == S_D) cerrn(i, "d object was never given away");
- }
- return 0;
-}
-// an m object has to be used somewhere in the block that owns it, on any path
-// block ends.
-int unused_check(int from) {
- while (from < nloc) {
- 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, 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;
-}
-// report the locals declared from token from onward that were never used.
-int local_hints(int from) {
- int i = from, f;
- if (!cmode || dry || panic || chkabort || stubgen) return 0;
- while (i < nloc) {
- f = ltag[i];
- 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] && !rt.eq_w(lname[i], "_")) { // _ discards its value
- if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL);
- else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) 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++;
- }
- return 0;
-}
-// block_close runs the analyzer's end-of-block work in order: hints for the
-// block's variables, o3 registers go back to the pool, an m object that was
-// never used is an error, exit_vars frees what is owned and complains about
-// what should have left, aliases whose owner dies are marked dead, then the
-// block's locals are dropped. unused_check comes first because an unused m
-// object is an error, not a free.
-int block_close(int save) {
- int i = save, k;
- af_close(save);
- local_hints(save);
- if (o3on) {
- while (i < nloc) {
- k = (ltag[i] >> 8) & 7;
- if ((ltag[i] & 16) && k && no3free < 8) {
- if (getflt(i)) k = 0 - k;
- o3free[no3free] = k;
- no3free++;
- }
- i++;
- }
- }
- i = lbase;
- unused_check(save);
- exit_vars(fdepth, 1);
- lend_drop(fdepth, save);
- alias_end(fdepth, save);
- while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; }
- local_drop(save);
- fdrop(save);
- td_end(fdepth);
- return 0;
-}
-
-// an alias in an outer block whose owner is about to disappear dies with it
-int alias_end(int dep, int save) {
- int i, r;
- if (!fchk) return 0;
- i = lbase;
- while (i < save) {
- if (isalias(i)) {
- r = lroot(i);
- if (r >= save || lhome[r] >= dep || lstate[r] == S_DEAD) lstate[i] = S_DEAD;
- }
- i++;
- }
- return 0;
-}
-// snapshots: [lbase] [nloc] then state, owner, home per local
-// save the states of all current locals; returns the snapshot offset
-int snap_push() {
- int b = snaptop, i = lbase; word p;
- af_forget();
- reserve_R_SNAP(b + 2 + 3 * (nloc - lbase));
- p = R_SNAP + b;
- p[0] = lbase; p[1] = nloc;
- p = p + 2;
- while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; }
- snaptop = p - R_SNAP;
- return b;
-}
-// drop the snapshot at base b.
-int snap_pop(int b) { snaptop = b; return 0; }
-// restore the local states saved in snapshot b.
-int snap_restore(int b) {
- word p = R_SNAP + b; int i = p[0], e = p[1];
- af_forget();
- if (e > nloc) e = nloc;
- p = p + 2;
- while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; }
- return 0;
-}
-// true for a plain or dead local state, which merge to dead.
-int empty(int s) { return s == S_PLAIN || s == S_DEAD; }
-// compare the current states with a snapshot: -1 if they agree, else the
-// first local that differs. plain and dead agree and merge to dead.
-int snap_cmp(int b) {
- word p = R_SNAP + b; int i = p[0], e = p[1], s;
- if (e > nloc) e = nloc;
- p = p + 2;
- while (i < e) {
- s = lstate[i];
- if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; }
- else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i;
- p = p + 3; i++;
- }
- return -1;
-}
-// the state names used in join and loop messages
-int statename(int s) {
- if (s == S_PLAIN) return "empty";
- if (s == S_M) return "m";
- if (s == S_U) return "u";
- if (s == S_D) return "d";
- if (s == S_DEAD) return "given away";
- if (s == S_CHILD) return "owned by a container";
- return "borrowed";
-}
-// 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(); 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.
-
-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.
-word jst(word p, int i) {
- if (i >= p[1]) return S_PLAIN;
- return p[2 + 3 * (i - p[0])];
-}
-// the owner recorded in a jump's state for local i.
-word jow(word p, int i) { return p[3 + 3 * (i - p[0])]; }
-// the jump's marker for local i, in the same record as jow.
-word jhm(word p, int i) { return p[4 + 3 * (i - p[0])]; }
-// the states at a break, with the jump to patch: [site] then a snapshot
-word exit_rec(int site) {
- word p; int i = lbase; word q;
- reserve_R_EXIT(extop + 3 + 3 * (nloc - lbase)); p = R_EXIT + extop;
- p[0] = site; p[1] = lbase; p[2] = nloc;
- q = p + 3;
- while (i < nloc) { q[0] = lstate[i]; q[1] = lowner[i]; q[2] = lhome[i]; q = q + 3; i++; }
- extop = q - R_EXIT;
- return p;
-}
-// jps is the set of paths about to be joined. jadd adds one, jsame compares
-// what two paths say about a local, and join folds them all into the current
-// states (reporting any local that disagrees)
-int jadd(int p) {
- if (njps >= jpscap) { jps = chk_resize(jps, njps, jpscap * 2); jpscap = jpscap * 2; }
- jps[njps] = p;
- njps++;
- return 0;
-}
-// true when the states of two jumps agree for local i.
-word jsame(int p, int q, int i) {
- int s = jst(p, i), t = jst(q, i);
- if (empty(s) && empty(t)) return 1;
- return s == t && jhm(p, i) == jhm(q, i) && (s != S_CHILD || jow(p, i) == jow(q, i));
-}
-// join the paths in jps over the locals [lbase, n0) into the current states
-int join(int n0, int what) {
- int i = lbase, j, p, s, s0, dead, okdead, bad;
- af_forget();
- if (!njps) return 0;
- while (i < n0) {
- p = jps[0]; s0 = jst(p, i);
- dead = 0; okdead = 1; bad = -1;
- j = 0;
- while (j < njps) {
- s = jst(jps[j], i);
- if (s == S_DEAD || s == S_D) dead = 1;
- if (s == S_U || s == S_BORROW) okdead = 0;
- if (!jsame(p, jps[j], i)) bad = j;
- j++;
- }
- lstate[i] = s0; lowner[i] = jow(p, i); lhome[i] = jhm(p, i);
- if (bad < 0) {
- if (empty(s0)) {
- j = 0;
- while (j < njps) {
- if (jst(jps[j], i) == S_DEAD) lstate[i] = S_DEAD;
- j++;
- }
- }
- } else if (dead && okdead) lstate[i] = S_DEAD;
- else if (fchk && !panic && !dry) {
- 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++;
- }
- return 0;
-}
-// on path p: count (and with doit, emit) the frees for what the join made dead but p still owns
-int fixups(int p, int n0, int doit) {
- int i = lbase, n = 0, s;
- if (!fchk) return 0;
- while (i < n0) {
- s = jst(p, i);
- if (lstate[i] == S_DEAD && (s == S_M || s == S_D)) { n++; if (doit) freevar(i); }
- i++;
- }
- return n;
-}
-// a jump back to the top of a loop: whatever the loop owns has to be as it was
-int backedge(int s0) {
- word p = R_SNAP + s0; int i = p[0], e = p[1], s;
- if (!fchk || !live) return 0;
- if (e > nloc) e = nloc;
- while (i < e) {
- s = lstate[i];
- if (empty(s) && empty(jst(p, i))) { if (jst(p, i) == S_DEAD) lstate[i] = S_DEAD; }
- else if (s != jst(p, i) || lhome[i] != jhm(p, i) || (s == S_CHILD && lowner[i] != jow(p, i))) {
- if (s == S_DEAD || s == S_D) {
- cerrn(i, "is given away inside a loop that comes back around to it; give it away on a path that leaves the loop");
- }
- else if (!panic && !dry) {
- errat();
- 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++;
- }
- return 0;
-}
-// joins two paths that end at the same point, like the branches of if/else:
-// path a jumped (through the hole at ja) and path b falls through to here
-// (the then path's snapshot is sa; livea / livebv say whether each path can
-// reach the join at all, since a path that returned is not part of it)
-int join2(int sa, int livea, int ja, int livebv, int n0, int what) {
- int sb, nt, cj;
- sb = snap_push();
- njps = 0; if (livea) jadd(R_SNAP + sa); if (livebv) jadd(R_SNAP + sb);
- join(n0, what);
- if (livebv) fixups(R_SNAP + sb, n0, 1);
- nt = 0; if (livea) nt = fixups(R_SNAP + sa, n0, 0);
- if (nt) { cj = hole(); op(O_JMP); setlit(ja, here); fixups(R_SNAP + sa, n0, 1); setlit(cj, here); }
- else setlit(ja, here);
- live = livea || livebv;
- 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 ----
-
-// recast: x as u / x as d / x as m
-int recast(int c, int t) {
- int i, st;
- if (c == C_FRESH && t == S_M) return C_PLAIN; // m*f(): the unnamed result is let go on purpose, so it can be discarded
- if (!fchk) return c;
- if (c < C_VAR) { cerr("only a named owned object can be recast; this expression has no owner"); return c; }
- i = c - C_VAR;
- st = lstate[i];
- luse(i);
- if (st == S_CHILD) cerrn(i, "belongs to a container; recast the container instead");
- else if (st == S_BORROW) cerrn(i, "is borrowed and can't be recast");
- else if (st == S_PLAIN) cerrn(i, "isn't an owned object");
- else if (st == S_DEAD) cerrn(i, "used after it was given away");
- else if (t == S_M && st != S_M) cerrn(i, "can't be recast back to m");
- else lstate[i] = t;
- return c;
-}
-// 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");
- } else {
- xgive = 1;
- }
- return 0;
- }
- 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
- if (st == S_D) {
- cerrn(i, "is d; pass it to a d parameter (lending it would lose it)");
- return 0;
- }
- touch(i);
- 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");
- else cerrn(i, "is borrowed and can't be given away");
- return 0;
-}
-// child, owner on the stack (owner on top). write owner into the child's
-// header and leave the child. a shared child keeps the owner it has. a null
-// child's address is out of range and the store does not land.
-// o3 code can't call own(), so this is own() inlined: skip the store when the
-// child is shared (flag bit 8) and otherwise write the owner into the child's
-// header word p[-2]
-int o3own() {
- int sk, st;
- lit(1); op(O_PICK); lit(4); op(O_SUB); op(O_LD); lit(8); op(16);
- sk = hole(); op(O_JZ);
- op(O_DROP);
- st = hole(); op(O_JMP);
- setlit(sk, here);
- lit(1); op(O_PICK); lit(2); op(O_SUB); op(O_SWAP); op(O_SWAP); op(O_ST);
- setlit(st, here);
- return 0;
-}
-// storing a child into container local p: emit the call that records the
-// 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) {
- 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);
- op(O_LD);
- }
- if (o3on && !mdbg) o3own();
- else callname(s_own);
- if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); }
- return 0;
-}
-// 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, 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) {
- cerrn(j, "used after it was given away");
- return C_PLAIN;
- }
- if (k == LV_LOCAL) {
- st = lstate[i];
- 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 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; }
- else lstate[i] = S_PLAIN;
- return C_PLAIN;
- }
- if (!empty(st)) {
- cerrn(i, "still owns an object; overwriting it would leak it");
- return C_PLAIN;
- }
- if (rc == C_FRESH) {
- 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[j] >= lhome[i]
- && !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;
- end_owner(j);
- luse(j); ev_put(j, EV_UP, i);
- if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
- return C_PLAIN;
- }
- if (js == S_U) {
- cerrn(j, "is u; it can only be handed to a variable of the enclosing block, or returned");
- }
- else cerrn(j, "is an owned object and can't be copied into another variable");
- return C_PLAIN;
- }
- if (k == LV_ELEM) {
- r = lroot(i);
- st = lstate[r];
- touch(i);
- if (rc == C_FRESH) { emitown(i); return C_PLAIN; }
- if (j < 0) return C_PLAIN;
- js = lstate[j];
- if (js == S_CHILD) cerrn(j, "already belongs to a container");
- else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored");
- else if (st == S_BORROW) {
- if (js == S_U) {
- cerrn(j, "is u; it has to leave through the block above, not into a borrowed object");
- }
- 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); 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 (k == LV_GLOBAL) lend_clear(i);
- if (j >= 0 && k == LV_GLOBAL && xlend == j && lstate[j] == S_M && lown[j]) { xlend = -1; touch(j); lend_add(i, j); return C_PLAIN; }
- xlend = -1;
- if (j >= 0) {
- 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;
-}
-// return c: an owned object (d, or u after u*x) goes up with the return, and the
-// function's return is owned: declared with .u, or inferred from this return when
-// the function has no .u (its callers compiled after this point see it). an m
-// 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;
- 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; }
- if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does
- if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) {
- 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) { end_owner(i); luse(i); ev_put(i, EV_RETURN, -1); }
- else cerrn(i, "has to be u to be returned");
- }
- return 0;
- }
- if (c == C_FRESH) cerr("returns a new object; give the function a .u return type");
- else if (i >= 0) cerrn(i, "is owned and can't be returned from a function whose return type isn't .u");
- return 0;
-}
-// the last expression of a block expression
-int handup(int c) {
- int i;
- if (!fchk) return C_PLAIN;
- 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); 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;
-}
-// throw away an expression result, which is an error for a fresh object.
-int discard(int c) {
- if (c == C_FRESH) cerr("a new object (or a u result) can't be thrown away");
- else if (c >= C_VAR) touch(c - C_VAR);
- xbor = -1;
- return 0;
-}
-
-// ---- 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
-// stay in memory after compiling, so programs can inspect them.
-// t[0] kind t[1] size in words t[2] name t[3] n t[4] a t[5] b t[6] c t[7..] extra
-// ptr: a = element arr: a = element, n = length
-// fn: a = return type, n = parameter count, b = signature (u m d tags), extra = parameter types
-// struct: n = field count (-1 until laid out), a = fields (name, type, offset),
-// b = generic template, c = type arguments [n, t1, t2 ...]
-// var: a type parameter, n = its index
-// gen: a generic struct: n = parameter count, a = fields (name, type), b = field count, c = parameter names
-
-
-// the type area is a bump allocator of its own (tytop .. R_TYEND). talloc
-// hands out zeroed words, tnew makes a record and adds it to the list of all
-// types (R_TY), and the constructors below look that list up first, which is
-// 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.
-int talloc(int n) {
- int p = tytop;
- if (tytop + n > tyend) {
- p = chk_resize(0, 0, n + 16385); p[0] = typeblocks; typeblocks = p;
- p++; tytop = p; tyend = p + n + 16384;
- }
- tytop = tytop + 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, 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 && !treadonly(t)) return t; i++; }
- t = tnew(TY_PTR, 1, 0, 0); t[4] = e;
- return t;
-}
-// sized integers (i8 u8 i16 u16 i32 u32) are TY_INT records with t[5] = bytes,
-// t[6] bit 0 = signed, and t[4] = the "packed view" twin: the same name with
-// t[6] bit 1 set. a variable, parameter or field of a sized type is an
-// ordinary word that is cut to size whenever it is stored. an element of an
-// array or a pointer of a sized type is stored packed, 8, 4 or 2 to a word,
-// and indexing yields the twin, whose lvalue is a byte address.
-word tbytes(word t) { if (t[0] == TY_INT || t[0] == TY_FLOAT) return t[5]; return 0; }
-// 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 (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(char* name) {
- int s = lookup(intern(name));
- if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); return 0; }
- callname(intern(name));
- return 0;
-}
-// 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 && !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(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;
- addsym(t[2], K_TYPE, t);
- return t;
-}
-// the function type with this return, parameter list and signature.
-word tfn(int ret, int np, word ps, int sig) {
- int i = 0, j; word t;
- while (i < ntl) {
- t = typelist[i];
- if (t[0] == TY_FN && t[4] == ret && t[3] == np && t[5] == sig) {
- j = 0; while (j < np && t[7 + j] == ps[j]) j++;
- if (j == np) return t;
- }
- i++;
- }
- t = tnew(TY_FN, 1, 0, np); t[4] = ret; t[3] = np; t[5] = sig;
- rt.copy_w(t + 7, ps, np);
- return 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 && !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, 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;
- return t;
-}
-// the instance of generic struct g with type arguments args[0..n). found if it
-// already exists, otherwise created unlaid-out (n = -1), so that mentioning
-// vec<T> inside vec<T>'s own methods does not recurse; tresolve lays it out.
-word tinst(word g, word args, int n) {
- int i = 0, j; word t, v;
- while (i < ntl) {
- t = typelist[i];
- if (t[0] == TY_STRUCT && t[5] == g) {
- v = t[6]; j = 0; while (j < n && v[1 + j] == args[j]) j++;
- if (j == n) return t;
- }
- i++;
- }
- 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; 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];
- 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) {
- ft = subst(f[i + i + 1], bs);
- fs[i * 3] = f[i + i]; fs[i * 3 + 1] = ft; fs[i * 3 + 2] = off;
- off = off + tsz(ft);
- i++;
- }
- t[4] = fs; t[1] = off; t[3] = n;
- return t;
-}
-// size of a type in words, laying out a generic instance first if it needs it
-word tsz(word t) {
- if (t[0] == TY_STRUCT && t[3] < 0) tresolve(t);
- return t[1];
-}
-// replace type parameters with what they were bound to (unbound ones become word)
-word subst(word t, word bs) {
- 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;
- while (i < n) { v[i] = subst(t[7 + i], bs); i++; }
- return tfn(subst(t[4], bs), n, v, t[5]);
- }
- if (k == TY_STRUCT && t[5]) {
- vec = t[6];
- n = vec[0];
- i = 0;
- while (i < n) { v[i] = subst(vec[1 + i], bs); i++; }
- return tinst(t[5], v, n);
- }
- return t;
-}
-// small type predicates. an array decays to a pointer to its element where a
-// value is wanted, and telem is what indexing a pointer or array yields.
-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 || 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;
-}
-// can a value of type src go where dst is expected?
-// type compatibility, used by the optional typechecker. word is compatible with
-// everything (untyped code stays legal), numbers convert freely, pointers need
-// 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 (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;
- if (isen(dst)) return 0;
- }
- if (isnum(dst) && isnum(src)) return 1;
- if (isfl(dst) && isfl(src)) return 1;
- src = decay(src);
- if (dst[0] == TY_PTR && src[0] == TY_PTR) {
- if (dst[4] == tword || src[4] == tword || dst[4] == tvoid || src[4] == tvoid) return 1;
- return dst[4] == src[4];
- }
- if ((dst[0] == TY_PTR || dst[0] == TY_FN || dst[0] == TY_DYN) && xzero) return 1; // a literal 0 is a null pointer
- if (dst[0] == TY_DYN && src[0] == TY_DYN && dst[4] == src[4]) return dst[3] || !src[3]; // a growable array can't hold a fixed one
- return dst == src;
-}
-// match pattern type p (which may contain type parameters) against actual type
-// a, filling the bindings bs. this is how a call to a generic function infers
-// 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++; }
- return 1;
- }
- return compat(p, a);
-}
-// 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) { // postfix pointers; only function pointers keep the display-only prefix form
- v = t[4][0];
- if (v == TY_FN) { rt.put_c('*'); return tprint(t[4]); }
- tprint(t[4]); rt.put_c('*');
- return 0;
- }
- 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) 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) rt.put_s(", ");
- tprint(t[7 + i]);
- if (i < 12 && tparamtag(t, i) == 1) rt.put_s(".m");
- if (i < 12 && tparamtag(t, i) == 2) rt.put_s(".d");
- i++;
- }
- rt.put_c(')');
- return 0;
- }
- if (t[2]) rt.put_s(t[2]); else rt.put_s("?");
- if (k == TY_STRUCT && t[5]) {
- 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;
-}
-// typechecker errors. they only fire when "typecheck on" is set, in reachable
-// code, and never during a dry run.
-int tcerr(int what, int want, int got) {
- if (!tc_on || !live || dry || panic) return 0;
- 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(); rt.put_s("type error: "); rt.put_s(what); rt.put_s(", got "); tprint(got);
- errend();
- return 0;
-}
-// the type parameters in scope (tpn): a generic function or struct body sees
-// T as a type. istypename says whether a name starts a type here, which is how
-// the parser tells a declaration from an expression.
-int tpfind(int n) {
- int i = ntp;
- while (i) {
- i--;
- if (tpn[i] == n) return i;
- }
- return -1;
-}
-// true when the name n is a type, a type parameter or a generic type.
-word istypename(char* n) {
- int s;
- 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;
- s = lookup(n);
- return s >= 0 && skind[s] == K_TYPE;
-}
-// the closing > of a type argument list; splits >> in two
-int closeangle() {
- if (is_op(s_shr)) { tv = s_gt; return 0; }
- 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 ----
-
-// grammar, outermost first:
-// ptype a type, then any number of (params): int(int, int) is a function type
-// 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, tags, tag; word[24] ps;
- while (is_op(s_lp)) {
- next(); np = 0; tags = 0;
- if (!is_op(s_rp)) {
- while (1) {
- if (np >= 24) fatal("too many parameters");
- 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 + 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(), 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; 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) { 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;
- }
- if (is_id(s_fn)) {
- next();
- if (!is_op(s_lp)) die("fn as a type needs its parameters: fn(int)");
- return tword;
- }
- if (tk != T_ID) { diefound("expected a type"); return tword; }
- n = tv;
- if (ginst) {
- i = gfind(n);
- if (i >= 0) { next(); return gib[i]; }
- }
- i = tpfind(n);
- if (i >= 0) { next(); return tvar(i, n); }
- s = source_lookup(n);
- // T in T first<T>(...)
- if (tvauto && (s < 0 || skind[s] != K_TYPE) && ntp < 8) {
- tpn[ntp] = n;
- lsmark(2, n);
- ntp++;
- next();
- 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;
- expect(s_lt);
- while (1) {
- if (na >= 8) die("too many type arguments");
- args[na] = ptype();
- if (args[na] == tvoid) serr("a type argument can't be void");
- na++;
- if (!accept(s_comma)) break;
- }
- closeangle();
- if (na != t[3]) serrn(n, "wrong number of type arguments");
- return tinst(t, args, na);
-}
-// does a declaration start here? (a type, or a bare .tag)
-// does a declaration start at the current token? (a type name, [, or a bare .tag)
-int tstart() {
- if (tk == T_OP) {
- if (tv == s_lk || tv == s_dot) return 1;
- return 0;
- }
- if (tk != T_ID) return 0;
- if (tv == s_fn) return 1;
- if (!istypename(tv)) return 0;
- // Type.name(...) at the start of a statement is a static call, not a declaration:
- // a declaration's dot is followed by a tag, u, m or d
- peekat(0);
- if (pk_tk == T_OP && pk_tv == s_dot) {
- peekat(1);
- if (pk_tk == T_ID && pk_tv != s_u && pk_tv != s_m && pk_tv != s_d) return 0;
- }
- return 1;
-}
-// [type] [.tag]
-// parse a declaration's [type] [.tag] into dty and dtag
-int dtype() {
- dty = 0; dtag = 0;
- if (!is_op(s_dot)) dty = ptype();
- if (accept(s_dot)) {
- if (tv == s_m) {
- dtag = S_M;
- } else if (tv == s_u) {
- dtag = S_U;
- } else if (tv == s_d) dtag = S_D; else die("a tag is .u, .m or .d");
- next();
- }
- return 0;
-}
-// the struct whose body or methods are being compiled, when it declares constants: a bare
-// name there is looked up as Struct.name before the global scope
-char* mstruct;
-// does a constant declaration start in a struct body? const NAME = e; const T NAME = e;
-// (a field of a const type has no initializer, so the = tells them apart)
-int struct_const_start() {
- if (!is_id(s_const)) return 0;
- peekat(0); if (pk_tk != T_ID) return 0;
- peekat(1);
- if (pk_tk == T_OP && pk_tv == s_eq) return 1;
- if (pk_tk == T_ID) { peekat(2); return pk_tk == T_OP && pk_tv == s_eq; }
- if (pk_tk == T_OP && pk_tv == s_star) { peekat(2); if (pk_tk != T_ID) return 0; peekat(3); return pk_tk == T_OP && pk_tv == s_eq; }
- return 0;
-}
-// const NAME = e, NAME = e; inside struct sn: a compile-time constant named Struct.NAME that takes no
-// space in the layout. the optional type is read and dropped, as in gconst.
-int struct_const(char* sn) {
- int off, s; char* cn;
- next();
- if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); }
- mstruct = sn;
- while (1) {
- off = toff; cn = name();
- s = lookup(methname(sn, cn));
- if (s >= ubase && s >= 0) serrn(cn, "is defined twice in this struct");
- expect(s_eq);
- s = addsym(methname(sn, cn), K_CONST, cexpr());
- rs_put(off, RS_SYM, rs_sym(s), s);
- if (!is_op(s_comma) && !is_op(s_semi)) { // a call or a variable's value follows what cprim could read
- serr("a struct constant must be a constant expression");
- while (!is_op(s_semi) && !is_op(s_rb) && tk != T_EOF) next();
- break;
- }
- if (!accept(s_comma)) break;
- }
- accept(s_semi);
- return 0;
-}
-// struct name { type field, ... } or struct name<A, B> { ... }
-// the struct keyword. fields go in a scratch buffer first (name, type pairs),
-// then are laid out: each field takes tsz(type) words, so offsets are words.
-// methods are collected as token lists while the struct is read (grabfn) and
-// compiled only when the struct is complete, so a method can use every field
-// 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() {
- 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; char* msv = mstruct;
- next();
- n = module_symbol(name());
- if (is_op(s_lt)) { // generic
- next(); ntp = 0;
- while (1) {
- if (ntp >= 8) die("too many type parameters");
- tpn[ntp] = name();
- lsmark(2, tpn[ntp]);
- ntp++;
- if (!accept(s_comma)) break;
- }
- closeangle();
- gen = 1;
- t = tnew(TY_GEN, 0, n, 0); t[3] = ntp;
- 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 (struct_const_start()) { struct_const(n); continue; }
- 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 = stat | 1;
- }
- if (is_id(s_fn)) { // fn name(params) { body }: a method, compiled once the struct is complete
- next();
- if (nm >= mcap) {
- mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap);
- mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap);
- mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap);
- }
- mn[nm] = name();
- mst[nm] = stat;
- mq[nm] = grabfn();
- mty[nm] = 0;
- k = methdecl(n, mn[nm]);
- 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 & 1) die("static must be followed by fn or a typed method");
- next();
- ft = tword;
- } else {
- // T name(params) { body } is a typed method; T name, ...; is a typed field
- rettype();
- fname = name();
- if (is_op(s_lp)) {
- if (nm >= mcap) {
- mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap);
- mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap);
- mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap);
- }
- mn[nm] = fname;
- mst[nm] = stat;
- mq[nm] = grabfn();
- 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 & 1) sflag[k] = sflag[k] | F_STATIC;
- nm++;
- continue;
- }
- if (stat & 1) die("static must be followed by fn or a typed method");
- if (dtag) serr("a field has no tag");
- ft = dty;
- }
- while (1) {
- if ((nf + 1) * 2 > bufcap) { buf = chk_resize(buf, nf * 2, bufcap * 2); bufcap = bufcap * 2; }
- if (fname) { buf[nf + nf] = fname; fname = 0; } else buf[nf + nf] = name();
- lsmark(3, buf[nf + nf]); buf[nf + nf + 1] = ft;
- field_note(t, buf[nf + nf], nameoff); lt_note(nameoff, ft); rs_put(nameoff, RS_FIELD, nameoff, 0); nf++;
- if (!accept(s_comma)) break;
- }
- expect(s_semi);
- }
- next();
- if (gen) {
- fs = talloc(nf + nf); rt.copy_w(fs, buf, nf + nf);
- t[4] = fs; t[5] = nf;
- mdg = ntp; rt.copy_w(mdn, tpn, ntp);
- ntp = 0;
- } else {
- fs = talloc(nf * 3 + 1);
- i = 0;
- bo = 0;
- while (i < nf) {
- ft = buf[i + i + 1];
- if (ft == t) { serr("a struct can't contain itself by value; use a pointer"); ft = tword; }
- fs[i * 3] = buf[i + i]; fs[i * 3 + 1] = ft;
- // a sized field packs into the current word at its own alignment; its
- // offset is recorded as minus (byte offset + 1). anything else starts a word
- k = tbytes(ft);
- if (k) { bo = (bo + k - 1) / k * k; fs[i * 3 + 2] = 0 - bo - 1; bo = bo + k; }
- else { bo = (bo + 7) / 8 * 8; fs[i * 3 + 2] = bo / 8; bo = bo + tsz(ft) * 8; }
- i++;
- }
- off = (bo + 7) / 8;
- t[4] = fs; t[1] = off; t[3] = nf;
- }
- accept(s_semi);
- i = 0;
- 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;
- mstruct = msv;
- return s;
-}
-// ---- 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"
-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;
- while (mn[j]) { b[i] = mn[j]; i++; j++; }
- b[i] = 0;
- return intern(b);
-}
-// 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, 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(char* n) {
- int i = 0; word t; int s, hit = 0 - 1;
- while (i < ntl) {
- t = typelist[i];
- if (t[0] == TY_STRUCT && t[3] >= 0) {
- s = methof(t, n);
- if (s >= 0) { if (hit >= 0) return 0 - 2; hit = s; }
- }
- i++;
- }
- return hit;
-}
-// 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(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");
- return s;
- }
- s = addsym(nm, K_FN, 0);
- sflag[s] = 0;
- return s;
-}
-// the tokens of a method, from its ( to the } that closes its body, as a token list
-// a method body is not parsed when the struct is read. its tokens are copied
-// 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.
-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 = rt.new(cap + cap);
- rt.copy_w(nq, q, cap);
- rt.free(q);
- q = nq;
- cap = cap + cap;
- }
- q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++;
- if (is_op(s_lb)) depth++;
- else if (is_op(s_rb)) { depth--; if (depth <= 0) { next(); break; } }
- next();
- }
- if (tk == T_EOF && depth > 0) die("missing } at the end of a method");
- q[0] = n;
- return q;
-}
-// compile a method by feeding the parser fn Struct.name(Struct* this, rest...)
-// build the token list "fn Struct.name ( Struct* this , rest..." from the
-// 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.
-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.
-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];
- 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);
- i = 0;
- while (i < mdg) {
- if (i) k = tkput(r, k, T_OP, s_comma, lnp);
- k = tkput(r, k, T_ID, mdn[i], lnp);
- i++;
- }
- k = tkput(r, k, T_OP, s_gt, lnp);
- }
- r[1 + k * 3] = q[1]; r[2 + k * 3] = q[2]; r[3 + k * 3] = q[3]; k++;
- if (stat && mdg) die("a static function can't be in a generic struct");
- if (!stat) {
- r[1 + k * 3] = T_ID; r[2 + k * 3] = sn; r[3 + k * 3] = lnp; k++;
- if (mdg) {
- k = tkput(r, k, T_OP, s_lt, lnp);
- i = 0;
- while (i < mdg) {
- if (i) k = tkput(r, k, T_OP, s_comma, lnp);
- k = tkput(r, k, T_ID, mdn[i], lnp);
- i++;
- }
- k = tkput(r, k, T_OP, s_gt, lnp);
- }
- r[1 + k * 3] = T_OP; r[2 + k * 3] = s_star; r[3 + k * 3] = lnp; k++;
- r[1 + k * 3] = T_ID; r[2 + k * 3] = s_this; r[3 + k * 3] = lnp; k++;
- }
- if (!stat && n > 1 && !(q[1 + 3] == T_OP && q[2 + 3] == s_rp)) {
- r[1 + k * 3] = T_OP;
- r[2 + k * 3] = s_comma;
- r[3 + k * 3] = lnp;
- k++;
- }
- i = 1;
- while (i < n) {
- r[1 + k * 3] = q[1 + i * 3];
- r[2 + k * 3] = q[2 + i * 3];
- r[3 + k * 3] = q[3 + i * 3];
- k++;
- i++;
- }
- r[0] = k;
- 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);
- rt.free(r); r = joined; rt.free(return_tokens); rt.free(buffer);
- }
- src_push(1, r, 0, 1);
- next();
- if (typed && !mdg) {
- // a typed method: skip the fn and the name, then compile from the ( with the declared return type
- next();
- next();
- ifkp = tpack(); ifkk = 0 - 1; if (isp != 0) ifkk = 0;
- o3nxt = 0; ntp = 0;
- fndone(fndef(1, rty, rtag, methname(sn, mn)), 0);
- } else topitem();
- if (tk != T_EOF) die("unexpected tokens after a method");
- src_pop();
- return 0;
-}
-// the call e.m(args): the receiver is on the stack and becomes this
-// 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, word receiver_fact, int receiver, word receiver_mask) {
- af_forget();
- 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;
- 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); effect_specialize(original, s, requirements, edges); }
- fnref(s); op(O_CALL);
- lv = 0;
- lvk = LV_NONE;
- xzero = 0;
- xbor = -1;
- xconst = 0;
- xfloat = 0;
- xstr = 0;
- xlenloc = -1;
- xstamp = 0;
- 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;
-}
-// type name = T;
-// the type keyword makes an alias, which is also how a pointer to an array or
-// function type gets a name.
-int kw_type() {
- int n;
- next();
- n = name();
- expect(s_eq);
- addsym(n, K_TYPE, ptype());
- expect(s_semi);
- return 0;
-}
-// does a parameter start with a type? a type name straight before , or ) is the parameter's
-// own name (fn f(string)), not a type, since a typed parameter always has a name after it.
-int paramstart() {
- if (tk == T_OP) return tv == s_lk;
- if (tk != T_ID) return 0;
- if (tv == s_fn) return 1;
- if (!istypename(tv)) return 0;
- peek();
- return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp));
-}
-// does a typedef start here? typedef followed by a name is one, so typedef can still be
-// a variable or a function's name.
-int typedef_ahead() {
- if (tk != T_ID || tv != s_typedef) return 0;
- peekat(0);
- return pk_tk == T_ID;
-}
-// unlink the symbol s from its name, leaving the rest of the name's chain as it was.
-int td_unlink(int s) {
- char* nm = sname[s]; int i = nm[-1] - 1;
- if (i == s) { nm[-1] = snext[s]; return 0; }
- while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1;
- if (i >= 0) snext[i] = snext[s];
- return 0;
-}
-// end the scope of the local typedefs made at block depth depth or deeper in this
-// compile context (and of any made in a context nested inside it).
-int td_end(int depth) {
- int k;
- while (ntd) {
- k = ntd * 3 - 3;
- if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break;
- td_unlink(tdstk[k]);
- ntd--;
- }
- return 0;
-}
-// is the name q (as a symbol is named) already a typedef of the block being compiled?
-int td_here(char* q) {
- int k = ntd;
- while (k) {
- k--;
- if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break;
- if (sname[tdstk[k * 3]] == q) return 1;
- }
- return 0;
-}
-// typedef T name;
-// the C spelling of type name = T;. name becomes another way to write T: the very same
-// type record, so the two are interchangeable everywhere. at the top level the name is
-// global; in a function it lasts to the end of its block, like a local. a name that is
-// already in use is an error, except the same type under the same name again. a tag
-// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none.
-int kw_typedef() {
- char* n; char* q; word t; int s, at, clash = 0, local = infn;
- next();
- t = ptype();
- if (panic) return 0;
- if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; }
- at = toff; n = name();
- if (panic) return 0;
- q = module_symbol(n);
- if (local) {
- s = findloc(n);
- if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1;
- } else {
- s = lookup_raw(q);
- if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; }
- if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again
- if (s >= 0) clash = 1;
- }
- if (clash) serrn(n, "already defined");
- else {
- s = addsym(n, K_TYPE, t);
- rs_put(at, RS_SYM, rs_sym(s), s);
- if (local) {
- if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); }
- tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++;
- }
- }
- expect(s_semi);
- return 0;
-}
-// (T) or (T*) ahead of the current '(', within the lookahead we have
-// the parser sees ( and has to decide between a parenthesized expression and a
-// cast. a cast is ( type stars ) so peek ahead through the stars to the ).
-int caststart() {
- int k;
- 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 ----
-
-int kw_memdebug() {
- int f = 0;
- stmtonly();
- next();
- if (is_id(s_on)) { mdbg = 1; f = 5; }
- else if (is_id(s_scrub)) { mdbg = 1; f = 7; }
- else if (is_id(s_off)) { mdbg = 0; f = 0; }
- else die("memdebug on, memdebug scrub or memdebug off");
- next();
- expect(s_semi);
- if (infn) { lit(f); callname(s_memset); op(O_DROP); }
- else mdbg_flags = f;
- return 0;
-}
-// 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(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
-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;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- pm_name = chk_resize(pm_name, npm, cap); pm_en = chk_resize(pm_en, npm, cap);
- pm_tag = chk_resize(pm_tag, npm, cap); pm_nf = chk_resize(pm_nf, npm, cap);
- pm_val = chk_resize(pm_val, npm, cap); pm_exp = chk_resize(pm_exp, npm, cap);
- pm_fnm = chk_resize(pm_fnm, npm * 8, cap * 8); pm_ft = chk_resize(pm_ft, npm * 8, cap * 8); pm_off = chk_resize(pm_off, npm * 8, cap * 8);
- pmcap = cap; return cap;
-}
-// grow the tagged-enum index independently of its member rows.
-int reserve_tagged_enums(int need) {
- int cap = pecap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- pe_st = chk_resize(pe_st, npe, cap); pe_first = chk_resize(pe_first, npe, cap);
- pe_n = chk_resize(pe_n, npe, cap); pe_name = chk_resize(pe_name, npe, cap);
- pecap = cap; return cap;
-}
-// a field's c-like spelling in a generated struct
-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(char* n) {
- int gi = npm - 1;
- while (gi >= 0) { if (pm_name[gi] == n && pm_en[gi] >= 0) return gi; gi--; }
- return -1;
-}
-// the tagged enum a type is a pointer to, plus one; 0 for any other type
-int ptagged(word t) {
- int e = 0;
- if (!t || t[0] != TY_PTR) return 0;
- while (e < npe) { if (t[4] == pe_st[e]) return e + 1; e++; }
- return 0;
-}
-// enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A)
-// 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() {
- 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(); }
- expect(s_lb);
- while (!is_op(s_rb)) {
- if (tk == T_EOF) { die("missing }"); break; }
- reserve_enum_members(npm + 1);
- gi = npm; npm++;
- n = name();
- pm_name[gi] = n; pm_tag[gi] = cnt; pm_nf[gi] = 0; pm_en[gi] = -1; pm_val[gi] = 0; pm_exp[gi] = 0;
- if (is_op(s_lb)) {
- tagged = 1; next();
- while (!is_op(s_rb)) {
- if (tk == T_EOF) { die("missing }"); break; }
- if (pm_nf[gi] >= 8) { fatal("too many fields in a member"); break; }
- if (is_id(s_var)) { next(); ft = tword; }
- else { rettype(); ft = dty; if (dtag) serr("a payload field has no tag"); }
- if (ft != tint && ft != tfloat && ft != tword) serr("a payload field is int, float or let");
- fname = name();
- nf = gi * 8 + pm_nf[gi];
- pm_fnm[nf] = fname; pm_ft[nf] = ft; pm_nf[gi]++;
- expect(s_semi);
- }
- expect(s_rb);
- } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; }
- cnt++;
- if (!accept(s_comma)) break;
- if (is_op(s_rb)) break;
- }
- expect(s_rb);
- expect(s_semi);
- if (tagged) return enum_tagged(en, first, cnt);
- if (en) { et = tenum(en); addsym(en, K_TYPE, et); }
- for (gi = first; gi < npm; gi++) {
- if (pm_exp[gi]) v = pm_val[gi];
- sk = addsym(pm_name[gi], K_CONST, v);
- stype[sk] = et;
- if (en) { sk = addsym(methname(en, pm_name[gi]), K_CONST, v); stype[sk] = et; }
- v++;
- }
- return 0;
-}
-// a tagged enum: one struct, with the tag as word 0 and the fields of every
-// member after it, each named member_field. a member's constructor is a static
-// 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(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; }
- for (gi = first; gi < npm; gi++) if (pm_exp[gi]) serr("a tagged enum's members take no explicit values");
- ebn = 0; ebuf[0] = 0;
- ebs("struct "); ebs(en); ebs(" { int tag; ");
- for (gi = first; gi < npm; gi++) {
- for (j = 0; j < pm_nf[gi]; j++) {
- nf = gi * 8 + j;
- pm_off[nf] = w; w++;
- ebs(ftext(pm_ft[nf])); ebs(" "); ebs(pm_name[gi]); ebs("_"); ebs(pm_fnm[nf]); ebs("; ");
- }
- }
- for (gi = first; gi < npm; gi++) {
- ebs("static "); ebs(en); ebs("*.u "); ebs(pm_name[gi]); ebs("(");
- for (j = 0; j < pm_nf[gi]; j++) {
- nf = gi * 8 + j;
- if (j) ebs(", ");
- if (pm_ft[nf] == tint) ebs("int "); else if (pm_ft[nf] == tfloat) ebs("float ");
- ebs(pm_fnm[nf]);
- }
- 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("; ");
- }
- ebs("return u*o; } ");
- }
- ebs("}");
- 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();
- 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;
- for (gi = first; gi < npm; gi++) {
- pm_en[gi] = npe;
- sk = addsym(pm_name[gi], K_CONST, pm_tag[gi]);
- stype[sk] = 0;
- }
- npe++;
- return 0;
-}
-// a switch over a tagged enum that leaves out a member, with no default, is a
-// warning, on the line of the switch (sl). e is the enum, cov the bit set of
-// the tags the switch covers.
-int cover_warn(int e, int cov, int sl) {
- int k, gi, first = 1;
- if (dry || panic || cmode) return 0;
- for (k = 0; k < pe_n[e]; k++) {
- if (k < 63 && ((cov >> k) & 1)) continue;
- 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;
- rt.put_s(pm_name[gi]);
- }
- if (!first) rt.put_c(10);
- return 0;
-}
-// ---- include ----
-
-// the text of an embedded library, one character to a word and ending in 0, as readfile gives a file.
-// it is unpacked from the byte string the first time it is asked for.
-char* bstr_text(bstr p) {
- int n = rt.len_s(p), i = 0; char* b = m*rt.new(n + 2);
- while (i < n) { b[i] = rt.at_s(p, i); i++; }
- b[n] = 0;
- return b;
-}
-// the embedded std module.
-char* std_text() {
- if (!std_buf) std_buf = bstr_text(std_source());
- return std_buf;
-}
-// the embedded icon module.
-char* icon_text() {
- if (!icon_buf) icon_buf = bstr_text(icon_source());
- 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 pp, sv; char* path; char* buf;
- stmtonly();
- next();
- 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 = 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;
-}
-// embed "path" as [type] name; declares name with the bytes of the file at path, read
-// when the unit is compiled. the path is found the way an include's is. with no type, or
-// bstr, name is a byte string of the file; char* gives one character to a word; a number
-// type takes the bytes little-endian (a float or f32 needs exactly its size); T[] and
-// T[n] lay the bytes out as the array's elements, packed for the sized types, one byte
-// to an element for char[]. at the top level name is a global, in a function a local.
-int kw_embed() {
- int pp, off, n, t = 0, i, w, count, k, a, j, v, sz, skip = -1; char* path; word data;
- stmtonly();
- next();
- pp = tpack();
- if (tk != T_STR) { die("embed takes a path in quotes: embed \"file\" as name;"); return 0; }
- path = tv; next();
- if (!is_id(s_as)) { die("expected as: embed \"file\" as [type] name;"); return 0; }
- next();
- peekat(0);
- if (!(tk == T_ID && pk_tk == T_OP && pk_tv == s_semi)) t = ptype();
- off = toff; n = name();
- expect(s_semi);
- data = m*embed_open(path);
- if (!data) { embed_fail(pp, "can't read the file named by embed"); data = chk_resize(0, 0, 1); }
- sz = data[0];
- if (!t) t = tbstr;
- // in a function the data sits in the code stream, so the code jumps over it
- if (infn && (t == tbstr || t == tptr(tchar) || t[0] == TY_ARR)) { reserve_code(2); skip = hole(); op(O_JMP); }
- if (t == tbstr || t == tptr(tchar)) {
- if (t == tbstr) { a = embed_bstr(data); }
- else { reserve_code(sz + 1); a = here; i = 0; while (i < sz) { emit(data[1 + i]); i++; } emit(0); }
- if (skip >= 0) setlit(skip, here);
- return embed_bind(n, off, t, a, 0);
- }
- if (t[0] == TY_ARR) {
- w = embed_width(t[4]);
- if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
- if (sz % w) { embed_fail(pp, "the embedded file's size is not a whole number of array elements"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
- count = sz / w;
- if (t[3] >= 0 && count > t[3]) { embed_fail(pp, "the embedded file is larger than the array it is declared as"); if (skip >= 0) setlit(skip, here); return embed_bind(n, off, tword, 0, 2); }
- if (t[3] < 0) t = tarr(t[4], count);
- k = tsz(t); reserve_code(k); a = here; i = 0;
- if (t[4][0] == TY_CHAR) { while (i < k) { if (i < sz) emit(data[1 + i]); else emit(0); i++; } }
- else {
- while (i < k) {
- v = 0; j = 0;
- while (j < 8) { if (i * 8 + j < sz) v = v | (data[1 + i * 8 + j] << (j * 8)); j++; }
- emit(v); i++;
- }
- }
- if (skip >= 0) setlit(skip, here);
- return embed_bind(n, off, t, a, 1);
- }
- w = embed_width(t);
- if (!w) { embed_fail(pp, "embed declares a bstr, a char*, a number, or an array of numbers"); return embed_bind(n, off, tword, 0, 2); }
- if (sz > w) { embed_fail(pp, "the embedded file is larger than the type it is declared as"); return embed_bind(n, off, tword, 0, 2); }
- if (isfl(t) && sz != w) { embed_fail(pp, "an embedded float takes exactly as many bytes as its type"); return embed_bind(n, off, tword, 0, 2); }
- v = 0; i = 0;
- while (i < sz) { v = v | (data[1 + i] << (i * 8)); i++; }
- if (isfl(t) && w == 4) v = rt.s2f(v);
- else v = cnorm(t, v);
- return embed_bind(n, off, t, v, 2);
-}
-// report msg at the embed declaration that starts at the token packed in pp. the
-// caller still declares the name, as a plain 0, so its uses are not reported too.
-int embed_fail(int pp, char* msg) {
- int sv = tpack();
- tunpack(pp); serr(msg); tunpack(sv);
- return 0;
-}
-// the bytes one element of type t takes in an embedded file: the packed size of a
-// sized type or f32, 1 for char, 8 for the other number types, 0 when t is not a number.
-int embed_width(word t) {
- if (tbytes(t)) return tbytes(t);
- if (t[0] == TY_CHAR) return 1;
- if (t[0] == TY_INT || t[0] == TY_WORD || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 8;
- return 0;
-}
-// the bytes of block data ([n, b0, b1, ...]) as byte string storage in the code stream.
-int embed_bstr(word data) {
- int n = data[0], a, i = 0, w, k;
- reserve_code((n >> 3) + 2); a = here;
- emit(n);
- while (i < n) {
- w = 0; k = 0;
- while (k < 8 && i < n) { w = w | (data[1 + i] << (k * 8)); k++; i++; }
- emit(w);
- }
- if (!(n & 7)) emit(0);
- return a;
-}
-// declare name of type t. kind 0: a is the address of the data, which becomes the value;
-// kind 1: a is an array's storage; kind 2: a is the value itself. a global keeps the
-// value in its slot; a local is initialized from it, and a local array copies the storage.
-int embed_bind(int n, int off, word t, int a, int kind) {
- int s, i;
- if (!infn) {
- if (kind == 1) { s = addsym(n, K_GARR, a); }
- else { reserve_code(1); s = addsym(n, K_GVAR, here); emit(a); }
- rs_put(off, RS_SYM, rs_sym(s), s); stype[s] = t;
- lt_note(off, t);
- return 0;
- }
- i = newloc(n); lrsoff[i] = off; rs_note(RS_LOCAL, off);
- ltag[i] = LDECL; lpos[i] = tpack(); lnsz[i] = 0; lleaf[i] = 0; ltype[i] = t;
- if (kind == 1) {
- larr[i] = 1; fslots = fslots + tsz(t); capture_cell(i, 0);
- vaddr(i); lit(a); lit(tsz(t)); callname(s_copy_w); op(O_DROP);
- } else {
- fslots++; if (isfl(t)) setflt(i, 1); capture_cell(i, 0);
- lit(a); vaddr(i); op(O_ST);
- setstr(i, t == tbstr);
- }
- lt_note(off, t);
- return 0;
-}
-// the bytes of an embedded file as [n, b0, b1, ...], found next to the including file,
-// then in the workspace folder, then as written, like an include. 0 when none opens.
-word.u embed_open(char*.m path) {
- if (path[0] != '/' && incdir) { char* combined = inc_cat(incdir, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; }
- if (path[0] != '/' && increoot) { char* combined = inc_cat(increoot, path); char* c = inc_norm(combined); word b = rt.readbytes(c); if (b) return b; }
- word b = rt.readbytes(path);
- return b;
-}
-// 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();
- if (is_id(s_on)) {
- tc_on = 1;
- } else if (is_id(s_off)) tc_on = 0; else die("typecheck on or typecheck off");
- next(); expect(s_semi);
- return 0;
-}
-// docs on; keeps the doc comments of what follows in the image, docs off; stops
-int kw_docs() {
- next();
- if (is_id(s_on)) {
- docs_on = 1;
- } else if (is_id(s_off)) docs_on = 0; else die("docs on or docs off");
- 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(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;
- nm = intern(dnmb);
- i = nm[-1] - 1;
- while (i >= 0) {
- if (skind[i] == K_DOC) return i;
- i = snext[i] - 1;
- }
- return -1;
-}
-// the packed documentation of a name (a byte string) or 0
-bstr docofs(char* nm) {
- int s = docsym(nm);
- if (s < 0) return 0;
- return sval[s];
-}
-// the documentation of a function, by address
-bstr docof(int f) {
- int i = nsym;
- while (i) {
- i--;
- if (skind[i] == K_FN && sval[i] == f && (sflag[i] & F_DEF)) return docofs(sname[i]);
- }
- return 0;
-}
-// documentation uses stable packed objects. symbols keep them alive until an
-// image copies their bytes after its code; ordinary docofs calls work too.
-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] | 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++; }
- p[1 + ((i - 1) >> 3)] = w;
- }
- 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(char* n) {
- int i = 0, w, k, c, b, nw;
- if (!dsn || !n || dry || ginst || doc_section(dsnap, dsn)) return 0;
- b = doc_pack(dsnap, dsn);
- 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;
- addsym(intern(dnmb), K_DOC, b);
- 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)
-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); rt.put_c(' '); } else rt.put_s("fn ");
- rt.put_s(n); rt.put_c('(');
- while (i < np) {
- if (i) rt.put_s(", ");
- if (typed) { tprint(pty[i]); rt.put_c(' '); }
- rt.put_s(pn[i]); i++;
- }
- rt.put_c(')'); return 0;
-}
-// attach a complete signature, measured before capture.
-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 = 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 = rt.outn;
- rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc;
- b = doc_pack(sgb, sl);
- 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 ----
-
-// 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
-// their first argument and remembers which local it was, so the o3 and
-// bounds-proof machinery can see it.
-int tfindfield(word t, char* n) {
- int i = 0;
- tresolve(t);
- while (i < t[3]) {
- if (t[4][i * 3] == n) return i;
- i++;
- }
- return -1;
-}
-// sizeof(T or expr), typeof(T or expr): the expression form is compiled dry, so it never runs
-int typearg() {
- int t, sb, c, ob;
- expect(s_lp);
- if (tstart() && !is_op(s_dot)) t = ptype();
- else {
- sb = snap_push(); ob = xbor; dry++;
- c = expr(); rval();
- t = xty;
- dry--; xbor = ob; snap_restore(sb); snap_pop(sb);
- }
- expect(s_rp);
- return t;
-}
-// len(a), cap(a), push(a, x), pop(a), resize(a, n): growable arrays.
-// 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;
- }
- 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) {
- 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 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;
- xstr = 0;
- xfloat = 0;
- xlenloc = -1;
- xstamp = 0;
- return 0;
- }
- if (t[0] == TY_ARR) { op(O_DROP); lit(t[3]); }
- else { tcneed(t[0] == TY_DYN || t == tword, "len needs an array", t); if (argloc >= 0) { op(O_DROP); vaddr(o3cache[argloc] + 1); op(O_LD); } else if (o3on && t[0] == TY_DYN) op(O_LD); else callname(s_dlen); }
- xty = tint;
- xlenloc = -1; xstamp = 0;
- 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 array_cap(word t) {
- xcls = C_PLAIN; xaf = 0; xbor = -1; effect_value = 0;
- expect(s_rp);
- 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 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; xcls = C_PLAIN;
- return 0;
- }
- 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;
- lit(0); op(O_PICK); op(O_LD);
- k = hole(); op(O_JZ);
- 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); 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);
- callname(s_dpop);
- setlit(e, here);
- return 0;
-}
-// push of a plain one-word element in o3 code, inline: when len < cap the word goes
-// straight into the items and len moves up, otherwise dpush grows the array
-int pushfast() {
- int k, e;
- lit(1); op(O_PICK); op(O_LD);
- lit(2); op(O_PICK); lit(1); op(O_ADD); op(O_LD);
- op(O_LT);
- k = hole(); op(O_JZ);
- lit(1); op(O_PICK); lit(2); op(O_ADD); op(O_LD);
- lit(2); op(O_PICK); op(O_LD); op(O_ADD);
- op(O_ST);
- lit(0); op(O_PICK); op(O_LD); lit(1); op(O_ADD); lit(1); op(O_PICK); op(O_ST);
- op(O_DROP); lit(0);
- e = hole(); op(O_JMP);
- setlit(k, here);
- callname(s_dpush);
- setlit(e, here);
- return 0;
-}
-// compile pop(a): remove and return the last element of a growable array.
-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 && !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 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);
- 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; 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;
- next();
- t = typearg();
- lit(tsz(t));
- xty = tint;
- return 0;
-}
-// compile typeof(e): the type of an expression, as a value.
-int kw_typeof() {
- int t;
- next();
- t = typearg();
- lit(t);
- xty = tword;
- return 0;
-}
-// ---- 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
-// ordinary zc function, so a call to it is a call like any other. the function
-// fills a block of words that sits in the code stream (the symbol's name, the
-// signature, a cache slot, then room for the arguments) and stores its arguments there,
-// and gate op 14 makes the native call (see the c abi section of core.c).
-// a signature is 4 bits per kind: the result, then each argument:
-// 0 void 1 long 2 double 3 byte pointer 4 int 5 float 6 word pointer
-// 7 unsigned int 8 short 9 unsigned short 10 char 11 unsigned char
-// 12 str, a zc byte string (b"..."), given to c as the address of its bytes
-// a double or float is a zc float. any pointer is a byte address (what
-// addr_b(s + 1) gives for a byte string), except "word *", which is a word
-// address (a zc object or array). native results come back the same way.
-// append the string s to the text being generated for an extern stub.
-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(char* s) {
- while (*s) { eb_char(*s); s++; }
- ebuf[ebn] = 0;
- return 0;
-}
-// append the decimal digits of n to the generated text.
-int ebn10(int n) {
- if (n >= 10) ebn10(n / 10);
- eb_char(48 + n % 10);
- ebuf[ebn] = 0;
- return 0;
-}
-// true for a c type keyword, like long, char or double.
-int ctypeword(int w) {
- 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, char* nm) {
- int i = 0, n; word ft;
- tresolve(st);
- n = st[1];
- if (st[0] != TY_STRUCT || st[5] || n < 1 || n > 15 || st[3] != n) { serrn(nm, "can't be passed by value to a native call: it needs one word fields, at most 15 words"); return 0 - 1; }
- while (i < n) {
- ft = st[4][i * 3 + 1];
- if (tbytes(ft) || tsz(ft) != 1 || ft[0] == TY_STRUCT || ft[0] == TY_ARR || st[4][i * 3 + 2] != i) { serrn(nm, "can't be passed by value to a native call: every field has to be one word (long, double or a pointer)"); return 0 - 1; }
- i++;
- }
- return n;
-}
-// the native signature kind of the c type at the current token, or -1 after an error.
-int ckind(int skipname) {
- 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 (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 (rt.eq_w(w, "struct")) sw = tv; next(); }
- other = 1; seen = 1;
- continue;
- }
- if (seen && !ctypeword(w)) break;
- 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();
- }
- if (!seen) { die("expected a c type"); return 0 - 1; }
- while (is_op(s_star)) {
- ptr = 1; next();
- while (is_id(intern("const"))) next();
- }
- if (skipname && tk == T_ID) next();
- if (ptr) {
- if (other && other != 1 && rt.eq_w(other, "word")) return 6;
- return 3;
- }
- if (dbl) return 2;
- if (flt) return 5;
- if (vd) return 0;
- if (chr) { if (uns) return 11; return 10; }
- if (shrt) { if (uns) return 9; return 8; }
- if (lng) return 1;
- if (ints || uns) { if (uns) return 7; return 4; }
- if (other == 1) {
- if (!sw) { serr("a struct has to be passed by pointer"); return 0 - 1; }
- sk = lookup(sw);
- if (sk < 0 || skind[sk] != K_TYPE || sval[sk][0] != TY_STRUCT) { serrn(sw, "isn't a zc struct: a native call takes a zc struct by value, declared with word fields"); return 0 - 1; }
- nw = cstruct_ok(sval[sk], sw);
- if (nw < 0) return 0 - 1;
- ckst = sval[sk]; ckn = sw;
- 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 (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.
-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);
- // a struct result: the stub makes it (new) and passes it as the first item; the
- // call block's word 2 says how many words it has and which of them are floats
- if (rk >= 13) {
- sret = 1; rst = ckst; rn = ckn;
- if (rst[1] > 7) { serr("a native call can return at most 7 words"); rk = 0 - 1; }
- }
- fname = name();
- cname = fname;
- expect(s_lp);
- if (!is_op(s_rp)) {
- while (1) {
- k = ckind(1);
- if (k < 0) { bad = 1; break; }
- if (k == 0 && np == 0 && is_op(s_rp)) break; // (void)
- if (k == 0) { serr("a parameter can't be void"); bad = 1; break; }
- if (np >= 15) { serr("a native call takes at most 15 arguments"); bad = 1; break; }
- ks[np] = k; kst[np] = ckst; ksn[np] = ckn; np++;
- if (!accept(s_comma)) break;
- }
- }
- expect(s_rp);
- if (accept(s_eq)) {
- if (tk != T_STR) { die("expected the c name as a string"); return 0; }
- cname = tv;
- next();
- }
- expect(s_semi);
- if (bad || rk < 0 || panic) return 0;
- if (sret && np > 14) { serr("a native call that returns a struct takes at most 14 arguments"); return 0; }
- rsd = 0;
- if (sret) {
- rsd = rst[1]; g = 0;
- while (g < rst[1]) {
- if (rst[4][g * 3 + 1] == tfloat) rsd = rsd | (2 << (4 + 4 * g)); else rsd = rsd | (1 << (4 + 4 * g));
- g++;
- }
- }
- // the block, then the name as packed bytes, then the block's own address
- // (the word before the stub's entry, which is how a call site finds it):
- // [0] the name's byte address, [1] the signature, [2] a lookup cache the
- // host fills in, [3..17] the arguments, [18..] the name
- // the arguments' nibbles: one per argument. a struct is a group: code 13 (in
- // registers if they fit) or 14 (in memory), its length in eightbytes, then one
- // class nibble per eightbyte (2 for a float word, 1 otherwise). the group is one
- // stack item: the struct's address
- sig = rk;
- pos = 1; i = 0;
- while (i < np) {
- if (ks[i] < 13) {
- sig = sig | (ks[i] << (4 * pos)); pos++;
- } else {
- st = kst[i]; len = st[1];
- if (pos + 1 + len > 15) { serr("the arguments of this native call are too many words"); return 0; }
- sig = sig | (ks[i] << (4 * pos)); pos++;
- sig = sig | (len << (4 * pos)); pos++;
- g = 0;
- while (g < len) {
- if (st[4][g * 3 + 1] == tfloat) sig = sig | (2 << (4 * pos)); else sig = sig | (1 << (4 * pos));
- pos++; g++;
- }
- }
- i++;
- }
- j = hole();
- op(O_JMP);
- 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
- i = 3;
- while (i < 18) { emit(0); i++; }
- n = 0;
- while (cname[n]) n++;
- i = 0;
- while (i < n) {
- w = 0; k = 0;
- while (k < 8 && i < n) { w = w | (cname[i] << (k * 8)); k++; i++; }
- emit(w);
- }
- if (!(n & 7)) emit(0);
- emit(a);
- setlit(j, here);
- // the stub, compiled as if it had been written in the source. it stays
- // 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(rn); ebs("*.u "); }
- else if (rk == 5) ebs("f32 "); else if (ckindfloat(rk)) ebs("float "); else ebs("int ");
- ebs(fname); ebs("(");
- i = 0;
- while (i < np) {
- if (i) ebs(", ");
- if (ks[i] >= 13) { ebs(ksn[i]); ebs(" p"); } else if (ks[i] == 5) ebs("f32 p"); else if (ckindfloat(ks[i])) ebs("float p"); else ebs("int p");
- ebn10(i);
- i++;
- }
- ebs(") { let b = "); ebn10(a); ebs(";");
- // 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(" core.syscall(14, b, 0, 0); b[3] = 0; return r; }");
- else {
- ebs(" return core.syscall(14, b, 0, 0)");
- if (rk == 5) ebs(" as f32"); else if (ckindfloat(rk)) ebs(" as float");
- ebs("; }");
- }
- 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();
- 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;
-}
-// the c kind of a zc type, for cfunc
-int ckindof(word t) {
- if (t == tfloat) return 2;
- if (t == tf32) return 5;
- if (t[0] == TY_INT && t[5]) { // i8 u8 i16 u16 i32 u32 pass and come back at their width
- if (t[5] == 1) { if (t[6] & 1) return 10; return 11; }
- if (t[5] == 2) { if (t[6] & 1) return 8; return 9; }
- if (t[6] & 1) return 4;
- return 7;
- }
- if (t == tvoid) return 0;
- if (t[0] == TY_PTR || t[0] == TY_ARR || t[0] == TY_DYN) return 6;
- return 1;
-}
-// cfunc(f): a native function pointer that runs the zc function f, with the
-// c signature its declared types give (float is a double, f32 a c float, i8..u32 their width, a pointer is a word
-// address, anything else a long). the pointer can be given to c code, which
-// may call it while a native call from zc is running.
-int kw_cfunc() {
- int s; word t; int sig = 1, i, nm;
- next();
- expect(s_lp);
- if (tk != T_ID) { die("cfunc needs the name of a function"); return 0; }
- 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];
- if (t && t[0] == TY_FN) {
- sig = ckindof(t[4]);
- i = 0;
- if (t[3] > 14) serrn(tv, "has more than 14 parameters");
- while (i < t[3] && i < 14) { sig = sig | (ckindof(t[7 + i]) << (4 * (i + 1))); i++; }
- } else {
- i = (ssig[s] >> 1) & 31;
- while (i > 0) { i--; sig = sig | (1 << (4 * (i + 1))); }
- }
- expr(); rval();
- lit(sig);
- callname(intern("cbmake"));
- expect(s_rp);
- xty = tint; xfloat = 0;
- return 0;
-}
-// intern the built-in types and the spellings the parser compares against.
-// the s_ names are plain globals holding interned handles, so a check like
-// 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);
- tint = tnew(TY_INT, 1, intern("int"), 0);
- tchar = tnew(TY_CHAR, 1, intern("char"), 0);
- 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);
- addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string;
- // f32: single precision. a variable holds the rounded value as a double; an element
- // of an array or pointer of f32 is packed two to a word, like the sized integers
- f32p = tnew(TY_FLOAT, 1, intern("f32"), 0);
- tf32 = tnew(TY_FLOAT, 1, f32p[2], 0);
- tf32[5] = 4; tf32[4] = f32p; f32p[5] = 4; f32p[6] = 2;
- addsym(tf32[2], K_TYPE, tf32);
- tsized("i8", 1, 1); addsym(intern("byte"), K_TYPE, tsized("u8", 1, 0));
- tsized("i16", 2, 1); tsized("u16", 2, 0); tsized("i32", 4, 1); tsized("u32", 4, 0);
- s_lt = intern("<");
- s_gt = intern(">");
- s_shr = intern(">>");
- s_le = intern("<=");
- s_ge = intern(">=");
- s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+");
- s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); 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("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("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("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("^");
- numops[6] = intern("<<");
- numops[7] = s_shr;
- nnumops = 8;
- return 0;
-}
-// true for an operator that works on numbers.
-int isnumop(int o) {
- int i = 0;
- while (i < nnumops) {
- if (numops[i] == o) return 1;
- i++;
- }
- return 0;
-}
-// the type of a binary operation, checking it when the typechecker is on
-// 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 rhs_type) {
- int o = bname[b];
- 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 || 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, 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 || 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(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 && 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 || 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 ----
-
-// 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.
-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 ----
-
-// 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
-// function that isn't o3, the call site where non-o3 code calls an o3
-// function whose result has pointers, and the end of an o3 block. a child
-// store of a value new(T) just allocated, or that an o3 function returned,
-// does not call fatten: the header is already that type. a call or return
-// from o3 to o3 does not fatten. fatten(p, t) stamps the type, zeroes the
-// slack, and follows every typed pointer. it returns p.
-// a type whose objects can never hold a pointer: numbers, chars, bools, and
-// arrays and plain structs of them. word, generics and anything unknown say no
-// two questions about a type's shape, both answered structurally:
-// isleaf can an object of this type never hold a heap pointer? the
-// allocator uses it to pick the recycle_leaf fast path (no children
-// to walk, so the block goes straight back on its size-class list).
-// hasptr can it hold one? fatten uses it to decide whether stamping a
-// type has to follow pointers.
-word isleaf(word t) {
- int k = t[0], i, n;
- if (k == TY_INT || k == TY_CHAR || k == TY_BOOL || k == TY_FLOAT) return 1;
- if (k == TY_ARR) return isleaf(t[4]);
- if (k == TY_STRUCT && !t[5]) {
- n = tnfields(t);
- i = 0;
- while (i < n) {
- if (!isleaf(tftype(t, i))) return 0;
- i++;
- }
- return 1;
- }
- return 0;
-}
-// 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 || k == TY_BSTR) return 1;
- if (k == TY_ARR) return hasptr(t[4]);
- if (k == TY_STRUCT && !t[5]) {
- n = tnfields(t);
- i = 0;
- while (i < n) {
- if (hasptr(tftype(t, i))) return 1;
- i++;
- }
- }
- return 0;
-}
-// stamp p with its type t, then the pointers it holds.
-int fatten(int p, int t) { fat1(p, t, 32); return p; }
-// stamp p with type t if it has no type yet, then follow its pointers. dl is a
-// depth limit so a cycle through growable arrays ends. a raw header holds
-// -(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 || !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]);
- n = p[0];
- i = 0;
- while (i < n) { fatin(p[2] + i * es, t[4], dl - 1); i++; }
- }
- return 0;
- }
- if (t[0] != TY_PTR || p[-1] >= 0) return 0;
- e = t[4]; n = 0 - p[-1] - 1; es = tsz(e);
- if (es < 1) es = 1;
- if (e == tword || e[0] == TY_VAR) p[-1] = 0;
- else if (n > es) p[-1] = tarr(e, 0);
- else p[-1] = e;
- 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;
-}
-// fatten the value stored at address a, which has type t: a pointer or handle
-// is fattened itself, an array or struct is walked element by element
-word fatin(word a, word t, int dl) {
- int k = t[0], i, n, o, ft, es;
- if (k == TY_PTR || k == TY_DYN) return fat1(a[0], t, dl);
- if (k == TY_ARR) {
- if (!hasptr(t[4])) return 0;
- n = t[3];
- es = tsz(t[4]);
- i = 0;
- while (i < n) { fatin(a + i * es, t[4], dl); i++; }
- return 0;
- }
- if (k == TY_STRUCT && !t[5]) {
- n = tnfields(t);
- i = 0;
- while (i < n) {
- o = tfoff(t, i);
- ft = tftype(t, i);
- if (o >= 0) fatin(a + o, ft, dl);
- i++;
- }
- }
- return 0;
-}
-// reflection accessors over type records (see the layout under types). they
-// resolve a generic instance first, so asking about vec<int> works.
-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.
-word tfoff(word t, int i) { tresolve(t); return t[4][i * 3 + 2]; }
-// the return type of a function type.
-word tret(word t) { return t[4]; }
-// the number of parameters of a function type.
-word tnparams(word t) { return t[3]; }
-// the type of parameter i of a function type.
-word tparam(word t, int i) { return t[7 + i]; }
-// the tag of parameter i: 0 untagged, 1 .m, 2 .d.
-word tparamtag(word t, int i) { return ((t[5] >> 6) >> (i + i)) & 3; } // 0 untagged (m), 1 .m, 2 .d
-// true when a function type returns an owned object (.u).
-word tretu(word t) { return t[5] & 1; }
-// the number of type arguments a generic struct instance has.
-word tnargs(word t) {
- if (t[0] != TY_STRUCT || !t[5]) return 0;
- return t[6][0];
-}
-// 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 ----
-
-word sigof(int f) {
- 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];
- n = (ssig[s] >> 1) & 31; k = 0;
- while (k < n) { ws[k] = tword; k++; }
- return tfn(tword, n, ws, ssig[s]);
-}
-// the function type of the function named nm, or 0 when there is none.
-word sigofs(char* nm) {
- int s = lookups(nm);
- if (s < 0 || skind[s] != K_FN) return 0;
- return sigof(sval[s]);
-}
-
-// ---- 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
-// their value.
-// memdebug: the address on top of the stack, checked before a heap load
-// (dbg_addr duplicates the address on top and calls the runtime's checkptr on
-// the copy, so memdebug catches a load from a freed object)
-int dbg_addr() {
- if (mdbg && !dry) {
- lit(0);
- op(O_PICK);
- callname(s_check);
- op(O_DROP);
- }
- return 0;
-}
-// turn an lvalue into a value. when lv is set the stack holds the address of a
-// variable, field or element that has not been loaded; rval loads it, unless
-// 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 (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); }
- lv = 0;
- }
- return 0;
-}
-// run a keyword's handler (kw_len, kw_struct, a parse word...) from the spot
-// where the parser met it. e is 1 in expression position, where statement-only
-// keywords refuse to run. the handler reports the class of what it produced
-// in xcls.
-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; 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()");
- xbor = -1;
- kw_expr = old;
- c = xcls;
- lv = 0; lvk = LV_NONE;
- return c;
-}
-// report an error when a statement is used inside an expression.
-int stmtonly() {
- if (kw_expr) serr("this can't be used inside an expression");
- return 0;
-}
-// the arguments of a call. ft is the callee's type (or 0), bs the generic bindings
-// (tags packs two bits per parameter: 0 lend, 1 .m, 2 .d. bs collects type
-// parameter bindings as the argument types are unified with the signature.
-// 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 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 || 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; effect_argument_base = 0;
- expect(s_lp);
- if (!is_op(s_rp)) {
- while (1) {
- 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 && (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
- if (fat && hasptr(t)) {
- lit(t);
- callname(s_fatten);
- xty = t;
- }
- 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++;
- if (!accept(s_comma)) break;
- }
- }
- expect(s_rp);
- 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
-// memdebug: store the current line in the runtime's alloc_line just before an
-// allocation, so a leak report can say where the object came from
-int stamp_site() {
- int s;
- if (!mdbg || dry || o3on) return 0;
- s = lookup(s_alloc);
- if (s < 0) return 0;
- lit(tline); lit(sval[s]); op(O_ST);
- 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
-// 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
-// straight off the class list. newsz and newleaf record what was allocated so
-// the variable it lands in can later be freed with the matching fast path.
-int newtyped() {
- word t; int c, fast = 0, length = 1, place = tpack();
- t = ptype();
- 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)"); }
- lit(t);
- expect(s_rp);
- stamp_site();
- 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;
- }
- if (accept(s_comma)) {
- c = expr(); rval(); plainuse(c);
- length = -1; if (xconst && xcval >= 0) length = xcval;
- if (tbytes(t)) { // packed: n elements take (n * bytes + 7) / 8 words
- if (tbytes(t) != 1) { lit(tbytes(t)); op(15); }
- lit(7); op(O_ADD); lit(3); op(19);
- } else if (tsz(t) != 1) { lit(tsz(t)); op(15); }
- lit(tarr(t, 0));
- newsz = -1; newleaf = 0;
- } else {
- lit(tsz(t)); lit(t);
- newsz = tsz(t); newleaf = 0;
- if (isleaf(t)) newleaf = t;
- if (!o3on && !dry && lookup(s_new_k) >= 0) { lit(szclass(newsz)); fast = 1; }
- }
- expect(s_rp);
- stamp_site();
- 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;
-}
-// ld8/st8 and the 16- and 32-bit pair. 0 when this call should stay a call.
-// a store's opcode is odd: the arguments arrive address then value, and the
-// opcode wants the address on top.
-// (the byte-access functions become single opcodes inside o3 code)
-int byteop(int s) {
- if (sname[s] == s_ld8) return 612;
- if (sname[s] == s_st8) return 613;
- if (sname[s] == s_ld16) return 614;
- if (sname[s] == s_st16) return 615;
- if (sname[s] == s_ld32) return 616;
- if (sname[s] == s_st32) return 617;
- return 0;
-}
-// ---- 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
-// its initializer, a parameter the type every call passes it, and a function
-// the type of what it returns. only types that change nothing are offered: no
-// floats and no sized integers, because a typed one converts or cuts to size
-// where the untyped word did not, so the annotated program is the same program.
-// the declarations are found while they compile, the calls too, and the
-// edits are written at the end of the unit, when every call has been seen.
-// a type that can be written in front of a name and changes nothing
-word ifpure(word t) {
- int k;
- 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 || 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;
- return t[4] == tword || ifpure(t[4]);
- }
- if (k == TY_ARR || k == TY_DYN) return t[4] == tword || ifpure(t[4]);
- return 0;
-}
-// 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 = 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;
- 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); }
- 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;
- return 0;
-}
-// the record of function symbol s: calls can come before the definition
-word if_get(int s) {
- int id; word r;
- if (!ifs || s < 0 || s >= symcap) return 0;
- id = ifs[s];
- if (id) return ifr[id - 1];
- 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;
- 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) {
- int r;
- if (!ifs || dry || j >= IFP || zero) return 0;
- r = if_get(s);
- if (r) if_join(r + 8 + 3 * j, t);
- return 0;
-}
-// a function at the top level starts: its header is at ifkp and ifkk says how
-// to edit it (1: fn becomes the type, 2: the type goes in front of a .u)
-int if_begin(int s, int k) {
- word r = if_get(s);
- ifcur = 0;
- if (!r) return 0;
- r[2] = 1; r[5] = ifkp; r[6] = k;
- ifcur = r;
- return 0;
-}
-// 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, word type) {
- word r = ifcur;
- if (!r || j >= IFP) return 0;
- 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;
-}
-// a return statement: t is what it returns, zero if that is a bare 0
-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;
-}
-// the function ends: which of its parameters were used as addresses
-int if_fin() {
- word r = ifcur; int j = 0;
- while (j < r[7]) {
- if (lptr[lbase + j]) r[10 + 3 * j] = r[10 + 3 * j] | 4;
- j++;
- }
- return 0;
-}
-// 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++;
- if (!r[2]) continue;
- j = 0;
- while (j < r[7]) {
- k = r[10 + 3 * j]; t = r[8 + 3 * j];
- if ((k & 3) && !t && (k & 4)) t = tptr(tword); // no call says more, but the body uses it as an address
- if ((k & 3) == 1 && t > 0) fact_add(r[9 + 3 * j], 0, t, " ");
- else if ((k & 3) == 2 && t > 0) fact_add(r[9 + 3 * j], 0, t, "");
- j++;
- }
- t = r[3];
- if (r[1] && t != 0 - 1) {
- if (!t && r[4]) t = tint;
- if (t && r[6] == 1) fact_add(r[5], 0 - 1, t, "");
- else if (t && r[6] == 2) fact_add(r[5], 0, t, "");
- }
- }
- return 0;
-}
-// ---- 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 (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, 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, HINT_ALL)) return 0;
- while ((1 << k) < v) 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;
-}
-// a call to function symbol s, with the callee name already consumed and the
-// ( next. in order:
-// 1. new(...) with a type argument is redirected to newtyped
-// 2. in o3 code, untyped new becomes new_oz and the byte accessors turn into
-// opcodes
-// 3. the arguments are parsed against the signature (args), checking each
-// against its parameter tag; generic type parameters are bound here
-// 4. free whose argument was a given-away last pointer becomes recycle; a
-// constant-size new becomes new_k with its class; put_s of a byte string
-// becomes put_bs
-// 5. the call is emitted (fnref pushes the address, or a patch site if the
-// function isn't defined yet)
-// 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]; 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(); 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) {
- kop = byteop(s);
- if (kop) {
- np = args(0, 0, bs, 0);
- if (kop & 1) {
- if (np != 2) serrn(sname[s], "wrong number of arguments");
- op(O_SWAP); op(kop); lit(0);
- } else {
- if (np != 1) serrn(sname[s], "wrong number of arguments");
- op(kop);
- }
- lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1;
- xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0;
- 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;
- 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; effect_argument_base = 0;
- np = args(tags, ft, bs, np);
- 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) { 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;
- }
- if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments");
- if (sname[s] == s_new) {
- stamp_site(); newsz = -1; newleaf = 0;
- if (np == 1 && xconst && xcval >= 0) newsz = xcval;
- xaf = af_new(newsz, 2, cp);
- if (lsp > 0) newloop(cp);
- // a constant size: the class is known now, so use the allocator's direct path
- if (newsz >= 0 && !o3on && !dry && lookup(s_new_k) >= 0) {
- lit(0);
- lit(szclass(newsz));
- s = lookup(s_new_k);
- }
- }
- if (sname[s] == s_put_s && np == 1 && xstr) {
- s = lookup(s_put_bs);
- if (s < 0) s = fwdfn(s_put_bs);
- }
- if (!(sflag[s] & F_DEF) || s == finsym) sflag[s] = sflag[s] | F_EARLY; // called before its body: an owned return is not inferred for it
- 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
- 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) {
- xstr = 1;
- }
- 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;
-}
-// an enhancement used like a function: the arguments, then its one opcode
-// an enhancement (a custom opcode declared with "enhance") called like a
-// function. it compiles to the one opcode, or to a call of its fallback
-// function when the cpu does not have the opcode.
-int enhcall(int s) {
- int nin = ssig[s] & 15, nout = (ssig[s] >> 4) & 15, n = 0, c;
- expect(s_lp);
- if (!is_op(s_rp)) {
- while (1) {
- c = expr();
- rval();
- plainuse(c);
- n++;
- if (!accept(s_comma)) break;
- }
- }
- 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; }
- op(XOP0 + sval[s]);
- if (nout == 0) lit(0);
- if (nout > 1) serrn(sname[s], "leaves several values; use it from asm");
- return C_PLAIN;
-}
-// ---- 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 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;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- clo_lo = chk_resize(clo_lo, nclo, cap);
- clo_hi = chk_resize(clo_hi, nclo, cap);
- clo_n = chk_resize(clo_n, nclo, cap);
- clo_env = chk_resize(clo_env, nclo, cap);
- clo_ret = chk_resize(clo_ret, nclo, cap);
- clo_name = chk_resize(clo_name, nclo * capture_width, cap * capture_width);
- clo_loc = chk_resize(clo_loc, nclo * capture_width, cap * capture_width);
- clo_via = chk_resize(clo_via, nclo * capture_width, cap * capture_width);
- closure_cap = cap; return cap;
-}
-int reserve_captures(int need) {
- 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) {
- 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 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).
-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, 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]++;
- return clo_n[c] - 1;
- }
- if (c == 0) return 0 - 1;
- r = clocap_at(c - 1, n); base = c * capture_width;
- if (r < 0) return 0 - 1;
- reserve_captures(clo_n[c] + 1); base = c * capture_width;
- 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
-// captured values), writes the three code words and copies each captured
-// variable in. ctx_push / ctx_pop save and restore the enclosing function's
-// compiler state around the body.
-int closure() {
- 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) && paramstart()) {
- pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
- }
- 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;
- }
- }
- expect(s_rp);
- ctx_push(); ifcur = 0; ifcall = -1;
- clo_lo[nclo] = lbase; clo_hi[nclo] = nloc; clo_n[nclo] = 0; clo_ret[nclo] = 0;
- nclo++;
- hop = hole(); op(O_JMP);
- entry = here;
- fn_begin(1, 0);
- if (declared) fty = declared;
- prologue(); borrow_prologue();
- i = 0;
- while (i < np) {
- 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++;
- }
- envl = newloc(intern("<env>")); fslots++;
- clo_env[nclo - 1] = envl;
- i = np + 1;
- while (i) { i--; laddr(i); op(O_ST); }
- 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); 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); 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++;
- }
- lv = 0;
- lvk = LV_NONE;
- xzero = 0;
- xbor = -1;
- xconst = 0;
- xfloat = 0;
- xstr = 0;
- xlenloc = -1;
- xstamp = 0;
- 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;
-}
-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,
-// ++ --, as) -> primary (literals, names, parentheses, blocks, closures)
-// each level leaves its value on the stack and sets the x* globals: xty is the
-// type, xfloat / xstr say the value is a float or byte string, xzero and
-// xconst mark a literal 0 or constant (xcval holds it), xbor names the local a
-// pointer value was borrowed from, and lv / lvk / lvi say the stack holds an
-// unloaded address and what it is the address of.
-//
-// primary: one operand. a number, string, parenthesized expression, block
-// expression, closure, or a name. a name is looked up in this order: a local
-// of the current function, a variable captured by an enclosing closure, then
-// the symbol table (keyword, constant, global, enhancement, function). a local
-// the analyzer tracks returns C_VAR + its index so the caller can tell which
-// variable the value is.
-int primary() {
- effect_value = 0; xprojection = 0; if_value = 0;
- int n, i, s, c, k, npos, sv; 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; 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); xvk = 1; xvv = tv; xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); 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 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 || pk_tv == s_colon)) return closure(); }
- n = tv; callpos = tpack();
- i = findloc(n);
- if (i < 0 && nclo) {
- k = clocap(n);
- if (k >= 0) {
- rs_note(RS_LOCAL, 0 - 1);
- next();
- i = nclo - 1;
- 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;
- return C_PLAIN;
- }
- }
- if (i >= 0) {
- 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");
- }
- vaddr(i);
- xty = ltype[i];
- if (larr[i]) return C_PLAIN;
- xfloat = getflt(i);
- if (isfl(ltype[i])) xfloat = 1;
- 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_VAR + i; }
- if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i;
- return C_PLAIN;
- }
- s = source_lookup(n);
- if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && skind[ms] == K_CONST) s = ms; }
- 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");
- return kwcall(s, 1);
- }
- 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();
- 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; sv = tpack(); mn = name();
- ms = lookup(methname(sname[s], 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];
- xzero = sval[ms] == 0;
- xconst = 1;
- xcval = sval[ms];
- return C_PLAIN;
- }
- if (ms >= 0 && skind[ms] == K_FN && (sflag[ms] & F_STATIC)) {
- if (!is_op(s_lp)) die("a static function is called with ( after its name");
- return call(ms);
- }
- if (!panic && !dry) {
- c = tpack(); tunpack(sv); // the error sits on the member name, where the quick fix replaces it
- errat(); rt.put_s(mn); rt.put_s(": is not a static function of this struct"); suggest_member(sname[s], 0, mn, 1); errend();
- tunpack(c);
- }
- lit(0);
- xty = tword;
- return C_PLAIN;
- }
- dien(n, "is a type, not a value");
- lit(0);
- xty = tword;
- return C_PLAIN;
- }
- npos = tpack();
- 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];
- xzero = sval[s] == 0;
- xconst = 1;
- xcval = sval[s];
- 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 (xty == tbstr) xstr = 1;
- lv = 1;
- 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; }
- if (s >= 0 && skind[s] == K_ENH) return enhcall(s);
- if (s < 0 && is_op(s_dot)) {
- peekat(0);
- if (pk_tk == T_ID && lib_file(n)) {
- // lib.name before include lib, for a library read from a file: the lexer only joins names of loaded modules
- sv = tpack(); tunpack(npos);
- errat(); rt.put_s("module access requires `include "); rt.put_s(n); rt.put_s(";`"); errend();
- tunpack(sv);
- word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = n; missing_imports = link;
- next(); n = methname(n, tv); next();
- }
- // Text.itoa with no std: a library's static function, reported as one missing name
- else if (pk_tk == T_ID && export_owner(methname(n, pk_tv))) { next(); n = methname(n, tv); next(); }
- }
- if (s < 0) s = fwdfn_at(n, callpos);
- if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f<int, char*>(...)
- next(); ngbind = 0;
- while (1) {
- if (ngbind >= 8) die("too many type arguments");
- gbind[ngbind] = ptype();
- ngbind++;
- if (!accept(s_comma)) break;
- }
- closeangle();
- if (!is_op(s_lp)) { // f<int> alone is that instance, as a function value
- rt.fill_w(bs0, 0, 8); rt.copy_w(bs0, gbind, ngbind); ngbind = 0;
- ft = stype[s];
- s = ginstance(s, bs0);
- fnref(s);
- xty = tword;
- if (ft) xty = subst(ft, bs0);
- return C_PLAIN;
- }
- }
- if (is_op(s_lp)) return call(s);
- fnref(s);
- if (stype[s]) xty = stype[s];
- return C_PLAIN;
-}
-// ++ and -- on the address at the top of the stack. post says the old value
-// 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)) {
- cerrn(lvi, "owns an object and can't be incremented");
- }
- tcneed(isnum(xty) || xty[0] == TY_PTR || xty == tword, "++ and -- need a number or a pointer", xty);
- if (lvk == LV_LOCAL) {
- ltag[lvi] = ltag[lvi] | LASG;
- setflt(lvi, 0); setstr(lvi, 0);
- if (forstep && lvi == ft_iv && dlt == 1) fs_ok = 1;
- if (dlt < 0) putfact(lvi, -1, -1); else putfact(lvi, getlo(lvi), -1);
- bpoison(lvi);
- }
- lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0;
- t = xty;
- lit(0); op(O_PICK);
- if (ispk(t)) pkld(t); else { dbg_addr(); op(O_LD); }
- // a sized integer wraps: the new value is cut to size before it is stored
- if (post) {
- lit(0); op(O_PICK); lit(dlt); op(O_ADD);
- if (tbytes(t)) tnorm(t);
- lit(2); op(O_PICK);
- if (ispk(t)) pkst(t); else op(O_ST);
- } else {
- lit(dlt); op(O_ADD);
- if (tbytes(t)) tnorm(t);
- lit(0); op(O_PICK); lit(2); op(O_PICK);
- if (ispk(t)) pkst(t); else op(O_ST);
- }
- op(O_SWAP); op(O_DROP);
- return C_PLAIN;
-}
-// p.x and p->x: a field of a struct, or of the struct a pointer points at
-// the field access after a dot or arrow: c is the class of the left side. a
-// field of a typed struct is its offset added to the address. an untyped word
-// is searched for the one struct that has that field name (fieldscan). if the
-// 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, fieldpos, sv; word st;
- 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;
- }
- fieldpos = tpack(); 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, borrowed, b, base_mask);
- }
- }
- if (st[0] != TY_STRUCT && (st == tword || isnum(st))) {
- k = fieldscan(n);
- if (k == 1) { st = fldst; scanned = 1; }
- else if (k > 1) {
- if (!panic && !dry) {
- errat();
- 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;
- }
- }
- if (st[0] != TY_STRUCT) {
- if (!panic && !dry) {
- errat();
- rt.put_c('.');
- rt.put_s(n);
- rt.put_s(": that's a ");
- tprint(xty);
- 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) {
- sv = tpack(); tunpack(fieldpos); // the error sits on the field name, where the quick fix replaces it
- errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); suggest_member(st[2], st, n, 0); errend();
- tunpack(sv);
- }
- 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 (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;
-}
-// array indexing, address arithmetic. mulsz scales an index by the element
-// size. the addr_ functions build the address of element ix of a growable
-// array in the different situations the bounds-check removal produces: the
-// stack holds handle and index (addr_stk), the items pointer is cached in a
-// hidden o3 variable (addr_cached), or only the handle is available
-// (addr_hdr). range_jz is the inline bounds check, one "outside" enhancement
-// and a branch to the oob report.
-int mulsz(int et) {
- if (tsz(et) != 1) { lit(tsz(et)); op(15); }
- return 0;
-}
-// address of an element of a stack array, from the index on the stack.
-int addr_stk(int et) {
- op(O_SWAP);
- lit(2);
- op(O_ADD);
- op(O_LD);
- op(O_SWAP);
- mulsz(et);
- op(O_ADD);
- return 0;
-}
-// address of an element of an array whose base is cached in a register.
-int addr_cached(int ci, int ix, int et) {
- vaddr(ci);
- op(O_LD);
- vaddr(ix);
- op(O_LD);
- mulsz(et);
- op(O_ADD);
- return 0;
-}
-// address of an element of a growable array, reading its header.
-int addr_hdr(int al, int ix, int et) {
- vaddr(al); op(O_LD); lit(2); op(O_ADD); op(O_LD);
- vaddr(ix); op(O_LD); mulsz(et); op(O_ADD);
- return 0;
-}
-// reload the index and the length on each side of the branch, so neither is a stack temporary
-int range_jz(int ix, int src, int fld) {
- int k, s;
- vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD);
- s = lookup(s_outside); op(XOP0 + sval[s]);
- k = hole(); op(O_JZ);
- vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD);
- callname(s_oob);
- setlit(k, here);
- return 0;
-}
-// suffixes after a primary, in a loop: e as T, [index], .field, ->field,
-// (call through a value), ++ and --. the index case is the long one. a byte
-// string takes an index or a slice. a growable array is checked: through dat()
-// normally, but inline when the compiler can prove or cheaply test the bound
-// (o3 code with the cache variables, or an index the loop test already
-// 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() { 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(); 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) {
- 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;
- if (o3on && lv && lvk == LV_LOCAL && o3cache[lvi]) ci = o3cache[lvi];
- next();
- rval();
- if (was) {
- c = expr(); rval(); plainuse(c);
- if (accept(s_colon)) {
- c = expr(); rval(); plainuse(c);
- expect(s_rk);
- 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); xvk = 0;
- lv = 0; lvk = LV_NONE; xty = tint; xfloat = 0; xstr = 0; xzero = 0; xbor = -1;
- c = C_PLAIN;
- continue;
- }
- b = -1;
- if (c == C_FRESH) cerr("index a new object only after binding it to a name");
- else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; }
- 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);
- 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"));
- et = tpk(et);
- } else if (xty[0] == TY_DYN) { // a growable array: bounds-checked, through the runtime
- et = xty[4];
- al = -1;
- if (lvk == LV_LOCAL) al = lvi;
- if (ci && !mdbg && lookup(s_outside) >= 0) op(O_DROP);
- c = expr();
- ix = -1;
- if (lv && lvk == LV_LOCAL) ix = lvi;
- rval(); plainuse(c);
- 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);
- enh = k >= 0 && skind[k] == K_ENH;
- if (!mdbg && ix >= 0 && al >= 0 && proven(al, ix)) { // the loop test already compared this index with the length
- if (ci) { vaddr(ci); op(O_LD); op(O_SWAP); mulsz(et); op(O_ADD); }
- else addr_stk(et);
- } else if (ci && !mdbg && enh && ix >= 0) {
- op(O_DROP);
- range_jz(ix, ci + 1, 0);
- addr_cached(ci, ix, et);
- } else if (ci && !mdbg && enh) {
- lit(0); op(O_PICK); vaddr(ci + 1); op(O_LD); op(XOP0 + sval[k]);
- k = hole(); op(O_JZ);
- vaddr(ci + 1); op(O_LD); callname(s_oob);
- setlit(k, here);
- vaddr(ci); op(O_LD); op(O_SWAP);
- mulsz(et); op(O_ADD);
- } else if (!mdbg && o3on && enh && ix >= 0 && al >= 0) {
- op(O_DROP); op(O_DROP);
- range_jz(ix, al, 1);
- addr_hdr(al, ix, et);
- } else if (!mdbg && o3on && enh && ix >= 0) {
- op(O_DROP);
- vaddr(ix); op(O_LD);
- lit(1); op(O_PICK); op(O_LD); op(XOP0 + sval[k]);
- k = hole(); op(O_JZ);
- vaddr(ix); op(O_LD); lit(1); op(O_PICK); op(O_LD); callname(s_oob);
- setlit(k, here);
- lit(2); op(O_ADD); op(O_LD); vaddr(ix); op(O_LD);
- mulsz(et); op(O_ADD);
- } else if (!mdbg && o3on && enh) {
- lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_LD); op(XOP0 + sval[k]);
- k = hole(); op(O_JZ);
- lit(1); op(O_PICK); op(O_LD); callname(s_oob);
- setlit(k, here);
- op(O_SWAP); lit(2); op(O_ADD); op(O_LD); op(O_SWAP);
- mulsz(et); op(O_ADD);
- } else callname(s_dat);
- } else {
- tcneed(isptr(xty) || xty == tword, "only pointers and arrays can be indexed", xty);
- et = telem(xty);
- n = 0 - 1; if (xty[0] == TY_ARR && xty[3] > 0) n = xty[3];
- xconst = 0;
- c = expr(); rval(); plainuse(c);
- 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");
- }
- tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty);
- expect(s_rk);
- if (tbytes(et)) { // packed: byte address = base * 8 + index * bytes
- if (tbytes(et) != 1) { lit(tbytes(et)); op(15); }
- op(O_SWAP); lit(8); op(15); op(O_ADD);
- et = tpk(et);
- } else {
- if (tsz(et) != 1) { lit(tsz(et)); op(15); }
- op(O_ADD);
- }
- }
- 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;
- xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0;
- continue;
- }
- if (tv == s_dot || tv == s_arrow) {
- xconst = 0;
- c = field(c);
- xfloat = 0;
- if (isfl(xty)) xfloat = 1;
- 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);
- 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);
- lit(n); op(O_PICK); op(O_CALL);
- cj = hole(); op(O_JMP);
- setlit(cz, here);
- lit(n); op(O_PICK); lit(3); op(O_ADD); // env: the captures start 3 words into the block
- lit(n + 1); op(O_PICK); op(O_LD); // the body's entry, word 0 of the block
- 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;
- }
- if (tv == s_inc) { next(); c = incdec(1, 1); continue; }
- if (tv == s_dec) { next(); c = incdec(1, -1); continue; }
- return c;
- }
- return c;
-}
-// prefix operators: the u*x / d*x / m*x recasts (the star has to touch the
-// 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, 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]) {
- t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D;
- next(); expect(s_star);
- c = unary(); place = -1; if (lvk == LV_LOCAL) place = lvi; rval();
- lvk = LV_NONE;
- if (c < C_VAR && c != C_FRESH && place >= 0 && fchk) { cerrn(place, "isn't an owned object, so there is no ownership to recast"); return c; }
- c = recast(c, t);
- if (t == S_M && c >= C_VAR && lstate[c - C_VAR] == S_M && lown[c - C_VAR]) xlend = c - C_VAR;
- return c;
- }
- }
- if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes
- next();
- t = ptype();
- expect(s_rp);
- 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(); 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);
- 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;
- xzero = 0;
- xbor = -1;
- xfloat = 0;
- if (isfl(xty)) xfloat = 1;
- xstr = 0;
- return C_PLAIN;
- }
- if (tv == s_amp) {
- place = tpack(); next(); c = unary();
- 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) { 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;
- if (!lv && !isagg(xty)) die("& needs something addressable");
- if (lv && ispk(xty)) serr("an element of a sized-integer array has no address; it is a byte inside a word");
- 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);
- 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); }
- if (tv == s_dec) { next(); unary(); return incdec(0, -1); }
- un = unfind(tv);
- 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);
- emit(0);
- op(O_SWAP);
- op(OP_FSUB);
- xty = tfloat;
- }
- else {
- emitseq(useq[un], ulen[un]); xfloat = 0;
- 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;
- return C_PLAIN;
- }
- }
- return postfix();
-}
-// binary operators by precedence climbing: parse an operand, then while the
-// next token is a binary operator of at least minp, parse its right side at one
-// 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; 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);
- if (b < 0) return c;
- p = bprec[b];
- if (p < minp) return c;
- lb = -1;
- if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor;
- 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;
- if (isfl(lt)) lf = 1;
- src = -1;
- if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1
- af_forget();
- ftest();
- a = hole(); op(O_JZ);
- c = bin(p + 1); rval(); plainuse(c);
- ftest();
- emitseq(useq[1], ulen[1]); // !! normalizes
- j = hole(); op(O_JMP);
- setlit(a, here); lit(0); setlit(j, here);
- xfloat = 0; xstr = 0;
- } else if (blen[b] == -2) { // || : 1 if the left side isn't 0, else the right side as 0 or 1
- af_forget();
- ftest();
- a = hole(); op(O_JZ);
- lit(1); j = hole(); op(O_JMP);
- setlit(a, here);
- c = bin(p + 1); rval(); plainuse(c);
- ftest();
- emitseq(useq[1], ulen[1]);
- setlit(j, here);
- xfloat = 0; xstr = 0;
- } else {
- 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; }
- rval(); plainuse(c);
- if (cmode && bname[b] == s_star && xconst && xcval >= 2 && !(xcval & (xcval - 1)) && !lf) {
- shifthint(opp, xcval);
- }
- rf = xfloat; rs = xstr;
- if (isfl(xty)) rf = 1;
- if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) {
- if (!(ls && rs)) serr("a byte string needs a byte string on the other side");
- if (bname[b] == s_plus) { callname(s_cat_s); xstr = 1; xfloat = 0; }
- else if (bname[b] == s_eqeq) { callname(s_eq_s); xstr = 0; xfloat = 0; }
- else if (bname[b] == s_ne) { callname(s_eq_s); lit(1); op(18); xstr = 0; xfloat = 0; }
- else if (bname[b] == s_lt) { callname(s_lt_s); xstr = 0; xfloat = 0; }
- else if (bname[b] == s_gt) { op(O_SWAP); callname(s_lt_s); xstr = 0; xfloat = 0; }
- else if (bname[b] == s_le) { callname(s_le_s); xstr = 0; xfloat = 0; }
- else { op(O_SWAP); callname(s_le_s); xstr = 0; xfloat = 0; }
- } 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();
- 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) {
- ft_nops++;
- 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; 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 ----
-
-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);
- if (ispk(t)) pkst(t); else op(O_ST);
- return 0;
-}
-// c ? a : b. both sides are analyzed from the same starting state (a snapshot,
-// 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; 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; 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; 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);
- if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; }
- 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;
- 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;
-}
-// the top of the expression grammar: a binary expression, then possibly ?: or
-// an assignment. plain = stores the value and leaves it on the stack; the
-// analyzer's assigncheck decides what the store means for ownership (a fresh
-// object makes the target an owner, a u object moves up a block, and so on).
-// compound assignment (+= etc.) loads the old value, applies the operator
-// sequence from the table and stores the result. an o3 variable is stored with
-// the value first and the address after, so no address waits on the stack
-// across a branch.
-int expr() {
- int c = bin(1), k, i, a, rc, lt, lf, rf, fk;
- if (tk != T_OP) return c;
- 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; 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];
- if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch
- xlend = -1;
- rc = expr(); rval();
- if (k != LV_LOCAL) af_escape(xaf);
- fcoerce(lt);
- if (k == LV_LOCAL) {
- ltag[i] = ltag[i] | LASG;
- setflt(i, xfloat);
- setstr(i, xstr);
- 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 {
- if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see
- storekeep(lt);
- }
- lv = 0; lvk = LV_NONE; xty = lt; xzero = 0;
- return assigncheck(k, i, rc);
- }
- a = asgfind(tv);
- if (a >= 0) {
- if (!lv) die("this can't be assigned to");
- if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
- cerrn(lvi, "owns an object; compound assignment would lose it");
- }
- lt = xty; require_mutable(lt);
- fk = -1; lf = xfloat;
- if (lvk == LV_LOCAL) fk = lvi;
- if (isfl(lt)) lf = 1;
- next();
- i = o3on && lvk == LV_LOCAL && (ltag[lvi] & 16) && !larr[lvi];
- if (i) { i = lvi; op(O_DROP); vaddr(i); op(O_LD); i = i + 1; }
- else if (ispk(lt)) { lit(0); op(O_PICK); pkld(lt); }
- else { lit(0); op(O_PICK); op(O_LD); }
- lv = 0;
- rc = expr(); rval(); plainuse(rc);
- rf = xfloat;
- if ((lf || rf) && isfop(bname[abin[a]])) femit(bname[abin[a]], lf, rf);
- else { emitseq(bseq[abin[a]], blen[abin[a]]); xfloat = 0; }
- fcoerce(lt);
- if (fk >= 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;
- if (bname[abin[a]] == s_plus && xconst && xcval > 0) putfact(fk, getlo(fk), -1);
- 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);
- lv = 0; lvk = LV_NONE; xty = lt; xzero = 0;
- return C_PLAIN;
- }
- return c;
-}
-// does the current token start a statement rather than an expression?
-// a block as an expression: { stmts; tail }. it opens a scope like any block,
-// so its variables are freed when it closes, and the tail is how a u object
-// leaves the block with a value (handup).
-word stmtstart() {
- int s;
- if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart();
- if (tk != T_ID || findloc(tv) >= 0) return 0;
- if (tstart() || typedef_ahead()) return 1;
- s = lookup(tv);
- return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR);
-}
-// { stmts; tail } as an expression: its value is the tail, or 0
-int blockexpr() {
- int save = nloc, c = C_PLAIN, tail = 0, t = tint;
- next();
- fdepth++; bxdepth++;
- while (!is_op(s_rb)) {
- if (tk == T_EOF) { die("missing }"); break; }
- if (stmtstart()) { stmt(); continue; }
- c = expr(); rval();
- if (accept(s_semi)) { op(O_DROP); discard(c); c = C_PLAIN; continue; }
- if (!is_op(s_rb)) die("expected ; or } after an expression");
- tail = 1; t = xty;
- }
- next();
- 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--;
- lv = 0; lvk = LV_NONE; xty = t; xzero = 0;
- return c;
-}
-
-// ---- 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
-// initializers and asm operands.
-word cprim() {
- int v, s, mc;
- if (tk == T_NUM) { v = tv; next(); return v; }
- if (accept(s_minus)) return 0 - cprim();
- if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); }
- if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; }
- if (tk == T_ID) {
- s = lookup(tv);
- if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; }
- if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) {
- next();
- return sval[s];
- }
- if (s >= 0 && skind[s] == K_TYPE) { // Enum.MEMBER in a constant expression
- peekat(0);
- if (pk_tk == T_OP && pk_tv == s_dot) {
- peekat(1);
- if (pk_tk == T_ID) {
- mc = lookup(methname(tv, pk_tv));
- if (mc >= 0 && skind[mc] == K_CONST) { next(); next(); next(); return sval[mc]; }
- }
- }
- }
- }
- die("expected a constant");
- return 0;
-}
-// compile an expression: a primary, then the operators that follow it.
-int cexpr() {
- int v = cprim(), o;
- while (tk == T_OP) {
- o = tv;
- if (o == bname[3]) { next(); v = v + cprim(); }
- else if (o == bname[4]) { next(); v = v - cprim(); }
- else if (o == bname[0]) { next(); v = v * cprim(); }
- else if (o == bname[15]) { next(); v = v | cprim(); }
- else if (o == bname[13]) { next(); v = v & cprim(); }
- else if (o == bname[5]) { next(); v = v << cprim(); }
- else if (o == bname[6]) { next(); v = v >> cprim(); }
- else return v;
- }
- return v;
-}
-
-// ---- 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
-// expression statement whose value is dropped. after a syntax error panic is
-// set and stmt calls resync to skip to the next ; or }.
-int resync() {
- while (tk != T_EOF && !is_op(s_semi) && !is_op(s_rb)) next();
- if (is_op(s_semi)) next();
- panic = 0;
- return 0;
-}
-// 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);
- if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; }
- }
- if (untyped_const()) { next(); localdecl(2); return 0; }
- if (typedef_ahead() && findloc(tv) < 0) return kw_typedef();
- if (tstart()) { localdecl(1); return 0; }
- if (tk == T_OP) {
- if (tv == s_semi) { next(); return 0; }
- if (tv == s_lb) { block(); return 0; }
- }
- c = expr(); rval(); op(O_DROP);
- expect(s_semi);
- discard(c);
- return 0;
-}
-// compile a block, with its own scope for the locals it declares.
-int block() {
- int save = nloc;
- 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--;
- return 0;
-}
-// the body of if/while/for: its own block, even without braces
-// (the body of if/while/for is always its own block, even without braces, so
-// variables declared in it die with it)
-int substmt() {
- int save = nloc;
- if (is_op(s_lb)) return block();
- fdepth++;
- stmt();
- block_close(save);
- fdepth--;
- return 0;
-}
-// let a = e, b[10]; or, typed: int a = 1, b; node*.m p = new(node); .u r;
-// let infers pointer, struct and function types from the initializer and
-// keeps numbers as plain words.
-// (each declared name gets a slot; an initializer is compiled and stored, and
-// assigncheck gives the new local its analyzer state. an initializer that is a
-// constant number marks the local as a const candidate for the hints, and a
-// new records the size and leaf-ness for the matching recycle.)
-// the type of each untyped local while the language server checks a document:
-// the byte offset of its name in the document, and the type's text in ltxt
-// (one character per word). the server turns it into the hover of the local.
-// record a parsed type expression so arrays, stars, and function types have hovers.
-int ht_note(int start, int end, word t) {
- if (!lt_on || isp != 0 || dry || !t || start < 0 || end <= start) return 0;
- if (!ht_start || ht_n == htcap) {
- if (ht_start) htcap = htcap * 2;
- ht_start = chk_resize(ht_start, ht_n, htcap);
- ht_end = chk_resize(ht_end, ht_n, htcap);
- ht_type = chk_resize(ht_type, ht_n, htcap);
- }
- ht_start[ht_n] = start; ht_end[ht_n] = end; ht_type[ht_n] = t; ht_n++;
- return 0;
-}
-// record the type of a local at byte offset o for the language server.
-int lt_note(int o, int t) {
- int sm, sb, sn, sc, sl, need;
- if (!lt_on || isp != 0 || dry || o < 0 || !t) return 0;
- if (!lt_off || lt_n == ltcap) {
- if (lt_off) ltcap = ltcap * 2;
- 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 = 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);
- }
- rt.outmode = 2; rt.outbuf = ltxt; rt.outn = ltxn; rt.outcap = ltxtcap;
- tprint(t);
- 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.
-// does `const name =` start here? a const with no type takes the initializer's type.
-int untyped_const() {
- if (tk != T_ID || tv != s_const) return 0;
- peekat(0); if (pk_tk != T_ID || istypename(pk_tv)) return 0;
- peekat(1); return pk_tk == T_OP && (pk_tv == s_eq || pk_tv == s_semi || pk_tv == s_comma);
-}
-// typed is 1 after a type, 0 after let, and 2 after an untyped const.
-int localdecl(int typed) {
- int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr, ro = typed == 2; word iq, it;
- if (ro) typed = 0;
- 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); vr = rsn - 1;
- n = name();
- i = newloc(n);
- lrsoff[i] = o;
- ltag[i] = tg | LDECL; lpos[i] = pp; lnsz[i] = 0; lleaf[i] = 0;
- if (o3on || o3one) ltag[i] = ltag[i] | 16;
- if (accept(s_lk)) {
- k = cexpr(); expect(s_rk);
- if (!t) t = tword;
- larr[i] = 1; ltype[i] = tarr(t, k); fslots = fslots + tsz(ltype[i]);
- 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] && 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;
- 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);
- vaddr(i);
- op(O_ST);
- o3refresh(i);
- setflt(i, xfloat);
- setstr(i, xstr);
- 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);
- else if (xty[0] == TY_STRUCT) ltype[i] = tptr(xty);
- else ltype[i] = xty;
- }
- assigncheck(LV_LOCAL, i, c);
- if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); }
- if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; }
- }
- } else if (ro) serr("a const needs an initializer");
- 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]); 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);
- if (!accept(s_comma)) break;
- }
- expect(s_semi);
- if (gok && gty && kp) fact_add(kp, 0 - 1, gty, "");
- return 0;
-}
-// compile a variable declaration.
-int kw_var() {
- stmtonly();
- ifkp = 0;
- if (isp == 0) ifkp = tpack(); // only the document itself: an include has other offsets
- next();
- return localdecl(0);
-}
-// if / else. the shape of every branching construct is the same: snapshot the
-// analyzer state, compile one path, restore the snapshot, compile the other,
-// then join the two end states (join2). a path that does not reach the join
-// (it returned or broke) is left out. without an else the "other path" is
-// the state before the branch.
-int kw_if() {
- int a, b, c, s0, sa, live0, livea, n0 = nloc, nt, cj;
- stmtonly();
- next();
- expect(s_lp); c = borrow_condition(); expect(s_rp);
- a = hole(); op(O_JZ);
- live0 = live;
- s0 = snap_push();
- substmt();
- livea = live; sa = snap_push();
- if (is_id(s_else)) {
- next();
- b = hole(); op(O_JMP);
- setlit(a, here);
- snap_restore(s0); live = live0;
- substmt();
- join2(sa, livea, b, live, n0, "ends the branches of an if in different states");
- } else {
- // the path that skips the then branch still has the states from before it
- njps = 0; if (livea) jadd(R_SNAP + sa); if (live0) jadd(R_SNAP + s0);
- join(n0, "ends the branches of an if in different states");
- if (livea) fixups(R_SNAP + sa, n0, 1);
- nt = 0; if (live0) nt = fixups(R_SNAP + s0, n0, 0);
- if (nt) {
- cj = hole();
- op(O_JMP);
- setlit(a, here);
- fixups(R_SNAP + s0, n0, 1);
- setlit(cj, here);
- }
- else setlit(a, here);
- live = livea || live0;
- }
- snap_pop(s0);
- return 0;
-}
-// loop frame: 0 continue target (-1 for a switch) 2 depth 3 snapshot at the top
-// 4 block-expression depth 5 locals at the start 6 its first exit record
-// loops and switches share a frame stack (R_LOOP, 8 words each). a break adds
-// an exit record (the analyzer state at that point) with a jump to patch;
-// loop_end joins all of them with the normal fall-out and patches the jumps.
-int loop_push(int cont, int s0) {
- word f;
- reserve_R_LOOP((lsp + 1) * 8);
- if (lsp >= loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, loopfacts_cap * 2); loopfacts_cap = loopfacts_cap * 2; }
- f = R_LOOP + lsp * 8;
- f[0] = cont;
- f[1] = 0;
- f[2] = fdepth;
- f[3] = s0;
- f[4] = bxdepth;
- f[5] = nloc;
- f[6] = extop;
- f[7] = 0;
- lpfact[lsp] = -1;
- lsp++;
- return 0;
-}
-// the end of a loop or switch: join its exits (the fall-out path sc through
-// the jump at a, if there is one, and every break) and lay out each path's
-// fixups on its way to the end
-int loop_end(int a, int sc) {
- word f, p; int e, n0, outs = 0;
- lsp--;
- f = R_LOOP + lsp * 8;
- n0 = f[5];
- njps = 0;
- if (sc >= 0) jadd(R_SNAP + sc);
- p = R_EXIT + f[6]; e = R_EXIT + extop;
- while (p < e) { jadd(p + 1); p = p + 3 + 3 * (p[2] - p[1]); }
- join(n0, "leaves the loop in different states on different paths");
- live = njps > 0;
- p = R_EXIT + f[6];
- while (p < e) {
- if (fixups(p + 1, n0, 0)) {
- setlit(p[0], here);
- fixups(p + 1, n0, 1);
- outs = fixnew(hole(), outs);
- op(O_JMP);
- }
- else outs = fixnew(p[0], outs);
- p = p + 3 + 3 * (p[2] - p[1]);
- }
- if (a >= 0) setlit(a, here);
- if (sc >= 0) fixups(R_SNAP + sc, n0, 1);
- fixall(outs, here);
- extop = f[6];
- return 0;
-}
-// while. the back edge is checked against the snapshot taken at the top, so a
-// loop body that gives something away and comes around again is an error. a
-// literal while (1) has no fall-out path, only its breaks.
-int kw_while() {
- int c0 = here, a, s0, sc = -1, c, inf = 0;
- stmtonly();
- next();
- s0 = snap_push();
- expect(s_lp);
- // while (1): no way out but break
- if (tk == T_NUM && tv) {
- peek();
- if (pk_tk == T_OP && pk_tv == s_rp) inf = 1;
- }
- c = borrow_condition(); expect(s_rp);
- a = hole(); op(O_JZ);
- if (!inf && live) sc = snap_push();
- loop_push(c0, s0);
- substmt();
- backedge(s0);
- jmp(c0);
- loop_end(a, sc);
- snap_pop(s0);
- return 0;
-}
-// for (init; test; step) body. the code is laid out test first, then a jump
-// over the step into the body, and the step jumps back to the test, so the
-// step is compiled before the body but runs after it. while the test and the
-// step are parsed, fortest and forstep record the shape "i < bound ... i++":
-// 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; word borrow_mark = borrow_slots;
- stmtonly();
- next();
- expect(s_lp);
- fdepth++;
- 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 = borrow_condition();
- } else { lit(1); inf = 1; xfloat = 0; }
- fortest = 0;
- expect(s_semi);
- a = hole(); op(O_JZ);
- if (!inf && live) sc = snap_push();
- b = hole(); op(O_JMP);
- st = here;
- 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);
- setlit(b, here);
- loop_push(st, s0);
- if (ft_iv >= 0 && ft_bd >= 0 && fs_ok && getlo(ft_iv) >= 0 && getln(ft_bd) >= 0) {
- lpfact[lsp - 1] = pfact(ft_iv, ft_bd, getln(ft_bd));
- }
- substmt();
- backedge(s0);
- jmp(st);
- loop_end(a, sc);
- snap_pop(s0);
- block_close(save);
- fdepth--;
- return 0;
-}
-// do stmt while (cond); the body runs once before the test. continue jumps
-// to the test, which is patched in after the body (f[0] == -2 marks that).
-int kw_do() {
- int body, s0, sc = -1, a, c; word f; int inf = 0;
- stmtonly();
- next();
- s0 = snap_push();
- body = here;
- loop_push(-2, s0);
- substmt();
- f = R_LOOP + (lsp - 1) * 8;
- fixall(f[7], here);
- backedge(s0);
- if (!is_id(s_while)) die("expected while after do");
- next();
- expect(s_lp);
- if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; }
- c = borrow_condition();
- expect(s_rp);
- expect(s_semi);
- if (!inf && live) sc = snap_push();
- a = hole(); op(O_JZ);
- jmp(body);
- loop_end(a, sc);
- snap_pop(s0);
- return 0;
-}
-// break and continue unwind the scopes they leave (exit_vars frees what those
-// blocks own on the way out). break records the analyzer state for the join
-// at the end of the loop; continue checks the state against the loop top.
-int kw_break() {
- word f;
- stmtonly();
- next(); expect(s_semi);
- if (lsp <= loopbase) { serr("break outside a loop or a switch"); return 0; }
- f = R_LOOP + (lsp - 1) * 8;
- if (f[4] != bxdepth) { serr("break can't leave a block expression"); return 0; }
- exit_vars(f[2] + 1, 0);
- if (live) exit_rec(hole()); else hole();
- op(O_JMP);
- live = 0;
- return 0;
-}
-// compile continue: jump to the test of the innermost loop.
-int kw_continue() {
- int j = lsp - 1; word f;
- stmtonly();
- next(); expect(s_semi);
- while (j >= loopbase && R_LOOP[j * 8] == -1) j--; // past any switch
- if (j < loopbase) { serr("continue outside a loop"); return 0; }
- f = R_LOOP + j * 8;
- if (f[4] != bxdepth) { serr("continue can't leave a block expression"); return 0; }
- backedge(f[3]);
- exit_vars(f[2] + 1, 0);
- if (f[0] == -2) { f[7] = fixnew(hole(), f[7]); op(O_JMP); } // do/while: the condition isn't placed yet
- else jmp(f[0]);
- live = 0;
- return 0;
-}
-// switch (e) { case 1, 2: ... case 'x': ... default: ... }
-// cases don't fall through: each one ends where the next begins, and break
-// leaves the switch early. case labels are constants.
-// one case's statements, analyzed from the state at the top of the switch.
-// the end of a case jumps out of the switch, so cases never fall through.
-int casebody(int s0, int live0) {
- int save = nloc;
- snap_restore(s0); live = live0;
- fdepth++;
- while (!is_op(s_rb) && !is_id(s_case) && !is_id(s_default)) {
- if (tk == T_EOF) { die("missing }"); break; }
- stmt();
- }
- block_close(save);
- fdepth--;
- if (live) { exit_rec(hole()); op(O_JMP); } // the end of a case leaves the switch
- live = 0;
- return 0;
-}
-// compile a switch statement.
-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;
- word[8] pbn; word[8] pbl;
- stmtonly();
- next();
- expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp);
- tg = ptagged(st);
- fdepth++;
- tmp = newloc(0); fslots++; // a hidden local holds the value
- laddr(lslot[tmp]); op(O_ST);
- live0 = live;
- s0 = snap_push();
- loop_push(-1, s0);
- expect(s_lb);
- while (!is_op(s_rb)) {
- 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();
- if (tg && tk == T_ID && pk_tk == T_OP && pk_tv == s_lp) gi = pm_find(tv);
- if (gi >= 0) {
- // case member(a, b): a member of a tagged enum, its fields bound to the names
- next(); next();
- nb = 0;
- while (!is_op(s_rp)) {
- if (nb >= 8) { die("too many names in a case"); break; }
- pbn[nb] = name(); nb++;
- if (!accept(s_comma)) break;
- }
- expect(s_rp);
- if (nb != pm_nf[gi]) serr("a case binds every field of its member");
- v = pm_tag[gi];
- if (v < 63) cov = cov | (1 << v);
- laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB);
- bj = fixnew(hole(), bj); op(O_JZ);
- expect(s_colon);
- arm = nloc;
- for (j = 0; j < nb && j < pm_nf[gi]; j++) {
- bi = newloc(pbn[j]); fslots++;
- ltype[bi] = pm_ft[gi * 8 + j];
- if (isfl(ltype[bi])) setflt(bi, 1);
- pbl[j] = bi;
- }
- 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);
- }
- casebody(s0, live0);
- block_close(arm);
- setlit(nxt, here);
- } else {
- while (1) {
- v = cexpr();
- if (tg && v >= 0 && v < 63) cov = cov | (1 << v);
- laddr(lslot[tmp]); op(O_LD);
- if (tg) op(O_LD);
- lit(v); op(O_SUB);
- bj = fixnew(hole(), bj); op(O_JZ);
- if (!accept(s_comma)) break;
- }
- expect(s_colon);
- nxt = hole(); op(O_JMP);
- fixall(bj, here);
- casebody(s0, live0);
- setlit(nxt, here);
- }
- } else if (is_id(s_default)) {
- next(); expect(s_colon);
- if (defent >= 0) serr("a switch has one default");
- skip = hole(); op(O_JMP); // only reached when nothing matched
- defent = here;
- casebody(s0, live0);
- setlit(skip, here);
- } else die("expected case or default");
- }
- next();
- snap_restore(s0); live = live0;
- if (tg && defent < 0) cover_warn(tg - 1, cov, sl);
- if (defent >= 0) { jmp(defent); loop_end(-1, -1); }
- else { a = hole(); op(O_JMP); loop_end(a, s0); } // nothing matched: out with the states from the top
- snap_pop(s0);
- block_close(save);
- fdepth--;
- return 0;
-}
-// return: coerce and check the value, run the analyzer's return rule, free
-// everything the function owns (exit_vars from depth 1), and jump to the
-// epilogue, which is a patch chain resolved when the function ends.
-int kw_return() {
- int c, returned = tint;
- stmtonly();
- next();
- if (!infn) serr("return outside a function");
- if (bxdepth) serr("return can't leave a block expression");
- if (is_op(s_semi)) { lit(0); c = C_PLAIN; if_ret(0, 1); }
- 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);
- else if (fty && !compat(fty, xty)) tcerr("return value", fty, xty);
- }
- if (nclo) {
- if (!clo_ret[nclo - 1]) clo_ret[nclo - 1] = returned;
- else if (clo_ret[nclo - 1] != returned) clo_ret[nclo - 1] = 0 - 1;
- }
- expect(s_semi);
- retcheck(c);
- borrow_cleanup();
- exit_vars(1, 0);
- epichain = fixnew(here, epichain);
- lit(rt.MW - 1); op(O_JMP);
- live = 0;
- return 0;
-}
-// o3 { ... }: an optimized region. o3 T x = e;: one o3 variable
-// (a block is compiled as normal code behind a jump and handed to the core
-// as a region once the function is complete: o3pend queues them. variables
-// declared outside that are o3 get registers too, and anything with a pointer
-// in it is fattened when control leaves the region.)
-int kw_o3() {
- int lo, i; word p; int save, outer;
- stmtonly();
- next();
- if (!is_op(s_lb)) { o3one = 1; localdecl(1); o3one = 0; return 0; }
- if (o3on || !infn) return block();
- reserve_code(2); lo = here + 2; lit(lo); op(O_JMP);
- o3on = 1; o3reset();
- save = nloc; outer = nloc;
- i = lbase;
- // o3 variables declared outside
- while (i < outer) {
- if (ltag[i] & 16) {
- o3promote(i);
- o3mkcache(i);
- o3refresh(i);
- }
- i++;
- }
- block();
- o3on = 0;
- 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;
- 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;
- while (i < outer) {
- ltag[i] = ltag[i] - (ltag[i] & 1792); o3cache[i] = 0;
- // leaving o3: fatten what they hold
- if ((ltag[i] & 16) && hasptr(ltype[i])) {
- vaddr(i);
- op(O_LD);
- lit(ltype[i]);
- callname(s_fatten);
- op(O_DROP);
- }
- i++;
- }
- return 0;
-}
-// unchecked turns the analyzer off for one statement
-int kw_unchecked() {
- int old = fchk;
- stmtonly();
- next();
- fchk = 0;
- substmt();
- fchk = old;
- 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)
-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;
-int reserve_asm_labels(int need) {
- int cap = asm_labelcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- asm_names = chk_resize(asm_names, asm_labelcap, cap); asm_sites = chk_resize(asm_sites, asm_labelcap, cap);
- asm_labelcap = cap; return cap;
-}
-int reserve_asm_references(int need) {
- int cap = asm_refcap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- asm_refs = chk_resize(asm_refs, asm_refcap, cap); asm_fixes = chk_resize(asm_fixes, asm_refcap, cap);
- asm_refcap = cap; return cap;
-}
-int asmblock() {
- af_forget();
- int nl = 0, nr = 0, i, k, c, s; word ln = asm_names, la = asm_sites, rn = asm_refs, ra = asm_fixes;
- expect(s_lb);
- while (!is_op(s_rb)) {
- if (tk == T_EOF) { die("unterminated asm block"); break; }
- if (tk == T_FLT) { op(OP_FLIT); emit(tv); next(); continue; }
- if (tk == T_NUM) { lit(tv); next(); continue; }
- if (tk == T_BSTR) { bstrlit(tv); next(); continue; }
- if (tk == T_STR) { strlit(tv); next(); continue; }
- if (is_op(s_hash)) {
- next();
- if (tk != T_NUM) die("# needs a number");
- emit(tv);
- next();
- continue;
- }
- if (is_op(s_minus)) {
- next();
- if (tk == T_NUM && tglue) {
- lit(0 - tv);
- next();
- } else {
- op(O_SUB);
- }
- continue;
- }
- if (is_op(s_colon)) {
- next();
- reserve_asm_labels(nl + 1); ln = asm_names; la = asm_sites;
- ln[nl] = name();
- la[nl] = here;
- nl++;
- continue;
- }
- if (tv[0] && !tv[1] && (c = mnemidx(tv[0])) >= 0) { op(c); next(); continue; }
- 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; 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; }
- s = lookup(tv);
- if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); next(); continue; }
- if (s >= 0 && skind[s] != K_FN && skind[s] != K_KW) { lit(sval[s]); next(); continue; }
- if (s >= 0 && skind[s] == K_FN) { fnref(s); next(); continue; }
- reserve_asm_references(nr + 1); rn = asm_refs; ra = asm_fixes;
- rn[nr] = tv; ra[nr] = hole(); nr++; next(); // a label further down this block
- }
- next();
- i = 0;
- while (i < nr) {
- k = -1; c = 0;
- while (c < nl) { if (ln[c] == rn[i]) k = c; c++; }
- if (k < 0) serrn(rn[i], "unknown name in asm");
- setlit(ra[i], la[k]);
- i++;
- }
- return 0;
-}
-// virtual enhancement bodies are recipes read as a flat range by the host.
-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 + 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();
- 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);
- 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 ----
-
-// 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,
-// stopping at the matching brace. it buffers the tokens in R_QBUF, then copies
-// them to a fresh object. quote compiles a list into the program as data and
-// returns a copy at run time; feed (below) pushes a list back as input.
-word .u grab() {
- int n = 0, depth = 0; word q;
- if (!is_op(s_lb)) die("expected {");
- next();
- while (1) {
- if (tk == T_EOF) { die("unterminated quote"); break; }
- if (is_op(s_lb)) depth++;
- if (is_op(s_rb)) { if (!depth) break; depth--; }
- reserve_R_QBUF((n + 1) * 3);
- R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++;
- next();
- }
- next();
- q = rt.new(n * 3 + 1);
- q[0] = n;
- rt.copy_w(q + 1, R_QBUF, n * 3);
- return u*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++; }
- return 0;
-}
-// 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 quote_tokens() {
- word q; int j, a, n;
- q = grab();
- j = hole(); op(O_JMP);
- n = q[0] * 3 + 1; reserve_code(n); a = here;
- wcopyto(q, n);
- 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(char* s) {
- int n = 0; word q;
- src_push(2, s, 0, 0);
- next();
- while (tk != T_EOF) {
- reserve_R_QBUF((n + 1) * 3);
- R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++;
- next();
- }
- src_pop();
- q = rt.new(n * 3 + 1);
- q[0] = n;
- 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.
-int feed(.d q) {
- src_push(1, q, 1, 1);
- next();
- return 0;
-}
-
-// ---- 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.
-// read the whole unit from stdin, up to a backtick (outside comments and
-// 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
-word .u readsrc() {
- int cap = 4090; word b; int n = 0, c; word nb; int w;
- 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 = rt.new(cap + cap + 6);
- rt.copy_w(nb, b, cap);
- rt.free(b);
- b = nb;
- cap = cap + cap + 6;
- }
- w = n >> 2;
- if (!(n & 3)) b[w] = 0;
- b[w] = b[w] | (c << ((n & 3) * 8));
- n++;
- }
- 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;
-}
-// skip a <...> group (type parameters)
-// prescan: before a unit is compiled, one dry pass over it records the
-// signature (parameter tags, .u return, o3) of every function declared in it,
-// so a call that appears before its definition is checked against the tags
-// the callee will have. it only needs the shape of a declaration, not its body.
-int pskipangle() {
- int dp = 0;
- while (tk != T_EOF) {
- if (is_op(s_lt)) dp++;
- else if (is_op(s_gt)) dp--;
- else if (is_op(s_shr)) dp = dp - 2;
- next();
- if (dp <= 0) return 0;
- }
- return 0;
-}
-// at the name after fn: read the signature's tags and record them
-// prescan: at the ( after a function name, read the parameter list and record
-// the signature packed into ssig (bit 0 .u return, bits 1..5 parameter count,
-// two bits per parameter from bit 6)
-int prescan_fn(int ret, int n, int o3) {
- int np = 0, tags = 0, t = 0, dp, s;
- next(); // the (
- if (!is_op(s_rp)) {
- np = 1; dp = 1;
- while (tk != T_EOF) {
- if (is_op(s_lp)) dp++;
- else if (is_op(s_rp)) { dp--; if (!dp) break; }
- else if (dp == 1 && is_op(s_comma)) { if (np <= 12) tags = tags + (t << (np + np - 2)); np++; t = 0; }
- else if (dp == 1 && is_op(s_dot)) { next(); if (is_id(s_m)) t = 1; else if (is_id(s_d)) t = 2; continue; }
- next();
- }
- if (np <= 12) tags = tags + (t << (np + np - 2));
- }
- next();
- 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;
- sflag[s] = sflag[s] | F_SIG | F_PRE;
- }
- if (o3) sflag[s] = sflag[s] | F_O3;
- return 0;
-}
-// 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; char* oldmodule = module_name; word oldimports = module_imports;
- dry++;
- src_push(kind, obj, 0, 0);
- next();
- while (tk != T_EOF) {
- 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) || typedef_ahead()) { 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;
- else if (tk == T_ID) {
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) {
- n = tv;
- next();
- prescan_fn(pend, n, po3);
- pend = 0;
- po3 = 0;
- continue;
- }
- if (pk_tk == T_OP && pk_tv == s_lt) { // list<T> in a type, or first<T>( a generic function
- n = tv; next(); dep = 0;
- while (tk != T_EOF) {
- if (is_op(s_lt)) {
- dep++;
- } else if (is_op(s_gt)) {
- dep--;
- } else if (is_op(s_shr)) dep = dep - 2;
- next();
- if (dep <= 0) break;
- }
- if (is_op(s_lp)) { prescan_fn(pend, n, po3); pend = 0; po3 = 0; }
- continue;
- }
- }
- }
- next();
- }
- src_pop();
- 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();
- 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 = 48;
-int ctx_push() {
- af_forget();
- word f;
- reserve_R_CTX((ctxsp + 1) * CTX_WORDS); f = R_CTX + ctxsp * CTX_WORDS;
- ctxsp++;
- 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; 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.
-int ctx_pop() {
- word f;
- ctxsp--;
- f = R_CTX + ctxsp * CTX_WORDS;
- local_drop(f[0]);
- fdrop(nloc);
- lend_drop(1 << 30, f[0]);
- lbase = f[1];
- fdepth = f[2];
- fslots = f[3];
- fchk = f[4];
- fret = f[5];
- 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]; 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 ----
-
-// 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
-// the shared exit (return jumps here through the epichain patch list). the
-// frame size is not known until the end, so the prologue's subtract is a hole
-// 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) {
- 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;
- lend_drop(1 << 30, nloc);
- 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(rt.ZFP); op(O_LD);
- if (!helper) { lit(0); op(O_PICK); }
- 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(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(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;
-}
-// emit the function epilogue, patching every return to it.
-int epilogue() {
- int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave);
- lit(0);
- fixall(epichain, here);
- 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(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(rt.ZFP); op(O_ST); setlit(done, here); setlit(node, here);
- }
- op(O_RET); borrow_initializer();
- setlit(fframe, fslots);
- infn = 0;
- local_drop(lbase);
- fdrop(lbase);
- td_end(0);
- return 0;
-}
-// the statements of a function between its braces, then the end-of-function
-// 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();
- if (ifcur && live && ifcur[1]) ifcur[4] = 1; // falling off the end gives 0
- if (fchk && live) {
- exit_vars(1, 1);
- 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:
-// int f(int a, node*.m n, .d x, b) { ... } <T> *list<T>.u mk(int n) { ... }
-// rty is 0 for fn (a plain word), ret is 1 for a .u return
-// (this is also where a function's signature is packed into ssig and its type
-// 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; 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;
- s = lookup(n);
- if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) {
- 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);
- }
- ifkk = 0;
- expect(s_lp);
- if (!is_op(s_rp)) {
- while (1) {
- t = 0;
- pty[np] = tword;
- pk = 0;
- if (!is_op(s_dot) && paramstart()) {
- 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, 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) {
- t = 1;
- } else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d");
- next();
- }
- 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;
- if (np < 12) tags = tags + (t << (np + np));
- np++;
- if (np >= 24) die("too many parameters");
- if (!accept(s_comma)) break;
- }
- }
- expect(s_rp);
- sig = ret + np + np + tags * 64;
- if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) {
- if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration");
- }
- else if ((sflag[s] & F_UNTYPED) && (tags || ret)) serrn(n, "was called before its tags were declared; put a prototype above the first call");
- ssig[s] = sig;
- sflag[s] = (sflag[s] | F_SIG) - (sflag[s] & F_PRE);
- if (ntp) { sflag[s] = sflag[s] | F_GEN; if (!ginst) gtmake(s); }
- if (typed) {
- if (!rty) rty = tword;
- ft = tfn(rty, np, pty, sig);
- 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");
- if (!ginst && ntp) { // a generic function is not compiled here: its tokens are kept and each set of type arguments gets its own copy
- if (sflag[s] & F_TPL) serrn(n, "is defined twice");
- sflag[s] = sflag[s] | F_TPL;
- gnew = gtmake(s);
- gskip();
- ntp = 0;
- 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(rt.MW - 1); op(O_JMP);
- return s;
- }
- sval[s] = here;
- sflag[s] = sflag[s] | F_DEF;
- fixall(sfix[s], here); sfix[s] = 0;
- fn_begin(chk, ret);
- finsym = s; finfer = !ret;
- 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(); borrow_prologue();
- i = 0;
- while (i < np) {
- 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);
- if (fchk && pt[i] == 1) lstate[t] = S_BORROW;
- if (fchk && pt[i] == 2) { lstate[t] = S_M; lown[t] = 1; }
- i++;
- }
- i = np;
- while (i) { i--; laddr(i); op(O_ST); }
- if (o3f) { // the region starts here: a jump, so the cpu enters it
- reserve_code(2); lo = here + 2; lit(lo); op(O_JMP);
- 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) {
- o3on = 0;
- o3fs = fslots;
- o3na = np;
- o3fn0 = sval[s];
- o3region(lo, hi, o3sl, o3rg, no3sl);
- }
- o3flush();
- ntp = 0; ifcur = 0;
- fn_remember(s, n);
- return s;
-}
-// 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.
-int gdecl(int typed) {
- ifgk = 0;
- if (isp == 0) ifgk = tpack();
- next();
- return gdecl2(0, 0, name());
-}
-// 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, 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");
- if (t == tvoid) serr("a variable can't be void");
- while (1) {
- if (accept(s_lk)) {
- k = cexpr(); expect(s_rk);
- if (!t) t = tword;
- arraytype = tarr(t, k); k = tsz(arraytype); reserve_code(k);
- s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype;
- while (k) { emit(0); k--; }
- if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
- } else if (t && isagg(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); vr = rsn - 1; vk = 1; vv = 0;
- if (t) stype[s] = t;
- emit(0);
- if (accept(s_eq)) {
- if (tk == T_STR) {
- 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 {
- 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;
- ng = toff; n = name();
- }
- expect(s_semi);
- if (gok && gty && kp) fact_add(kp, 0 - 1, gty, "");
- return 0;
-}
-// a constant cut to the sized integer type t, the way tnorm does at run time
-int cnorm(word t, int v) {
- int b, mask, sg;
- if (!t) return v;
- b = tbytes(t);
- if (!b || t[0] != TY_INT) return v;
- mask = (1 << (8 * b)) - 1;
- v = v & mask;
- if (t[6] & 1) { sg = 1 << (8 * b - 1); v = (v ^ sg) - sg; }
- return 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, 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) { 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;
-}
-// enhance [n] name(a, b) micro "recipe" [else fn]; n outputs, 1 if left out
-// enhance [n] name(a, b) vm { asm } [else fn];
-// 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; char*[8] pn;
- next();
- // enhance 2 divmod(a, b)
- if (tk == T_NUM) {
- if (tv < 0 || tv > 8) serr("an enhancement has at most 8 outputs");
- nout = tv;
- next();
- }
- n = name();
- expect(s_lp);
- if (!is_op(s_rp)) {
- while (1) {
- if (np == 8) die("an enhancement takes at most 8 inputs");
- 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 + 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;
- while (i < np) { *(na + i + hdelta) = here; p = pn[i]; while (*p) { emit(*p); p++; } emit(0); i++; }
- if (accept(s_micro)) {
- if (tk != T_STR) die("expected the recipe as a string");
- ebn = 0; while (tk == T_STR) { ebs(tv); next(); }
- reserve_code(ebn + 1); pay = here; wcopyto(ebuf, ebn + 1);
- } else if (accept(s_vm)) { pay = contiguous_asm(); len = here - pay; kind = 1; }
- else die("expected micro or vm");
- slot = nenh;
- if (slot >= 512) fatal("all 512 enhancement slots are taken");
- nenh++;
- p = dsc + hdelta;
- p[0] = slot; p[1] = kind; p[2] = np; p[3] = nout; p[4] = pay; p[5] = len; p[6] = na;
- s = addsym(n, K_ENH, slot);
- ssig[s] = np + nout * 16 + dsc * 256;
- if (accept(s_else)) { r = lookup(tv); if (r < 0) r = fwdfn(tv); next(); sfix[s] = r + 1; }
- expect(s_semi);
- if (!hdelta) {
- 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;
- }
- }
- return 0;
-}
-// recovery at the top level: skip to the next token that can start a
-// declaration, outside any braces
-int topsync() {
- int depth = 0;
- while (tk != T_EOF) {
- if (is_op(s_lb)) depth++;
- else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; }
- else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef
- || tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3
- || tv == s_memdebug || tv == s_enum || tv == s_include)) break;
- next();
- }
- panic = 0;
- return 0;
-}
-// 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; 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) {
- if (n >= 8) fatal("too many type parameters");
- ps[n] = name();
- n++;
- if (!accept(s_comma)) break;
- }
- closeangle();
- rt.fill_w(bs, 0, 8);
- i = 0;
- while (i < ntp) {
- j = 0;
- while (j < n && ps[j] != tpn[i]) j++;
- if (j == n) serrn(tpn[i], "isn't a type"); else bs[i] = tvar(j, ps[j]);
- i++;
- }
- if (ntp && dty) dty = subst(dty, bs);
- rt.copy_w(tpn, ps, n); ntp = n;
- if (ginst) ntp = 0;
- return 0;
-}
-// the return type of a function or the type of a global, with unknown names allowed for now
-int rettype() {
- tvauto = 1;
- dtype();
- tvauto = 0;
- return 0;
-}
-// finish a function declaration. an immediate fn also becomes a keyword
-// (kind K_KW whose handler is the function), which is how parse words work:
-// the parser calls it with kwcall the moment it meets the name.
-int fndone(int s, int imm) {
- int k;
- ntp = 0;
- if (imm) {
- if (hdelta) serr("parse words can't be defined while building an image");
- k = addsym(sname[s], K_KW, sval[s]);
- sflag[k] = F_DEF | F_STMT | F_EXPR | F_TOP;
- }
- return 0;
-}
-// one top-level item. this is the dispatcher for everything a unit can
-// contain: unchecked / immediate / o3 prefixes, fn declarations (with or
-// without a leading return type), globals, struct, type, enum, include,
-// typecheck, memdebug, enhance, const, and parse words used at the top level.
-// anything else is a typed global or function: a type, a name, and then
-// ( or < means function, otherwise a global.
-int topitem0() {
- int r, i;
- dsn = 0; dnm = 0;
- if (docs_on && dn && dline == tline && !ginst) {
- if (dn > docsnapcap) { docsnapcap = dn; dsnap = chk_resize(dsnap, 0, docsnapcap); }
- i = 0; while (i < dn) { dsnap[i] = dbuf[i]; i++; }
- dsn = dn; dline = 0;
- }
- r = topitem1();
- if (dsn && dnm) doc_attach(dnm);
- dsn = 0; dnm = 0;
- return r;
-}
-// compile one top-level item, after the first check of its keyword.
-int topitem1() {
- int chk = 1, imm = 0, s, k, n;
- if (panic) return topsync();
- if (is_id(s_unchecked)) {
- next();
- if (accept(s_semi)) { check_on = 0; return 0; } // unchecked; switches the analyzer off from here on
- chk = 0;
- }
- if (is_id(s_immediate)) { next(); imm = 1; }
- o3nxt = 0;
- if (is_id(s_o3)) { next(); o3nxt = 1; }
- ntp = 0; ifkk = 0;
- if (is_id(s_fn)) {
- peek();
- if (!(pk_tk == T_OP && pk_tv == s_lp)) {
- ifkp = tpack(); ifkk = 1;
- if (isp != 0) ifkk = 0;
- next();
- n = name();
- if (ginst) n = ginsn;
- dty = 0;
- genparams();
- return fndone(fndef(chk, 0, 0, n), imm);
- }
- }
- if (!chk || imm || o3nxt) { // has to be a function, with a return type
- ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2;
- if (isp != 0) ifkk = 0;
- rettype(); n = name(); if (ginst) n = ginsn; genparams();
- if (dtag && dtag != S_U) serr("the only return tag is .u");
- if (!is_op(s_lp)) { die("expected a function"); return 0; }
- return fndone(fndef(chk, dty, dtag == S_U, n), imm);
- }
- if (is_id(s_var)) return gdecl(0);
- if (is_id(s_struct)) { kw_struct(); return 0; }
- if (is_id(s_type)) return kw_type();
- if (typedef_ahead()) return kw_typedef();
- if (is_id(s_typecheck)) return kw_typecheck();
- if (is_id(s_docs)) return kw_docs();
- 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)) {
- 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; }
- }
- if (tstart() || (tk == T_ID && lookup(tv) < 0)) { // int f(...) { }, T first<T>(...) { }, int g = 1;
- ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2;
- if (isp != 0) ifkk = 0;
- rettype(); n = name();
- if (is_op(s_lp) || is_op(s_lt)) {
- if (ginst) n = ginsn;
- genparams();
- if (dtag && dtag != S_U) serr("the only return tag is .u");
- return fndone(fndef(chk, dty, dtag == S_U, n), 0);
- }
- if (ntp) { serrn(tpn[0], "isn't a type"); ntp = 0; }
- return gdecl2(dty, dtag, n);
- }
- die("expected a declaration");
- return 0;
-}
-// ---- 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;
- while (i < gng) {
- if (gnm[i] == n) return i;
- i++;
- }
- return 0 - 1;
-}
-// does a type still mention a type parameter?
-word hasvar(word t) {
- int k = t[0], i; word v;
- if (k == TY_VAR) return 1;
- if (k == TY_PTR || k == TY_ARR || k == TY_DYN) return hasvar(t[4]);
- if (k == TY_FN) {
- if (hasvar(t[4])) return 1;
- i = 0;
- while (i < t[3]) {
- if (hasvar(t[7 + i])) return 1;
- i++;
- }
- return 0;
- }
- if (k == TY_STRUCT && t[5]) {
- v = t[6]; i = 0;
- while (i < v[0]) {
- if (hasvar(v[1 + i])) return 1;
- i++;
- }
- }
- return 0;
-}
-// add the current token to the item being recorded
-int grec_add() {
- word nb;
- if (gn * 3 + 4 > gcap) {
- nb = rt.new(gcap + gcap);
- rt.copy_w(nb, gbuf, gcap);
- rt.free(gbuf);
- gbuf = nb;
- gcap = gcap + gcap;
- }
- gbuf[1 + gn * 3] = tk; gbuf[2 + gn * 3] = tv; gbuf[3 + gn * 3] = tpack();
- gn++;
- return 0;
-}
-// the template entry for generic function s, made on first sight of its header
-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_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;
-}
-int reserve_generics(int need) {
- int cap = gtcap;
- if (need <= cap) return cap;
- 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) {
- int cap = gicap;
- if (need <= cap) return cap;
- while (cap < need) cap = cap * 2;
- gi_t = chk_resize(gi_t, gi_n, cap); gi_sym = chk_resize(gi_sym, gi_n, cap);
- gi_done = chk_resize(gi_done, gi_n, cap); gi_ar = chk_resize(gi_ar, gi_n * 8, cap * 8);
- gicap = cap; return cap;
-}
-// make the generic instance of the function or struct s for its type arguments.
-int gtmake(int s) {
- int k = 0;
- if (!gtab) ginit();
- while (k < gt_n) {
- if (gt_sym[k] == s) break;
- k++;
- }
- if (k == gt_n) {
- if (gt_n >= gtcap) reserve_generics(gt_n + 1);
- gt_n++;
- 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;
- rt.copy_w(gt_nm + k * 8, tpn, ntp);
- return k;
-}
-// skip a function body without compiling it: from the { to its matching }
-int gskip() {
- int depth = 0;
- if (!is_op(s_lb)) { die("expected { to start the function"); return 0; }
- while (tk != T_EOF) {
- if (is_op(s_lb)) depth++;
- else if (is_op(s_rb)) {
- depth--;
- if (depth <= 0) { next(); break; }
- }
- next();
- }
- return 0;
-}
-// the item just read defined generic function gnew: keep its tokens
-int gattach(word .d buf, int n) {
- n--; // the last token belongs to the next item
- 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;
- // compiler storage, not program objects: internal, so live_objects and leak reports skip it
- q = chk_resize(0, 0, 1 + count * 3); q[0] = count; rt.copy_w(q + 1, gbuf + 1, count * 3);
- meta = chk_resize(0, 0, 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 = 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; 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; }
- k++;
- }
- if (t < 0) return s;
- np = gt_np[t];
- i = 0;
- while (i < np) {
- if (!bs[i]) bs[i] = tword;
- if (hasvar(bs[i])) return s;
- i++;
- }
- k = 0;
- while (k < gi_n) {
- if (gi_t[k] == t) {
- j = 0;
- while (j < np && gi_ar[k * 8 + j] == bs[j]) j++;
- if (j == np) return gi_sym[k];
- }
- k++;
- }
- if (gi_n >= gicap) reserve_instances(gi_n + 1);
- i = 0; j = 0;
- 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;
- while (k < np) {
- if (k) { nm[i] = ','; i++; }
- a = bs[k]; nd = 0;
- while (a && nd < 20) { dg[nd] = '0' + (a & 15); nd++; a = a >> 4; }
- while (nd) { nd--; nm[i] = dg[nd]; i++; }
- k++;
- }
- nm[i] = '>'; i++; nm[i] = 0;
- ns = addsym(intern(nm), K_FN, tpack());
- sflag[ns] = F_SIG | (sflag[s] & F_O3);
- ssig[ns] = ssig[s];
- k = gi_n; gi_n++;
- gi_t[k] = t; gi_sym[k] = ns; gi_done[k] = 0;
- rt.copy_w(gi_ar + k * 8, bs, np);
- return ns;
-}
-// compile one instance: replay the template's tokens with its type
-// parameters bound
-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];
- 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; 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
-// any they ask for in turn
-int gdrain() {
- int k, more = 1;
- if (infn || ginst) return 0;
- if (panic) return 0;
- while (more) {
- more = 0;
- k = 0;
- while (k < gi_n) {
- if (!gi_done[k] && gt_tok[gi_t[k]]) {
- gi_done[k] = 1;
- gcompile(k);
- more = 1;
- }
- k++;
- }
- }
- return 0;
-}
-// one top-level item, recording its tokens so that a generic function's can be kept
-int topitem() {
- int sb = gbuf, sn = gn, sc = gcap, sr = grec, sg = gnew, rb, rn;
- if (ginst) return topitem0();
- 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 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() {
- 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 = 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;
- while (i <= la) {
- last = row[0]; row[0] = i; j = 1;
- while (j <= lb) {
- x = row[j] + 1;
- y = row[j - 1] + 1;
- z = last + (a[i - 1] != b[j - 1]);
- last = row[j];
- if (y < x) x = y;
- if (z < x) x = z;
- row[j] = x;
- j++;
- }
- i++;
- }
- return row[lb];
-}
-// the part of a symbol a bare name is compared with: put_c for rt.put_c, the whole name otherwise
-char* similar_tail(char* name) {
- int k = 0; char* tail = name;
- while (name[k] && name[k] != '.') k++;
- if (name[k] && module_named(name, k)) tail = name + k + 1;
- return tail;
-}
-// is name[0..k) a module the compiler knows?
-int module_named(char* name, int k) {
- char* prefix = chk_resize(0, 0, k + 1); int known;
- rt.copy_w(prefix, name, k); prefix[k] = 0;
- known = module_known(prefix);
- rt.free(prefix);
- return known;
-}
-// 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. a module's export is
-// compared without its prefix, so putc finds rt.put_c
-int similar(int n) {
- int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3;
- if (rt.len_w(n) < 3) return 0; // one edit turns g into gc: too short to guess from
- if (lim < 1) lim = 1;
- if (lim > 3) lim = 3;
- while (j < nsym) {
- k = skind[j];
- if (k == K_FN || k == K_GVAR || k == K_GARR || k == K_CONST) {
- if (sflag[j] & F_DEF) {
- if (sname[j] != n) {
- dist = editdist(n, similar_tail(sname[j]));
- if (dist < bd) { bd = dist; best = sname[j]; }
- }
- }
- }
- j++;
- }
- if (bd > lim) return 0;
- return best;
-}
-// the field or method of struct sn closest to n (statics is 0), or the static function or constant
-// (statics is 1), when it is close enough to be a likely typo. the limits are those of similar.
-char* similar_member(char* sn, word st, char* n, int statics) {
- int j, bd = 99, dist, k, ln = rt.len_w(sn), lim = rt.len_w(n) / 3, nf; char* best = 0; char* tail;
- if (rt.len_w(n) < 3) return 0;
- if (lim < 1) lim = 1;
- if (lim > 3) lim = 3;
- if (!statics && st && st[0] == TY_STRUCT) {
- nf = st[3]; k = 0;
- while (k < nf) {
- tail = st[4][k * 3]; dist = editdist(n, tail);
- if (dist == 2 && swapped(n, tail)) dist = 1;
- if (dist > 0 && dist < bd) { bd = dist; best = tail; }
- k++;
- }
- }
- j = 0;
- while (j < nsym) {
- k = skind[j];
- if (((k == K_FN && ((sflag[j] & F_STATIC) != 0) == statics) || (statics && k == K_CONST)) && rt.len_w(sname[j]) > ln + 1 && sname[j][ln] == '.') {
- tail = sname[j] + ln + 1;
- if (has_prefix(sname[j], sn, ln) && !has_dot(tail)) {
- dist = editdist(n, tail);
- if (dist == 2 && swapped(n, tail)) dist = 1;
- if (dist > 0 && dist < bd) { bd = dist; best = tail; }
- }
- }
- j++;
- }
- if (bd > lim) return 0;
- return best;
-}
-// are a and b the same but for two neighbouring chars swapped (itao for itoa)? a swap counts as one typo.
-int swapped(char* a, char* b) {
- int i = 0, la = rt.len_w(a);
- if (la != rt.len_w(b)) return 0;
- while (a[i] && a[i] == b[i]) i++;
- if (!a[i] || !a[i + 1] || a[i] != b[i + 1] || a[i + 1] != b[i]) return 0;
- i = i + 2;
- while (a[i] && a[i] == b[i]) i++;
- return !a[i];
-}
-// does s start with the first n chars of p?
-int has_prefix(char* s, char* p, int n) {
- int i = 0;
- while (i < n && s[i] == p[i]) i++;
- return i == n;
-}
-// does the string contain a dot?
-int has_dot(char* s) {
- int i = 0;
- while (s[i]) { if (s[i] == '.') return 1; i++; }
- return 0;
-}
-// ends an error message with "; did you mean 'x'?" when a member of struct sn is close to n
-int suggest_member(char* sn, word st, char* n, int statics) {
- char* best = similar_member(sn, st, n, statics);
- if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
- return 0;
-}
-// a copy of s[a..e) as a string
-char* lib_copy(char* s, int a, int e) {
- char* b = chk_resize(0, 0, e - a + 1); rt.copy_w(b, s + a, e - a); b[e - a] = 0;
- return b;
-}
-// group.name, as a fresh string: the lists below outlive the name table of a check
-char* lib_join(char* group, char* name) {
- int g = rt.len_w(group), n = rt.len_w(name); char* b = chk_resize(0, 0, g + n + 2);
- rt.copy_w(b, group, g); b[g] = '.'; rt.copy_w(b + g + 1, name, n); b[g + n + 1] = 0;
- return b;
-}
-// the declarations of a library, read from its source text, as a list of [next, tail, path]:
-// a top-level name's path is the name itself, a static method's is Group.name. the scan
-// only looks at shapes: a name before ( or a declarator's = ; , at the top, or inside a
-// struct's braces before (. qualified calls (run.statement_hook(...)) are not declarations.
-word lib_scan(char* s) {
- int i = 0, depth = 0, paren = 0, init = 0, la = -1, le = 0, dotted = 0, after_dot = 0, words = 0, skip = 0, gpend = 0, gdepth = -1, c, a, k;
- char* group = 0; char* path; word list = 0, entry, back = 0;
- while (s[i]) {
- c = s[i];
- if (c == '/' && s[i + 1] == '/') { while (s[i] && s[i] != 10) i++; continue; }
- if (c == '/' && s[i + 1] == '*') { i = i + 2; while (s[i] && !(s[i] == '*' && s[i + 1] == '/')) i++; if (s[i]) i = i + 2; continue; }
- if (c == 34 || c == 39) { i++; while (s[i] && s[i] != c) { if (s[i] == 92 && s[i + 1]) i++; i++; } if (s[i]) i++; la = -1; continue; }
- if (c == 32 || c == 9 || c == 10 || c == 13) { i++; continue; }
- if (isdig(c)) { while (isidc(s[i])) i++; la = -1; continue; }
- if (isal(c)) {
- a = i; while (isidc(s[i])) i++;
- if (gpend) { group = lib_copy(s, a, i); gpend = 0; entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = group; entry[2] = group; list = entry; }
- if (depth == 0 && paren == 0 && !init) {
- if (words == 0 && ((i - a == 9 && rt.eq_w(lib_copy(s, a, i), "namespace")) || (i - a == 7 && rt.eq_w(lib_copy(s, a, i), "include")))) skip = 1;
- if (i - a == 6 && rt.eq_w(lib_copy(s, a, i), "struct")) { gpend = 1; group = 0; }
- words++;
- }
- la = a; le = i; dotted = after_dot; after_dot = 0;
- continue;
- }
- after_dot = c == '.';
- if (c == '<' && s[i + 1] != '<' && s[i + 1] != '=' && paren == 0 && !init && (depth == 0 || (depth == 1 && group))) {
- // generic parameters: int find<T>(...) is still find
- k = 1; i++; while (s[i] && k) { if (s[i] == '<') k++; if (s[i] == '>') k--; i++; }
- continue;
- }
- if (c == '(' && paren == 0 && !init && la >= 0 && !dotted && !skip) {
- if (depth == 0 || (depth == 1 && group && gdepth == 0)) {
- path = lib_copy(s, la, le);
- entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = path; entry[2] = path;
- if (depth == 1) entry[2] = lib_join(group, path);
- list = entry;
- }
- }
- if (((c == '=' && s[i + 1] != '=') || c == ';' || c == ',' || c == '[') && depth == 0 && paren == 0 && !init && la >= 0 && !dotted && !skip && words > 1) {
- entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = lib_copy(s, la, le); entry[2] = entry[1]; list = entry;
- }
- if (c == '=' && s[i + 1] != '=' && depth == 0 && paren == 0) init = 1;
- if (c == '(' || c == '[') paren++;
- if ((c == ')' || c == ']') && paren > 0) paren--;
- if (c == '{') { if (depth == 0 && paren == 0 && group) gdepth = 0; else if (depth == 0) group = 0; depth++; }
- if (c == '}' && depth > 0) { depth--; if (depth == 0) { group = 0; gdepth = -1; words = 0; skip = 0; init = 0; } }
- if (c == ';' && depth == 0 && paren == 0) { init = 0; words = 0; skip = 0; group = 0; }
- if (c == ',' && depth == 0 && paren == 0) init = 0;
- la = -1; i++;
- }
- // first declared first: Text.itoa comes before Bytes.itoa
- while (list) { entry = list[0]; list[0] = back; back = list; list = entry; }
- return back;
-}
-// the embedded libraries, scanned once
-word std_names, icon_names; int names_read;
-word lib_names(char* module) {
- if (!names_read) { names_read = 1; std_names = lib_scan(std_text()); icon_names = lib_scan(icon_text()); }
- if (rt.eq_w(module, "std")) return std_names;
- return icon_names;
-}
-// the libraries read from files, named in src/lib/modules.txt (one per line, # starts a
-// comment), as a list of [next, name, declarations, scanned]. the compiler names none of them.
-word file_libs; int file_libs_read;
-word lib_files() {
- int i = 0, a; word entry, tail = 0; char* save = incres;
- if (file_libs_read) return file_libs;
- file_libs_read = 1;
- char* text = inc_open("src/lib/modules.txt");
- incres = save;
- if (!text) return 0;
- while (text[i]) {
- while (text[i] == 32 || text[i] == 9 || text[i] == 10 || text[i] == 13) i++;
- if (text[i] == '#') { while (text[i] && text[i] != 10) i++; continue; }
- a = i; while (isidc(text[i])) i++;
- if (i == a) { if (text[i]) i++; continue; }
- entry = chk_resize(0, 0, 4); entry[1] = lib_copy(text, a, i);
- if (tail) tail[0] = entry; else file_libs = entry;
- tail = entry;
- }
- rt.free(text);
- return file_libs;
-}
-// the entry of a file library, or 0
-word lib_file(char* name) {
- word p = lib_files();
- while (p) { if (rt.eq_w(p[1], name)) return p; p = p[0]; }
- return 0;
-}
-// a file library's declarations, read and scanned the first time they are needed
-word lib_file_names(word entry) {
- char* save = incres;
- if (entry[3]) return entry[2];
- entry[3] = 1;
- char* filename = inc_cat("src/lib", entry[1]); 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);
- char* text = inc_open(full);
- if (text) entry[2] = lib_scan(text);
- rt.free(text);
- incres = save;
- return entry[2];
-}
-// where name is in a library's list: Text.itoa is itself, a bare itoa is a top-level
-// declaration first, then a static method of some group. 0 when it is not there.
-char* lib_path(word list, char* name) {
- word p = list; int k = 0;
- while (p) { if (rt.eq_w(p[2], name)) return p[2]; p = p[0]; }
- while (name[k]) { if (name[k] == '.') return 0; k++; }
- p = list;
- while (p) { if (rt.eq_w(p[1], name)) return p[2]; p = p[0]; }
- return 0;
-}
-// the module that exports a bare name, and the path after its prefix: put_c is rt's put_c,
-// itoa is std's Text.itoa. every owner, best first, is a list of [next, module, path]: the
-// compiler's own modules, then the modules this unit loaded, then the libraries, which are
-// read from their source whether this unit loaded them or not.
-word export_list, export_tail; char* export_path;
-int export_add(char* module, char* path) {
- word p = export_list;
- if (!path) return 0;
- while (p) { if (rt.eq_w(p[1], module)) return 0; p = p[0]; }
- p = chk_resize(0, 0, 3); p[1] = module; p[2] = path;
- if (export_tail) export_tail[0] = p; else export_list = p;
- export_tail = p;
- return 0;
-}
-word export_owners(char* name) {
- word p = included_modules;
- export_list = 0; export_tail = 0;
- export_add("rt", export_in("rt", name));
- export_add("core", export_in("core", name));
- export_add("run", export_in("run", name));
- while (p) { export_add(p[1], export_in(p[1], name)); p = p[0]; }
- export_add("std", lib_path(lib_names("std"), name));
- export_add("icon", lib_path(lib_names("icon"), name));
- p = lib_files();
- while (p) { export_add(p[1], lib_path(lib_file_names(p), name)); p = p[0]; }
- return export_list;
-}
-char* export_owner(char* name) {
- word p = export_owners(name);
- if (!p) return 0;
- export_path = p[2]; return p[1];
-}
-// name, when module defines it: a function, a global, a constant or a type of it. 0 otherwise.
-char* export_in(char* module, char* name) {
- int s = lookup_raw(methname(module, name));
- if (s < 0) return 0;
- if (skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF))) return name;
- return 0;
-}
-// end a compile unit from token from onward, and report what it defined.
-int unit_end(int from) {
- int i = from, best; char* owner;
- effect_finish(from);
- panic = 0;
- while (i < nsym) {
- // a bodyless declaration (fn f();) promises a definition elsewhere. in check mode that
- // elsewhere is another file of the same build, which this document can't see
- if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i] && !(cmode && (sflag[i] & F_DECL)) && !missing_import_name(sname[i])) {
- tunpack(sval[i]);
- owner = export_owner(sname[i]);
- best = 0; if (!owner) best = similar(sname[i]);
- if (!dry) {
- errat();
- rt.put_s(sname[i]); rt.put_s(" is used but never defined");
- if (owner) {
- rt.put_s("; did you mean to ");
- if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); }
- rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(export_path); rt.put_s("?");
- }
- else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
- errend();
- }
- }
- i++;
- }
- missing_imports = 0;
- if (nerr) giveup();
- return 0;
-}
-
-// compile a token list as the body of a new function, or (top) as top-level
-// declarations. returns the entry address of the function.
-// (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.)
-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; 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);
- next();
- if (top) unit_top();
- else {
- entry = here;
- fn_begin(1, 0);
- prologue(); borrow_prologue(); borrow_begin();
- while (tk != T_EOF) stmt();
- unused_check(lbase);
- if (fchk && live) exit_vars(1, 1);
- epilogue();
- }
- src_pop();
- unit_end(from);
- if (hop >= 0) setlit(hop, here);
- if (here > ptop) ptop = here;
- 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.
-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(char* s) {
- word q = tokens(s); int r = eval(q);
- rt.free(d*q);
- return r;
-}
-
-// ---- 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,
-// constants, enhancements) as the image's trailing symbol table
-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 + rt.len_w(sname[i]);
- i++;
- }
- reserve_code(words); i = from;
- // the docs first: each one's bytes go in the image here, and its symbol
- // takes their address (the bytes come after the code, so no code moves)
- while (i < nsym) {
- if (skind[i] == K_DOC) {
- b = sval[i];
- sval[i] = here;
- emit(b[0]);
- k = 0;
- while (k < ((b[0] + 7) >> 3)) { emit(b[1 + k]); k++; }
- if (!(b[0] & 7)) emit(0);
- }
- i++;
- }
- table = here; i = from;
- while (i < nsym) {
- if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) {
- emit(skind[i]); emit(sval[i]); emit(ssig[i]);
- p = sname[i];
- while (*p) { emit(*p); p++; }
- emit(0);
- }
- i++;
- }
- emit(0);
- return table;
-}
-// the reverse: read a blob and define its names, so a running genome knows its
-// own functions by name
-// hide: leave out the language server's names. functions are entered before
-// constants and globals in the blob, so the server's functions come after
-// chk_mark but the runtime's constants and globals come after it too: the
-// 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("run.chk_mark"), srvn = intern("lsp.LSP_SERVER");
- while (*p) {
- n = intern(p + 3);
- if (hide && n == srvn) after = 1;
- 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; rt.install(p[2] >> 8); }
- }
- p = p + 3 + rt.len_w(p + 3) + 1;
- }
- return 0;
-}
-// compile the rest of stdin for org into a growing output buffer. the image's
-// addresses remain logical addresses at org while its temporary storage grows.
-// image_words and image_size expose the finished image; initial is its first
-// buffer reservation. the matching host layout must still fit the finished image.
-int build_image_grow(int org, int initial) {
- int sh = here, sd = hdelta, sl = hlimit, sub = ubase, svl = vislo, sns = nsym, sen = nenh, n;
- word saved_buffer = image_buffer; int saved_active = image_active, saved_org = image_org, saved_capacity = image_capacity;
- if (org < 0 || initial < 7 || initial > 0x7fffffff - org) fatal("bad image reservation");
- 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(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;
- image_buffer[6] = here;
- n = here - org; image_words = image_buffer; image_size = n;
- popsyms(sns);
- ubase = sub; vislo = svl; here = sh; hdelta = sd; hlimit = sl; nenh = sen;
- image_buffer = saved_buffer; image_active = saved_active; image_org = saved_org; image_capacity = saved_capacity;
- return n;
-}
-// 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 = rt.syscall(9, 0, 0, 0);
- if (dst < 0 || n > words - dst) fatal("image destination too small");
- rt.copy_w(dst, image_words, n);
- return n;
-}
-
-// ---- 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 > 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
-// spellings the parser compares against; zc_tables fills the operator and
-// keyword tables. an operator is a name, a precedence and a code sequence in
-// 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;
- 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; ntd = 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("--");
- s_star = intern("*");
- s_slash = intern("/");
- s_amp = intern("&");
- s_colon = intern(":");
- s_hash = intern("#");
- s_minus = intern("-");
- s_dot = intern(".");
- s_q = intern("?");
- s_case = intern("case");
- s_default = intern("default");
- s_andand = intern("&&");
- s_oror = intern("||");
- 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("run.start");
- s_main = intern("main");
- s_free = intern("rt.free");
- s_recycle = intern("rt.recycle"); s_copy_w = intern("rt.copy_w");
- s_do = intern("do");
- s_while = intern("while");
- s_enum = intern("enum");
- s_include = intern("include");
- 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;
-}
-// operators and keywords. these are plain tables in memory: a program can
-// add operators, change precedences or replace keywords with its own.
-// (the order of the first binary operators matters: cexpr refers to * + - << >>
-// & | by their index, and the comments mark which ones)
-int zc_tables() {
- add_unop("!", "0 $ 0 ~ - | 63 > 1 +"); // index 0
- add_unop("!!", "0 $ 0 ~ - | 63 > 0 ~ -"); // index 1: normalize to 0 or 1
- add_unop("~", "-1 ^"); // index 2
- add_unop("-", "0 ~ -");
- add_binop("*", 10, "*"); // the first few indexes are used by cexpr
- add_binop("/", 10, "idiv"); // native divide, wired in by rt.zc
- add_binop("%", 10, "imod");
- add_binop("+", 9, "+"); // 3
- add_binop("-", 9, "-"); // 4
- add_binop("<<", 8, "ishl"); // 5
- add_binop(">>", 8, ">"); // 6
- add_binop("<", 7, "<");
- add_binop(">", 7, "~ <");
- add_binop("<=", 7, "~ < 1 ^");
- add_binop(">=", 7, "< 1 ^");
- add_binop("==", 6, "- 0 $ 0 ~ - | 63 > 1 +");
- add_binop("!=", 6, "- 0 $ 0 ~ - | 63 > 0 ~ -");
- add_binop("&", 5, "&"); // 13
- add_binop("^", 4, "^");
- add_binop("|", 3, "|"); // 15
- add_binop("&&", -1, 0);
- add_binop("||", -2, 0);
- addkop(s_eq); addkop(s_arrow); addkop(s_inc); addkop(s_dec);
- add_kw("let", kw_var, F_STMT);
- add_kw("if", kw_if, F_STMT);
- add_kw("while", kw_while, F_STMT);
- 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 | F_STMT);
- add_kw("embed", kw_embed, 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);
- add_kw("return", kw_return, F_STMT);
- add_kw("unchecked", kw_unchecked, F_STMT);
- add_kw("asm", kw_asm, F_STMT | F_EXPR);
- add_kw("quote", kw_quote, F_EXPR);
- add_kw("sizeof", kw_sizeof, F_EXPR);
- add_kw("typeof", kw_typeof, F_EXPR);
- add_kw("extern", kw_extern, F_TOP);
- add_kw("cfunc", kw_cfunc, F_EXPR);
- add_kw("o3", kw_o3, F_STMT);
- add_kw("len", kw_len, F_EXPR);
- add_kw("cap", kw_cap, F_EXPR);
- add_kw("push", kw_push, F_EXPR);
- add_kw("pop", kw_pop, F_EXPR);
- 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.
-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 && 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 <= 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 + 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) { 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
-// the compiler, then either run the language server (a genome that carries it)
-// or compile the unit on stdin, collect the garbage that compiling made, and
-// call main().
-int start() {
- int s, f;
- rt.heap_init(); chk_heap = 1;
- shield_types();
- zc_init();
- blob_load(rt.GORG[5], 0);
- zc_tables();
- types_init();
- 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.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;
- 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) 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("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(); }
- rt.halt();
- return 0;
-}
-
-// ---- 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
-// keywords), so the document's parse words, operators and types change what
-// the rest of it means exactly as they would in a real build. the symbols
-// the document made stay in the tables afterwards for the highlighter.
-// chk_mark is the last function of the compiler proper, and LSP_SERVER (in
-// src/lsp/server.zc) the first constant of the server: the server's names are
-// hidden from the document, the runtime's and the compiler's are not.
-int chk_mark() { return 0; }
-// reset check mode for a new document.
-int chk_reset() {
- 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 = 1; dn = 0; dline = 0; docpending = 0; dsect = 0; dsn = 0; dnm = 0;
- pq_n = 0;
- pendot = 0;
- qdone = 0;
- ch = 0;
- chkind = 0;
- line = 1;
- tk = 0;
- tv = 0;
- tglue = 0;
- tline = 1;
- toff = 0 - 1; toklen = 0; tpend = 0 - 1; tpline = 1;
- infn = 0;
- fdepth = 0;
- fslots = 0;
- fchk = 0;
- fret = 0;
- live = 0;
- bxdepth = 0;
- epichain = 0;
- fframe = 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;
- nfact = 0; fortest = 0; forstep = 0; argloc = 0; arrloc = 0; njps = 0; extop = 0; nnumops = 0;
- nenh = 0; panic = 0; nerr = 0; dgprev = 0; nclo = 0; newsz = 0 - 1; nomagic = 0; callpos = 0;
- grec = 0; gnew = 0 - 1; gt_n = 0; gi_n = 0; ginst = 0; gtab = 0;
- return 0;
-}
-// src: the document, 4 chars to a word and a 0 at the end. buf, cap: where
-// the records go. sets: 4 * LSW words for the name sets. lim: code may grow up
-// to here. every block the caller holds is a heap object reachable from its
-// globals, so the check's garbage is collected and nothing is cut off.
-// 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) 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 = 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(rt.GORG[5], 1);
- zc_tables();
- types_init();
- here = 0; hdelta = 0; hlimit = lim; ptop = 0;
- ubase = nsym; vislo = 0;
- prescan(3, src);
- src_push(3, src, 0, 0);
- next();
- unit_top();
- rs_fix();
- unit_end(ubase);
- if (ifx) if_emit();
- src_pop();
- rs_sort();
- chk_end();
- return 0;
-}
-// walk a document's tokens with the lexer as the check left it. hl_begin
-// loads the first token into tk, tv, toff and toklen; next() moves on; hl_end
-// restores the previous state.
-// the syntax highlighter's view of the lexer. it re-lexes the document after
-// the check, so the tokens it sees are cut exactly where the compiler's were
-// (parse words that changed the operators included).
-int hl_begin(int src) {
- chkabort = 0; isp = -1; pq_n = 0; pendot = 0; qdone = 0; panic = 0;
- dry++;
- src_push(3, src, 0, 0);
- next();
- return 0;
-}
-// end a highlight pass: leave the source and the dry run.
-int hl_end() {
- src_pop();
- dry--;
- isp = -1;
- pq_n = 0;
- return 0;
-}
-// the source of the std and icon libraries, embedded when the compiler is built, so
-// `include std;` and `include icon;` need no file at run time.
-embed "src/lib/std.zc" as bstr std_embedded;
-embed "src/lib/icon.zc" as bstr icon_embedded;
-bstr std_source() { return std_embedded; }
-bstr icon_source() { return icon_embedded; }
diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc
new file mode 100644
index 0000000..45269a4
--- /dev/null
+++ b/src/compiler/decl.zc
@@ -0,0 +1,1413 @@
+// ---- 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.
+// read the whole unit from stdin, up to a backtick (outside comments and
+// 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
+word .u readsrc() {
+ int cap = 4090; word b; int n = 0, c; word nb; int w;
+ 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 = rt.new(cap + cap + 6);
+ rt.copy_w(nb, b, cap);
+ rt.free(b);
+ b = nb;
+ cap = cap + cap + 6;
+ }
+ w = n >> 2;
+ if (!(n & 3)) b[w] = 0;
+ b[w] = b[w] | (c << ((n & 3) * 8));
+ n++;
+ }
+ 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;
+}
+// skip a <...> group (type parameters)
+// prescan: before a unit is compiled, one dry pass over it records the
+// signature (parameter tags, .u return, o3) of every function declared in it,
+// so a call that appears before its definition is checked against the tags
+// the callee will have. it only needs the shape of a declaration, not its body.
+int pskipangle() {
+ int dp = 0;
+ while (tk != T_EOF) {
+ if (is_op(s_lt)) dp++;
+ else if (is_op(s_gt)) dp--;
+ else if (is_op(s_shr)) dp = dp - 2;
+ next();
+ if (dp <= 0) return 0;
+ }
+ return 0;
+}
+// at the name after fn: read the signature's tags and record them
+// prescan: at the ( after a function name, read the parameter list and record
+// the signature packed into ssig (bit 0 .u return, bits 1..5 parameter count,
+// two bits per parameter from bit 6)
+int prescan_fn(int ret, int n, int o3) {
+ int np = 0, tags = 0, t = 0, dp, s;
+ next(); // the (
+ if (!is_op(s_rp)) {
+ np = 1; dp = 1;
+ while (tk != T_EOF) {
+ if (is_op(s_lp)) dp++;
+ else if (is_op(s_rp)) { dp--; if (!dp) break; }
+ else if (dp == 1 && is_op(s_comma)) { if (np <= 12) tags = tags + (t << (np + np - 2)); np++; t = 0; }
+ else if (dp == 1 && is_op(s_dot)) { next(); if (is_id(s_m)) t = 1; else if (is_id(s_d)) t = 2; continue; }
+ next();
+ }
+ if (np <= 12) tags = tags + (t << (np + np - 2));
+ }
+ next();
+ 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;
+ sflag[s] = sflag[s] | F_SIG | F_PRE;
+ }
+ if (o3) sflag[s] = sflag[s] | F_O3;
+ return 0;
+}
+// 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; char* oldmodule = module_name; word oldimports = module_imports;
+ dry++;
+ src_push(kind, obj, 0, 0);
+ next();
+ while (tk != T_EOF) {
+ 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) || typedef_ahead()) { 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;
+ else if (tk == T_ID) {
+ peek();
+ if (pk_tk == T_OP && pk_tv == s_lp) {
+ n = tv;
+ next();
+ prescan_fn(pend, n, po3);
+ pend = 0;
+ po3 = 0;
+ continue;
+ }
+ if (pk_tk == T_OP && pk_tv == s_lt) { // list<T> in a type, or first<T>( a generic function
+ n = tv; next(); dep = 0;
+ while (tk != T_EOF) {
+ if (is_op(s_lt)) {
+ dep++;
+ } else if (is_op(s_gt)) {
+ dep--;
+ } else if (is_op(s_shr)) dep = dep - 2;
+ next();
+ if (dep <= 0) break;
+ }
+ if (is_op(s_lp)) { prescan_fn(pend, n, po3); pend = 0; po3 = 0; }
+ continue;
+ }
+ }
+ }
+ next();
+ }
+ src_pop();
+ 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();
+ 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 = 48;
+int ctx_push() {
+ af_forget();
+ word f;
+ reserve_R_CTX((ctxsp + 1) * CTX_WORDS); f = R_CTX + ctxsp * CTX_WORDS;
+ ctxsp++;
+ 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; 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.
+int ctx_pop() {
+ word f;
+ ctxsp--;
+ f = R_CTX + ctxsp * CTX_WORDS;
+ local_drop(f[0]);
+ fdrop(nloc);
+ lend_drop(1 << 30, f[0]);
+ lbase = f[1];
+ fdepth = f[2];
+ fslots = f[3];
+ fchk = f[4];
+ fret = f[5];
+ 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]; 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 ----
+
+// 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
+// the shared exit (return jumps here through the epichain patch list). the
+// frame size is not known until the end, so the prologue's subtract is a hole
+// 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) {
+ 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;
+ lend_drop(1 << 30, nloc);
+ 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(rt.ZFP); op(O_LD);
+ if (!helper) { lit(0); op(O_PICK); }
+ 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(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(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;
+}
+// emit the function epilogue, patching every return to it.
+int epilogue() {
+ int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave);
+ lit(0);
+ fixall(epichain, here);
+ 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(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(rt.ZFP); op(O_ST); setlit(done, here); setlit(node, here);
+ }
+ op(O_RET); borrow_initializer();
+ setlit(fframe, fslots);
+ infn = 0;
+ local_drop(lbase);
+ fdrop(lbase);
+ td_end(0);
+ return 0;
+}
+// the statements of a function between its braces, then the end-of-function
+// 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();
+ if (ifcur && live && ifcur[1]) ifcur[4] = 1; // falling off the end gives 0
+ if (fchk && live) {
+ exit_vars(1, 1);
+ 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:
+// int f(int a, node*.m n, .d x, b) { ... } <T> *list<T>.u mk(int n) { ... }
+// rty is 0 for fn (a plain word), ret is 1 for a .u return
+// (this is also where a function's signature is packed into ssig and its type
+// 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; 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;
+ s = lookup(n);
+ if (s < 0 || s < ubase || skind[s] != K_FN || (sflag[s] & F_DEF)) {
+ 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);
+ }
+ ifkk = 0;
+ expect(s_lp);
+ if (!is_op(s_rp)) {
+ while (1) {
+ t = 0;
+ pty[np] = tword;
+ pk = 0;
+ if (!is_op(s_dot) && paramstart()) {
+ 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, 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) {
+ t = 1;
+ } else if (tv == s_d) t = 2; else die("a parameter tag is .m or .d");
+ next();
+ }
+ 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;
+ if (np < 12) tags = tags + (t << (np + np));
+ np++;
+ if (np >= 24) die("too many parameters");
+ if (!accept(s_comma)) break;
+ }
+ }
+ expect(s_rp);
+ sig = ret + np + np + tags * 64;
+ if ((sflag[s] & F_SIG) && !(sflag[s] & F_PRE)) {
+ if (ssig[s] != sig) serrn(n, "doesn't match its earlier declaration");
+ }
+ else if ((sflag[s] & F_UNTYPED) && (tags || ret)) serrn(n, "was called before its tags were declared; put a prototype above the first call");
+ ssig[s] = sig;
+ sflag[s] = (sflag[s] | F_SIG) - (sflag[s] & F_PRE);
+ if (ntp) { sflag[s] = sflag[s] | F_GEN; if (!ginst) gtmake(s); }
+ if (typed) {
+ if (!rty) rty = tword;
+ ft = tfn(rty, np, pty, sig);
+ 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");
+ if (!ginst && ntp) { // a generic function is not compiled here: its tokens are kept and each set of type arguments gets its own copy
+ if (sflag[s] & F_TPL) serrn(n, "is defined twice");
+ sflag[s] = sflag[s] | F_TPL;
+ gnew = gtmake(s);
+ gskip();
+ ntp = 0;
+ 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(rt.MW - 1); op(O_JMP);
+ return s;
+ }
+ sval[s] = here;
+ sflag[s] = sflag[s] | F_DEF;
+ fixall(sfix[s], here); sfix[s] = 0;
+ fn_begin(chk, ret);
+ finsym = s; finfer = !ret;
+ 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(); borrow_prologue();
+ i = 0;
+ while (i < np) {
+ 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);
+ if (fchk && pt[i] == 1) lstate[t] = S_BORROW;
+ if (fchk && pt[i] == 2) { lstate[t] = S_M; lown[t] = 1; }
+ i++;
+ }
+ i = np;
+ while (i) { i--; laddr(i); op(O_ST); }
+ if (o3f) { // the region starts here: a jump, so the cpu enters it
+ reserve_code(2); lo = here + 2; lit(lo); op(O_JMP);
+ 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) {
+ o3on = 0;
+ o3fs = fslots;
+ o3na = np;
+ o3fn0 = sval[s];
+ o3region(lo, hi, o3sl, o3rg, no3sl);
+ }
+ o3flush();
+ ntp = 0; ifcur = 0;
+ fn_remember(s, n);
+ return s;
+}
+// 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.
+int gdecl(int typed) {
+ ifgk = 0;
+ if (isp == 0) ifgk = tpack();
+ next();
+ return gdecl2(0, 0, name());
+}
+// 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, 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");
+ if (t == tvoid) serr("a variable can't be void");
+ while (1) {
+ if (accept(s_lk)) {
+ k = cexpr(); expect(s_rk);
+ if (!t) t = tword;
+ arraytype = tarr(t, k); k = tsz(arraytype); reserve_code(k);
+ s = addsym(n, K_GARR, here); rs_put(ng, RS_SYM, rs_sym(s), s); stype[s] = arraytype;
+ while (k) { emit(0); k--; }
+ if (gok) { if (!gty) gty = tint; else if (gty != tint) gok = 0; }
+ } else if (t && isagg(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); vr = rsn - 1; vk = 1; vv = 0;
+ if (t) stype[s] = t;
+ emit(0);
+ if (accept(s_eq)) {
+ if (tk == T_STR) {
+ 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 {
+ 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;
+ ng = toff; n = name();
+ }
+ expect(s_semi);
+ if (gok && gty && kp) fact_add(kp, 0 - 1, gty, "");
+ return 0;
+}
+// a constant cut to the sized integer type t, the way tnorm does at run time
+int cnorm(word t, int v) {
+ int b, mask, sg;
+ if (!t) return v;
+ b = tbytes(t);
+ if (!b || t[0] != TY_INT) return v;
+ mask = (1 << (8 * b)) - 1;
+ v = v & mask;
+ if (t[6] & 1) { sg = 1 << (8 * b - 1); v = (v ^ sg) - sg; }
+ return 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, 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) { 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;
+}
+// enhance [n] name(a, b) micro "recipe" [else fn]; n outputs, 1 if left out
+// enhance [n] name(a, b) vm { asm } [else fn];
+// 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; char*[8] pn;
+ next();
+ // enhance 2 divmod(a, b)
+ if (tk == T_NUM) {
+ if (tv < 0 || tv > 8) serr("an enhancement has at most 8 outputs");
+ nout = tv;
+ next();
+ }
+ n = name();
+ expect(s_lp);
+ if (!is_op(s_rp)) {
+ while (1) {
+ if (np == 8) die("an enhancement takes at most 8 inputs");
+ 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 + 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;
+ while (i < np) { *(na + i + hdelta) = here; p = pn[i]; while (*p) { emit(*p); p++; } emit(0); i++; }
+ if (accept(s_micro)) {
+ if (tk != T_STR) die("expected the recipe as a string");
+ ebn = 0; while (tk == T_STR) { ebs(tv); next(); }
+ reserve_code(ebn + 1); pay = here; wcopyto(ebuf, ebn + 1);
+ } else if (accept(s_vm)) { pay = contiguous_asm(); len = here - pay; kind = 1; }
+ else die("expected micro or vm");
+ slot = nenh;
+ if (slot >= 512) fatal("all 512 enhancement slots are taken");
+ nenh++;
+ p = dsc + hdelta;
+ p[0] = slot; p[1] = kind; p[2] = np; p[3] = nout; p[4] = pay; p[5] = len; p[6] = na;
+ s = addsym(n, K_ENH, slot);
+ ssig[s] = np + nout * 16 + dsc * 256;
+ if (accept(s_else)) { r = lookup(tv); if (r < 0) r = fwdfn(tv); next(); sfix[s] = r + 1; }
+ expect(s_semi);
+ if (!hdelta) {
+ 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;
+ }
+ }
+ return 0;
+}
+// recovery at the top level: skip to the next token that can start a
+// declaration, outside any braces
+int topsync() {
+ int depth = 0;
+ while (tk != T_EOF) {
+ if (is_op(s_lb)) depth++;
+ else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; }
+ else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef
+ || tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3
+ || tv == s_memdebug || tv == s_enum || tv == s_include)) break;
+ next();
+ }
+ panic = 0;
+ return 0;
+}
+// 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; 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) {
+ if (n >= 8) fatal("too many type parameters");
+ ps[n] = name();
+ n++;
+ if (!accept(s_comma)) break;
+ }
+ closeangle();
+ rt.fill_w(bs, 0, 8);
+ i = 0;
+ while (i < ntp) {
+ j = 0;
+ while (j < n && ps[j] != tpn[i]) j++;
+ if (j == n) serrn(tpn[i], "isn't a type"); else bs[i] = tvar(j, ps[j]);
+ i++;
+ }
+ if (ntp && dty) dty = subst(dty, bs);
+ rt.copy_w(tpn, ps, n); ntp = n;
+ if (ginst) ntp = 0;
+ return 0;
+}
+// the return type of a function or the type of a global, with unknown names allowed for now
+int rettype() {
+ tvauto = 1;
+ dtype();
+ tvauto = 0;
+ return 0;
+}
+// finish a function declaration. an immediate fn also becomes a keyword
+// (kind K_KW whose handler is the function), which is how parse words work:
+// the parser calls it with kwcall the moment it meets the name.
+int fndone(int s, int imm) {
+ int k;
+ ntp = 0;
+ if (imm) {
+ if (hdelta) serr("parse words can't be defined while building an image");
+ k = addsym(sname[s], K_KW, sval[s]);
+ sflag[k] = F_DEF | F_STMT | F_EXPR | F_TOP;
+ }
+ return 0;
+}
+// one top-level item. this is the dispatcher for everything a unit can
+// contain: unchecked / immediate / o3 prefixes, fn declarations (with or
+// without a leading return type), globals, struct, type, enum, include,
+// typecheck, memdebug, enhance, const, and parse words used at the top level.
+// anything else is a typed global or function: a type, a name, and then
+// ( or < means function, otherwise a global.
+int topitem0() {
+ int r, i;
+ dsn = 0; dnm = 0;
+ if (docs_on && dn && dline == tline && !ginst) {
+ if (dn > docsnapcap) { docsnapcap = dn; dsnap = chk_resize(dsnap, 0, docsnapcap); }
+ i = 0; while (i < dn) { dsnap[i] = dbuf[i]; i++; }
+ dsn = dn; dline = 0;
+ }
+ r = topitem1();
+ if (dsn && dnm) doc_attach(dnm);
+ dsn = 0; dnm = 0;
+ return r;
+}
+// compile one top-level item, after the first check of its keyword.
+int topitem1() {
+ int chk = 1, imm = 0, s, k, n;
+ if (panic) return topsync();
+ if (is_id(s_unchecked)) {
+ next();
+ if (accept(s_semi)) { check_on = 0; return 0; } // unchecked; switches the analyzer off from here on
+ chk = 0;
+ }
+ if (is_id(s_immediate)) { next(); imm = 1; }
+ o3nxt = 0;
+ if (is_id(s_o3)) { next(); o3nxt = 1; }
+ ntp = 0; ifkk = 0;
+ if (is_id(s_fn)) {
+ peek();
+ if (!(pk_tk == T_OP && pk_tv == s_lp)) {
+ ifkp = tpack(); ifkk = 1;
+ if (isp != 0) ifkk = 0;
+ next();
+ n = name();
+ if (ginst) n = ginsn;
+ dty = 0;
+ genparams();
+ return fndone(fndef(chk, 0, 0, n), imm);
+ }
+ }
+ if (!chk || imm || o3nxt) { // has to be a function, with a return type
+ ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2;
+ if (isp != 0) ifkk = 0;
+ rettype(); n = name(); if (ginst) n = ginsn; genparams();
+ if (dtag && dtag != S_U) serr("the only return tag is .u");
+ if (!is_op(s_lp)) { die("expected a function"); return 0; }
+ return fndone(fndef(chk, dty, dtag == S_U, n), imm);
+ }
+ if (is_id(s_var)) return gdecl(0);
+ if (is_id(s_struct)) { kw_struct(); return 0; }
+ if (is_id(s_type)) return kw_type();
+ if (typedef_ahead()) return kw_typedef();
+ if (is_id(s_typecheck)) return kw_typecheck();
+ if (is_id(s_docs)) return kw_docs();
+ 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)) {
+ 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; }
+ }
+ if (tstart() || (tk == T_ID && lookup(tv) < 0)) { // int f(...) { }, T first<T>(...) { }, int g = 1;
+ ifkp = tpack(); ifkk = 0 - 1; if (is_op(s_dot)) ifkk = 2;
+ if (isp != 0) ifkk = 0;
+ rettype(); n = name();
+ if (is_op(s_lp) || is_op(s_lt)) {
+ if (ginst) n = ginsn;
+ genparams();
+ if (dtag && dtag != S_U) serr("the only return tag is .u");
+ return fndone(fndef(chk, dty, dtag == S_U, n), 0);
+ }
+ if (ntp) { serrn(tpn[0], "isn't a type"); ntp = 0; }
+ return gdecl2(dty, dtag, n);
+ }
+ die("expected a declaration");
+ return 0;
+}
+// ---- 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;
+ while (i < gng) {
+ if (gnm[i] == n) return i;
+ i++;
+ }
+ return 0 - 1;
+}
+// does a type still mention a type parameter?
+word hasvar(word t) {
+ int k = t[0], i; word v;
+ if (k == TY_VAR) return 1;
+ if (k == TY_PTR || k == TY_ARR || k == TY_DYN) return hasvar(t[4]);
+ if (k == TY_FN) {
+ if (hasvar(t[4])) return 1;
+ i = 0;
+ while (i < t[3]) {
+ if (hasvar(t[7 + i])) return 1;
+ i++;
+ }
+ return 0;
+ }
+ if (k == TY_STRUCT && t[5]) {
+ v = t[6]; i = 0;
+ while (i < v[0]) {
+ if (hasvar(v[1 + i])) return 1;
+ i++;
+ }
+ }
+ return 0;
+}
+// add the current token to the item being recorded
+int grec_add() {
+ word nb;
+ if (gn * 3 + 4 > gcap) {
+ nb = rt.new(gcap + gcap);
+ rt.copy_w(nb, gbuf, gcap);
+ rt.free(gbuf);
+ gbuf = nb;
+ gcap = gcap + gcap;
+ }
+ gbuf[1 + gn * 3] = tk; gbuf[2 + gn * 3] = tv; gbuf[3 + gn * 3] = tpack();
+ gn++;
+ return 0;
+}
+// the template entry for generic function s, made on first sight of its header
+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_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;
+}
+int reserve_generics(int need) {
+ int cap = gtcap;
+ if (need <= cap) return cap;
+ 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) {
+ int cap = gicap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ gi_t = chk_resize(gi_t, gi_n, cap); gi_sym = chk_resize(gi_sym, gi_n, cap);
+ gi_done = chk_resize(gi_done, gi_n, cap); gi_ar = chk_resize(gi_ar, gi_n * 8, cap * 8);
+ gicap = cap; return cap;
+}
+// make the generic instance of the function or struct s for its type arguments.
+int gtmake(int s) {
+ int k = 0;
+ if (!gtab) ginit();
+ while (k < gt_n) {
+ if (gt_sym[k] == s) break;
+ k++;
+ }
+ if (k == gt_n) {
+ if (gt_n >= gtcap) reserve_generics(gt_n + 1);
+ gt_n++;
+ 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;
+ rt.copy_w(gt_nm + k * 8, tpn, ntp);
+ return k;
+}
+// skip a function body without compiling it: from the { to its matching }
+int gskip() {
+ int depth = 0;
+ if (!is_op(s_lb)) { die("expected { to start the function"); return 0; }
+ while (tk != T_EOF) {
+ if (is_op(s_lb)) depth++;
+ else if (is_op(s_rb)) {
+ depth--;
+ if (depth <= 0) { next(); break; }
+ }
+ next();
+ }
+ return 0;
+}
+// the item just read defined generic function gnew: keep its tokens
+int gattach(word .d buf, int n) {
+ n--; // the last token belongs to the next item
+ 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;
+ // compiler storage, not program objects: internal, so live_objects and leak reports skip it
+ q = chk_resize(0, 0, 1 + count * 3); q[0] = count; rt.copy_w(q + 1, gbuf + 1, count * 3);
+ meta = chk_resize(0, 0, 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 = 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; 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; }
+ k++;
+ }
+ if (t < 0) return s;
+ np = gt_np[t];
+ i = 0;
+ while (i < np) {
+ if (!bs[i]) bs[i] = tword;
+ if (hasvar(bs[i])) return s;
+ i++;
+ }
+ k = 0;
+ while (k < gi_n) {
+ if (gi_t[k] == t) {
+ j = 0;
+ while (j < np && gi_ar[k * 8 + j] == bs[j]) j++;
+ if (j == np) return gi_sym[k];
+ }
+ k++;
+ }
+ if (gi_n >= gicap) reserve_instances(gi_n + 1);
+ i = 0; j = 0;
+ 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;
+ while (k < np) {
+ if (k) { nm[i] = ','; i++; }
+ a = bs[k]; nd = 0;
+ while (a && nd < 20) { dg[nd] = '0' + (a & 15); nd++; a = a >> 4; }
+ while (nd) { nd--; nm[i] = dg[nd]; i++; }
+ k++;
+ }
+ nm[i] = '>'; i++; nm[i] = 0;
+ ns = addsym(intern(nm), K_FN, tpack());
+ sflag[ns] = F_SIG | (sflag[s] & F_O3);
+ ssig[ns] = ssig[s];
+ k = gi_n; gi_n++;
+ gi_t[k] = t; gi_sym[k] = ns; gi_done[k] = 0;
+ rt.copy_w(gi_ar + k * 8, bs, np);
+ return ns;
+}
+// compile one instance: replay the template's tokens with its type
+// parameters bound
+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];
+ 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; 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
+// any they ask for in turn
+int gdrain() {
+ int k, more = 1;
+ if (infn || ginst) return 0;
+ if (panic) return 0;
+ while (more) {
+ more = 0;
+ k = 0;
+ while (k < gi_n) {
+ if (!gi_done[k] && gt_tok[gi_t[k]]) {
+ gi_done[k] = 1;
+ gcompile(k);
+ more = 1;
+ }
+ k++;
+ }
+ }
+ return 0;
+}
+// one top-level item, recording its tokens so that a generic function's can be kept
+int topitem() {
+ int sb = gbuf, sn = gn, sc = gcap, sr = grec, sg = gnew, rb, rn;
+ if (ginst) return topitem0();
+ 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 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() {
+ 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 = 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;
+ while (i <= la) {
+ last = row[0]; row[0] = i; j = 1;
+ while (j <= lb) {
+ x = row[j] + 1;
+ y = row[j - 1] + 1;
+ z = last + (a[i - 1] != b[j - 1]);
+ last = row[j];
+ if (y < x) x = y;
+ if (z < x) x = z;
+ row[j] = x;
+ j++;
+ }
+ i++;
+ }
+ return row[lb];
+}
+// the part of a symbol a bare name is compared with: put_c for rt.put_c, the whole name otherwise
+char* similar_tail(char* name) {
+ int k = 0; char* tail = name;
+ while (name[k] && name[k] != '.') k++;
+ if (name[k] && module_named(name, k)) tail = name + k + 1;
+ return tail;
+}
+// is name[0..k) a module the compiler knows?
+int module_named(char* name, int k) {
+ char* prefix = chk_resize(0, 0, k + 1); int known;
+ rt.copy_w(prefix, name, k); prefix[k] = 0;
+ known = module_known(prefix);
+ rt.free(prefix);
+ return known;
+}
+// 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. a module's export is
+// compared without its prefix, so putc finds rt.put_c
+int similar(int n) {
+ int j = 0, best = 0, bd = 99, dist, k, lim = rt.len_w(n) / 3;
+ if (rt.len_w(n) < 3) return 0; // one edit turns g into gc: too short to guess from
+ if (lim < 1) lim = 1;
+ if (lim > 3) lim = 3;
+ while (j < nsym) {
+ k = skind[j];
+ if (k == K_FN || k == K_GVAR || k == K_GARR || k == K_CONST) {
+ if (sflag[j] & F_DEF) {
+ if (sname[j] != n) {
+ dist = editdist(n, similar_tail(sname[j]));
+ if (dist < bd) { bd = dist; best = sname[j]; }
+ }
+ }
+ }
+ j++;
+ }
+ if (bd > lim) return 0;
+ return best;
+}
+// the field or method of struct sn closest to n (statics is 0), or the static function or constant
+// (statics is 1), when it is close enough to be a likely typo. the limits are those of similar.
+char* similar_member(char* sn, word st, char* n, int statics) {
+ int j, bd = 99, dist, k, ln = rt.len_w(sn), lim = rt.len_w(n) / 3, nf; char* best = 0; char* tail;
+ if (rt.len_w(n) < 3) return 0;
+ if (lim < 1) lim = 1;
+ if (lim > 3) lim = 3;
+ if (!statics && st && st[0] == TY_STRUCT) {
+ nf = st[3]; k = 0;
+ while (k < nf) {
+ tail = st[4][k * 3]; dist = editdist(n, tail);
+ if (dist == 2 && swapped(n, tail)) dist = 1;
+ if (dist > 0 && dist < bd) { bd = dist; best = tail; }
+ k++;
+ }
+ }
+ j = 0;
+ while (j < nsym) {
+ k = skind[j];
+ if (((k == K_FN && ((sflag[j] & F_STATIC) != 0) == statics) || (statics && k == K_CONST)) && rt.len_w(sname[j]) > ln + 1 && sname[j][ln] == '.') {
+ tail = sname[j] + ln + 1;
+ if (has_prefix(sname[j], sn, ln) && !has_dot(tail)) {
+ dist = editdist(n, tail);
+ if (dist == 2 && swapped(n, tail)) dist = 1;
+ if (dist > 0 && dist < bd) { bd = dist; best = tail; }
+ }
+ }
+ j++;
+ }
+ if (bd > lim) return 0;
+ return best;
+}
+// are a and b the same but for two neighbouring chars swapped (itao for itoa)? a swap counts as one typo.
+int swapped(char* a, char* b) {
+ int i = 0, la = rt.len_w(a);
+ if (la != rt.len_w(b)) return 0;
+ while (a[i] && a[i] == b[i]) i++;
+ if (!a[i] || !a[i + 1] || a[i] != b[i + 1] || a[i + 1] != b[i]) return 0;
+ i = i + 2;
+ while (a[i] && a[i] == b[i]) i++;
+ return !a[i];
+}
+// does s start with the first n chars of p?
+int has_prefix(char* s, char* p, int n) {
+ int i = 0;
+ while (i < n && s[i] == p[i]) i++;
+ return i == n;
+}
+// does the string contain a dot?
+int has_dot(char* s) {
+ int i = 0;
+ while (s[i]) { if (s[i] == '.') return 1; i++; }
+ return 0;
+}
+// ends an error message with "; did you mean 'x'?" when a member of struct sn is close to n
+int suggest_member(char* sn, word st, char* n, int statics) {
+ char* best = similar_member(sn, st, n, statics);
+ if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
+ return 0;
+}
+// a copy of s[a..e) as a string
+char* lib_copy(char* s, int a, int e) {
+ char* b = chk_resize(0, 0, e - a + 1); rt.copy_w(b, s + a, e - a); b[e - a] = 0;
+ return b;
+}
+// group.name, as a fresh string: the lists below outlive the name table of a check
+char* lib_join(char* group, char* name) {
+ int g = rt.len_w(group), n = rt.len_w(name); char* b = chk_resize(0, 0, g + n + 2);
+ rt.copy_w(b, group, g); b[g] = '.'; rt.copy_w(b + g + 1, name, n); b[g + n + 1] = 0;
+ return b;
+}
+// the declarations of a library, read from its source text, as a list of [next, tail, path]:
+// a top-level name's path is the name itself, a static method's is Group.name. the scan
+// only looks at shapes: a name before ( or a declarator's = ; , at the top, or inside a
+// struct's braces before (. qualified calls (run.statement_hook(...)) are not declarations.
+word lib_scan(char* s) {
+ int i = 0, depth = 0, paren = 0, init = 0, la = -1, le = 0, dotted = 0, after_dot = 0, words = 0, skip = 0, gpend = 0, gdepth = -1, c, a, k;
+ char* group = 0; char* path; word list = 0, entry, back = 0;
+ while (s[i]) {
+ c = s[i];
+ if (c == '/' && s[i + 1] == '/') { while (s[i] && s[i] != 10) i++; continue; }
+ if (c == '/' && s[i + 1] == '*') { i = i + 2; while (s[i] && !(s[i] == '*' && s[i + 1] == '/')) i++; if (s[i]) i = i + 2; continue; }
+ if (c == 34 || c == 39) { i++; while (s[i] && s[i] != c) { if (s[i] == 92 && s[i + 1]) i++; i++; } if (s[i]) i++; la = -1; continue; }
+ if (c == 32 || c == 9 || c == 10 || c == 13) { i++; continue; }
+ if (isdig(c)) { while (isidc(s[i])) i++; la = -1; continue; }
+ if (isal(c)) {
+ a = i; while (isidc(s[i])) i++;
+ if (gpend) { group = lib_copy(s, a, i); gpend = 0; entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = group; entry[2] = group; list = entry; }
+ if (depth == 0 && paren == 0 && !init) {
+ if (words == 0 && ((i - a == 9 && rt.eq_w(lib_copy(s, a, i), "namespace")) || (i - a == 7 && rt.eq_w(lib_copy(s, a, i), "include")))) skip = 1;
+ if (i - a == 6 && rt.eq_w(lib_copy(s, a, i), "struct")) { gpend = 1; group = 0; }
+ words++;
+ }
+ la = a; le = i; dotted = after_dot; after_dot = 0;
+ continue;
+ }
+ after_dot = c == '.';
+ if (c == '<' && s[i + 1] != '<' && s[i + 1] != '=' && paren == 0 && !init && (depth == 0 || (depth == 1 && group))) {
+ // generic parameters: int find<T>(...) is still find
+ k = 1; i++; while (s[i] && k) { if (s[i] == '<') k++; if (s[i] == '>') k--; i++; }
+ continue;
+ }
+ if (c == '(' && paren == 0 && !init && la >= 0 && !dotted && !skip) {
+ if (depth == 0 || (depth == 1 && group && gdepth == 0)) {
+ path = lib_copy(s, la, le);
+ entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = path; entry[2] = path;
+ if (depth == 1) entry[2] = lib_join(group, path);
+ list = entry;
+ }
+ }
+ if (((c == '=' && s[i + 1] != '=') || c == ';' || c == ',' || c == '[') && depth == 0 && paren == 0 && !init && la >= 0 && !dotted && !skip && words > 1) {
+ entry = chk_resize(0, 0, 3); entry[0] = list; entry[1] = lib_copy(s, la, le); entry[2] = entry[1]; list = entry;
+ }
+ if (c == '=' && s[i + 1] != '=' && depth == 0 && paren == 0) init = 1;
+ if (c == '(' || c == '[') paren++;
+ if ((c == ')' || c == ']') && paren > 0) paren--;
+ if (c == '{') { if (depth == 0 && paren == 0 && group) gdepth = 0; else if (depth == 0) group = 0; depth++; }
+ if (c == '}' && depth > 0) { depth--; if (depth == 0) { group = 0; gdepth = -1; words = 0; skip = 0; init = 0; } }
+ if (c == ';' && depth == 0 && paren == 0) { init = 0; words = 0; skip = 0; group = 0; }
+ if (c == ',' && depth == 0 && paren == 0) init = 0;
+ la = -1; i++;
+ }
+ // first declared first: Text.itoa comes before Bytes.itoa
+ while (list) { entry = list[0]; list[0] = back; back = list; list = entry; }
+ return back;
+}
+// the embedded libraries, scanned once
+word std_names, icon_names; int names_read;
+word lib_names(char* module) {
+ if (!names_read) { names_read = 1; std_names = lib_scan(std_text()); icon_names = lib_scan(icon_text()); }
+ if (rt.eq_w(module, "std")) return std_names;
+ return icon_names;
+}
+// the libraries read from files, named in src/lib/modules.txt (one per line, # starts a
+// comment), as a list of [next, name, declarations, scanned]. the compiler names none of them.
+word file_libs; int file_libs_read;
+word lib_files() {
+ int i = 0, a; word entry, tail = 0; char* save = incres;
+ if (file_libs_read) return file_libs;
+ file_libs_read = 1;
+ char* text = inc_open("src/lib/modules.txt");
+ incres = save;
+ if (!text) return 0;
+ while (text[i]) {
+ while (text[i] == 32 || text[i] == 9 || text[i] == 10 || text[i] == 13) i++;
+ if (text[i] == '#') { while (text[i] && text[i] != 10) i++; continue; }
+ a = i; while (isidc(text[i])) i++;
+ if (i == a) { if (text[i]) i++; continue; }
+ entry = chk_resize(0, 0, 4); entry[1] = lib_copy(text, a, i);
+ if (tail) tail[0] = entry; else file_libs = entry;
+ tail = entry;
+ }
+ rt.free(text);
+ return file_libs;
+}
+// the entry of a file library, or 0
+word lib_file(char* name) {
+ word p = lib_files();
+ while (p) { if (rt.eq_w(p[1], name)) return p; p = p[0]; }
+ return 0;
+}
+// a file library's declarations, read and scanned the first time they are needed
+word lib_file_names(word entry) {
+ char* save = incres;
+ if (entry[3]) return entry[2];
+ entry[3] = 1;
+ char* filename = inc_cat("src/lib", entry[1]); 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);
+ char* text = inc_open(full);
+ if (text) entry[2] = lib_scan(text);
+ rt.free(text);
+ incres = save;
+ return entry[2];
+}
+// where name is in a library's list: Text.itoa is itself, a bare itoa is a top-level
+// declaration first, then a static method of some group. 0 when it is not there.
+char* lib_path(word list, char* name) {
+ word p = list; int k = 0;
+ while (p) { if (rt.eq_w(p[2], name)) return p[2]; p = p[0]; }
+ while (name[k]) { if (name[k] == '.') return 0; k++; }
+ p = list;
+ while (p) { if (rt.eq_w(p[1], name)) return p[2]; p = p[0]; }
+ return 0;
+}
+// the module that exports a bare name, and the path after its prefix: put_c is rt's put_c,
+// itoa is std's Text.itoa. every owner, best first, is a list of [next, module, path]: the
+// compiler's own modules, then the modules this unit loaded, then the libraries, which are
+// read from their source whether this unit loaded them or not.
+word export_list, export_tail; char* export_path;
+int export_add(char* module, char* path) {
+ word p = export_list;
+ if (!path) return 0;
+ while (p) { if (rt.eq_w(p[1], module)) return 0; p = p[0]; }
+ p = chk_resize(0, 0, 3); p[1] = module; p[2] = path;
+ if (export_tail) export_tail[0] = p; else export_list = p;
+ export_tail = p;
+ return 0;
+}
+word export_owners(char* name) {
+ word p = included_modules;
+ export_list = 0; export_tail = 0;
+ export_add("rt", export_in("rt", name));
+ export_add("core", export_in("core", name));
+ export_add("run", export_in("run", name));
+ while (p) { export_add(p[1], export_in(p[1], name)); p = p[0]; }
+ export_add("std", lib_path(lib_names("std"), name));
+ export_add("icon", lib_path(lib_names("icon"), name));
+ p = lib_files();
+ while (p) { export_add(p[1], lib_path(lib_file_names(p), name)); p = p[0]; }
+ return export_list;
+}
+char* export_owner(char* name) {
+ word p = export_owners(name);
+ if (!p) return 0;
+ export_path = p[2]; return p[1];
+}
+// name, when module defines it: a function, a global, a constant or a type of it. 0 otherwise.
+char* export_in(char* module, char* name) {
+ int s = lookup_raw(methname(module, name));
+ if (s < 0) return 0;
+ if (skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF))) return name;
+ return 0;
+}
+// end a compile unit from token from onward, and report what it defined.
+int unit_end(int from) {
+ int i = from, best; char* owner;
+ effect_finish(from);
+ panic = 0;
+ while (i < nsym) {
+ // a bodyless declaration (fn f();) promises a definition elsewhere. in check mode that
+ // elsewhere is another file of the same build, which this document can't see
+ if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i] && !(cmode && (sflag[i] & F_DECL)) && !missing_import_name(sname[i])) {
+ tunpack(sval[i]);
+ owner = export_owner(sname[i]);
+ best = 0; if (!owner) best = similar(sname[i]);
+ if (!dry) {
+ errat();
+ rt.put_s(sname[i]); rt.put_s(" is used but never defined");
+ if (owner) {
+ rt.put_s("; did you mean to ");
+ if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); }
+ rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(export_path); rt.put_s("?");
+ }
+ else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
+ errend();
+ }
+ }
+ i++;
+ }
+ missing_imports = 0;
+ if (nerr) giveup();
+ return 0;
+}
+
+// compile a token list as the body of a new function, or (top) as top-level
+// declarations. returns the entry address of the function.
+// (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.)
+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; 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);
+ next();
+ if (top) unit_top();
+ else {
+ entry = here;
+ fn_begin(1, 0);
+ prologue(); borrow_prologue(); borrow_begin();
+ while (tk != T_EOF) stmt();
+ unused_check(lbase);
+ if (fchk && live) exit_vars(1, 1);
+ epilogue();
+ }
+ src_pop();
+ unit_end(from);
+ if (hop >= 0) setlit(hop, here);
+ if (here > ptop) ptop = here;
+ 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.
+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(char* s) {
+ word q = tokens(s); int r = eval(q);
+ rt.free(d*q);
+ return r;
+}
+
diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc
new file mode 100644
index 0000000..c6d29c6
--- /dev/null
+++ b/src/compiler/expr.zc
@@ -0,0 +1,1912 @@
+// ---- 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
+// their value.
+// memdebug: the address on top of the stack, checked before a heap load
+// (dbg_addr duplicates the address on top and calls the runtime's checkptr on
+// the copy, so memdebug catches a load from a freed object)
+int dbg_addr() {
+ if (mdbg && !dry) {
+ lit(0);
+ op(O_PICK);
+ callname(s_check);
+ op(O_DROP);
+ }
+ return 0;
+}
+// turn an lvalue into a value. when lv is set the stack holds the address of a
+// variable, field or element that has not been loaded; rval loads it, unless
+// 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 (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); }
+ lv = 0;
+ }
+ return 0;
+}
+// run a keyword's handler (kw_len, kw_struct, a parse word...) from the spot
+// where the parser met it. e is 1 in expression position, where statement-only
+// keywords refuse to run. the handler reports the class of what it produced
+// in xcls.
+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; 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()");
+ xbor = -1;
+ kw_expr = old;
+ c = xcls;
+ lv = 0; lvk = LV_NONE;
+ return c;
+}
+// report an error when a statement is used inside an expression.
+int stmtonly() {
+ if (kw_expr) serr("this can't be used inside an expression");
+ return 0;
+}
+// the arguments of a call. ft is the callee's type (or 0), bs the generic bindings
+// (tags packs two bits per parameter: 0 lend, 1 .m, 2 .d. bs collects type
+// parameter bindings as the argument types are unified with the signature.
+// 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 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 || 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; effect_argument_base = 0;
+ expect(s_lp);
+ if (!is_op(s_rp)) {
+ while (1) {
+ 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 && (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
+ if (fat && hasptr(t)) {
+ lit(t);
+ callname(s_fatten);
+ xty = t;
+ }
+ 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++;
+ if (!accept(s_comma)) break;
+ }
+ }
+ expect(s_rp);
+ 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
+// memdebug: store the current line in the runtime's alloc_line just before an
+// allocation, so a leak report can say where the object came from
+int stamp_site() {
+ int s;
+ if (!mdbg || dry || o3on) return 0;
+ s = lookup(s_alloc);
+ if (s < 0) return 0;
+ lit(tline); lit(sval[s]); op(O_ST);
+ 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
+// 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
+// straight off the class list. newsz and newleaf record what was allocated so
+// the variable it lands in can later be freed with the matching fast path.
+int newtyped() {
+ word t; int c, fast = 0, length = 1, place = tpack();
+ t = ptype();
+ 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)"); }
+ lit(t);
+ expect(s_rp);
+ stamp_site();
+ 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;
+ }
+ if (accept(s_comma)) {
+ c = expr(); rval(); plainuse(c);
+ length = -1; if (xconst && xcval >= 0) length = xcval;
+ if (tbytes(t)) { // packed: n elements take (n * bytes + 7) / 8 words
+ if (tbytes(t) != 1) { lit(tbytes(t)); op(15); }
+ lit(7); op(O_ADD); lit(3); op(19);
+ } else if (tsz(t) != 1) { lit(tsz(t)); op(15); }
+ lit(tarr(t, 0));
+ newsz = -1; newleaf = 0;
+ } else {
+ lit(tsz(t)); lit(t);
+ newsz = tsz(t); newleaf = 0;
+ if (isleaf(t)) newleaf = t;
+ if (!o3on && !dry && lookup(s_new_k) >= 0) { lit(szclass(newsz)); fast = 1; }
+ }
+ expect(s_rp);
+ stamp_site();
+ 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;
+}
+// ld8/st8 and the 16- and 32-bit pair. 0 when this call should stay a call.
+// a store's opcode is odd: the arguments arrive address then value, and the
+// opcode wants the address on top.
+// (the byte-access functions become single opcodes inside o3 code)
+int byteop(int s) {
+ if (sname[s] == s_ld8) return 612;
+ if (sname[s] == s_st8) return 613;
+ if (sname[s] == s_ld16) return 614;
+ if (sname[s] == s_st16) return 615;
+ if (sname[s] == s_ld32) return 616;
+ if (sname[s] == s_st32) return 617;
+ return 0;
+}
+// ---- 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
+// its initializer, a parameter the type every call passes it, and a function
+// the type of what it returns. only types that change nothing are offered: no
+// floats and no sized integers, because a typed one converts or cuts to size
+// where the untyped word did not, so the annotated program is the same program.
+// the declarations are found while they compile, the calls too, and the
+// edits are written at the end of the unit, when every call has been seen.
+// a type that can be written in front of a name and changes nothing
+word ifpure(word t) {
+ int k;
+ 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 || 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;
+ return t[4] == tword || ifpure(t[4]);
+ }
+ if (k == TY_ARR || k == TY_DYN) return t[4] == tword || ifpure(t[4]);
+ return 0;
+}
+// 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 = 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;
+ 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); }
+ 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;
+ return 0;
+}
+// the record of function symbol s: calls can come before the definition
+word if_get(int s) {
+ int id; word r;
+ if (!ifs || s < 0 || s >= symcap) return 0;
+ id = ifs[s];
+ if (id) return ifr[id - 1];
+ 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;
+ 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) {
+ int r;
+ if (!ifs || dry || j >= IFP || zero) return 0;
+ r = if_get(s);
+ if (r) if_join(r + 8 + 3 * j, t);
+ return 0;
+}
+// a function at the top level starts: its header is at ifkp and ifkk says how
+// to edit it (1: fn becomes the type, 2: the type goes in front of a .u)
+int if_begin(int s, int k) {
+ word r = if_get(s);
+ ifcur = 0;
+ if (!r) return 0;
+ r[2] = 1; r[5] = ifkp; r[6] = k;
+ ifcur = r;
+ return 0;
+}
+// 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, word type) {
+ word r = ifcur;
+ if (!r || j >= IFP) return 0;
+ 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;
+}
+// a return statement: t is what it returns, zero if that is a bare 0
+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;
+}
+// the function ends: which of its parameters were used as addresses
+int if_fin() {
+ word r = ifcur; int j = 0;
+ while (j < r[7]) {
+ if (lptr[lbase + j]) r[10 + 3 * j] = r[10 + 3 * j] | 4;
+ j++;
+ }
+ return 0;
+}
+// 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++;
+ if (!r[2]) continue;
+ j = 0;
+ while (j < r[7]) {
+ k = r[10 + 3 * j]; t = r[8 + 3 * j];
+ if ((k & 3) && !t && (k & 4)) t = tptr(tword); // no call says more, but the body uses it as an address
+ if ((k & 3) == 1 && t > 0) fact_add(r[9 + 3 * j], 0, t, " ");
+ else if ((k & 3) == 2 && t > 0) fact_add(r[9 + 3 * j], 0, t, "");
+ j++;
+ }
+ t = r[3];
+ if (r[1] && t != 0 - 1) {
+ if (!t && r[4]) t = tint;
+ if (t && r[6] == 1) fact_add(r[5], 0 - 1, t, "");
+ else if (t && r[6] == 2) fact_add(r[5], 0, t, "");
+ }
+ }
+ return 0;
+}
+// ---- 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 (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, 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, HINT_ALL)) return 0;
+ while ((1 << k) < v) 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;
+}
+// a call to function symbol s, with the callee name already consumed and the
+// ( next. in order:
+// 1. new(...) with a type argument is redirected to newtyped
+// 2. in o3 code, untyped new becomes new_oz and the byte accessors turn into
+// opcodes
+// 3. the arguments are parsed against the signature (args), checking each
+// against its parameter tag; generic type parameters are bound here
+// 4. free whose argument was a given-away last pointer becomes recycle; a
+// constant-size new becomes new_k with its class; put_s of a byte string
+// becomes put_bs
+// 5. the call is emitted (fnref pushes the address, or a patch site if the
+// function isn't defined yet)
+// 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]; 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(); 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) {
+ kop = byteop(s);
+ if (kop) {
+ np = args(0, 0, bs, 0);
+ if (kop & 1) {
+ if (np != 2) serrn(sname[s], "wrong number of arguments");
+ op(O_SWAP); op(kop); lit(0);
+ } else {
+ if (np != 1) serrn(sname[s], "wrong number of arguments");
+ op(kop);
+ }
+ lv = 0; lvk = LV_NONE; xty = tword; xzero = 0; xbor = -1;
+ xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0;
+ 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;
+ 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; effect_argument_base = 0;
+ np = args(tags, ft, bs, np);
+ 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) { 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;
+ }
+ if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) serrn(sname[s], "wrong number of arguments");
+ if (sname[s] == s_new) {
+ stamp_site(); newsz = -1; newleaf = 0;
+ if (np == 1 && xconst && xcval >= 0) newsz = xcval;
+ xaf = af_new(newsz, 2, cp);
+ if (lsp > 0) newloop(cp);
+ // a constant size: the class is known now, so use the allocator's direct path
+ if (newsz >= 0 && !o3on && !dry && lookup(s_new_k) >= 0) {
+ lit(0);
+ lit(szclass(newsz));
+ s = lookup(s_new_k);
+ }
+ }
+ if (sname[s] == s_put_s && np == 1 && xstr) {
+ s = lookup(s_put_bs);
+ if (s < 0) s = fwdfn(s_put_bs);
+ }
+ if (!(sflag[s] & F_DEF) || s == finsym) sflag[s] = sflag[s] | F_EARLY; // called before its body: an owned return is not inferred for it
+ 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
+ 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) {
+ xstr = 1;
+ }
+ 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;
+}
+// an enhancement used like a function: the arguments, then its one opcode
+// an enhancement (a custom opcode declared with "enhance") called like a
+// function. it compiles to the one opcode, or to a call of its fallback
+// function when the cpu does not have the opcode.
+int enhcall(int s) {
+ int nin = ssig[s] & 15, nout = (ssig[s] >> 4) & 15, n = 0, c;
+ expect(s_lp);
+ if (!is_op(s_rp)) {
+ while (1) {
+ c = expr();
+ rval();
+ plainuse(c);
+ n++;
+ if (!accept(s_comma)) break;
+ }
+ }
+ 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; }
+ op(XOP0 + sval[s]);
+ if (nout == 0) lit(0);
+ if (nout > 1) serrn(sname[s], "leaves several values; use it from asm");
+ return C_PLAIN;
+}
+// ---- 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 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;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ clo_lo = chk_resize(clo_lo, nclo, cap);
+ clo_hi = chk_resize(clo_hi, nclo, cap);
+ clo_n = chk_resize(clo_n, nclo, cap);
+ clo_env = chk_resize(clo_env, nclo, cap);
+ clo_ret = chk_resize(clo_ret, nclo, cap);
+ clo_name = chk_resize(clo_name, nclo * capture_width, cap * capture_width);
+ clo_loc = chk_resize(clo_loc, nclo * capture_width, cap * capture_width);
+ clo_via = chk_resize(clo_via, nclo * capture_width, cap * capture_width);
+ closure_cap = cap; return cap;
+}
+int reserve_captures(int need) {
+ 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) {
+ 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 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).
+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, 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]++;
+ return clo_n[c] - 1;
+ }
+ if (c == 0) return 0 - 1;
+ r = clocap_at(c - 1, n); base = c * capture_width;
+ if (r < 0) return 0 - 1;
+ reserve_captures(clo_n[c] + 1); base = c * capture_width;
+ 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
+// captured values), writes the three code words and copies each captured
+// variable in. ctx_push / ctx_pop save and restore the enclosing function's
+// compiler state around the body.
+int closure() {
+ 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) && paramstart()) {
+ pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
+ }
+ 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;
+ }
+ }
+ expect(s_rp);
+ ctx_push(); ifcur = 0; ifcall = -1;
+ clo_lo[nclo] = lbase; clo_hi[nclo] = nloc; clo_n[nclo] = 0; clo_ret[nclo] = 0;
+ nclo++;
+ hop = hole(); op(O_JMP);
+ entry = here;
+ fn_begin(1, 0);
+ if (declared) fty = declared;
+ prologue(); borrow_prologue();
+ i = 0;
+ while (i < np) {
+ 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++;
+ }
+ envl = newloc(intern("<env>")); fslots++;
+ clo_env[nclo - 1] = envl;
+ i = np + 1;
+ while (i) { i--; laddr(i); op(O_ST); }
+ 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); 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); 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++;
+ }
+ lv = 0;
+ lvk = LV_NONE;
+ xzero = 0;
+ xbor = -1;
+ xconst = 0;
+ xfloat = 0;
+ xstr = 0;
+ xlenloc = -1;
+ xstamp = 0;
+ 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;
+}
+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,
+// ++ --, as) -> primary (literals, names, parentheses, blocks, closures)
+// each level leaves its value on the stack and sets the x* globals: xty is the
+// type, xfloat / xstr say the value is a float or byte string, xzero and
+// xconst mark a literal 0 or constant (xcval holds it), xbor names the local a
+// pointer value was borrowed from, and lv / lvk / lvi say the stack holds an
+// unloaded address and what it is the address of.
+//
+// primary: one operand. a number, string, parenthesized expression, block
+// expression, closure, or a name. a name is looked up in this order: a local
+// of the current function, a variable captured by an enclosing closure, then
+// the symbol table (keyword, constant, global, enhancement, function). a local
+// the analyzer tracks returns C_VAR + its index so the caller can tell which
+// variable the value is.
+int primary() {
+ effect_value = 0; xprojection = 0; if_value = 0;
+ int n, i, s, c, k, npos, sv; 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; 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); xvk = 1; xvv = tv; xzero = tv == 0; xconst = 1; xcval = tv; xty = tint; next(); 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 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 || pk_tv == s_colon)) return closure(); }
+ n = tv; callpos = tpack();
+ i = findloc(n);
+ if (i < 0 && nclo) {
+ k = clocap(n);
+ if (k >= 0) {
+ rs_note(RS_LOCAL, 0 - 1);
+ next();
+ i = nclo - 1;
+ 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;
+ return C_PLAIN;
+ }
+ }
+ if (i >= 0) {
+ 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");
+ }
+ vaddr(i);
+ xty = ltype[i];
+ if (larr[i]) return C_PLAIN;
+ xfloat = getflt(i);
+ if (isfl(ltype[i])) xfloat = 1;
+ 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_VAR + i; }
+ if (tracked(i) || lrskind[i] == RS_PARAM || (local_origin_mask && local_origin_mask[i])) return C_VAR + i;
+ return C_PLAIN;
+ }
+ s = source_lookup(n);
+ if (mstruct && !ginst) { ms = lookup(methname(mstruct, n)); if (ms >= 0 && skind[ms] == K_CONST) s = ms; }
+ 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");
+ return kwcall(s, 1);
+ }
+ 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();
+ 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; sv = tpack(); mn = name();
+ ms = lookup(methname(sname[s], 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];
+ xzero = sval[ms] == 0;
+ xconst = 1;
+ xcval = sval[ms];
+ return C_PLAIN;
+ }
+ if (ms >= 0 && skind[ms] == K_FN && (sflag[ms] & F_STATIC)) {
+ if (!is_op(s_lp)) die("a static function is called with ( after its name");
+ return call(ms);
+ }
+ if (!panic && !dry) {
+ c = tpack(); tunpack(sv); // the error sits on the member name, where the quick fix replaces it
+ errat(); rt.put_s(mn); rt.put_s(": is not a static function of this struct"); suggest_member(sname[s], 0, mn, 1); errend();
+ tunpack(c);
+ }
+ lit(0);
+ xty = tword;
+ return C_PLAIN;
+ }
+ dien(n, "is a type, not a value");
+ lit(0);
+ xty = tword;
+ return C_PLAIN;
+ }
+ npos = tpack();
+ 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];
+ xzero = sval[s] == 0;
+ xconst = 1;
+ xcval = sval[s];
+ 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 (xty == tbstr) xstr = 1;
+ lv = 1;
+ 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; }
+ if (s >= 0 && skind[s] == K_ENH) return enhcall(s);
+ if (s < 0 && is_op(s_dot)) {
+ peekat(0);
+ if (pk_tk == T_ID && lib_file(n)) {
+ // lib.name before include lib, for a library read from a file: the lexer only joins names of loaded modules
+ sv = tpack(); tunpack(npos);
+ errat(); rt.put_s("module access requires `include "); rt.put_s(n); rt.put_s(";`"); errend();
+ tunpack(sv);
+ word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = n; missing_imports = link;
+ next(); n = methname(n, tv); next();
+ }
+ // Text.itoa with no std: a library's static function, reported as one missing name
+ else if (pk_tk == T_ID && export_owner(methname(n, pk_tv))) { next(); n = methname(n, tv); next(); }
+ }
+ if (s < 0) s = fwdfn_at(n, callpos);
+ if (is_op(s_lt) && (sflag[s] & F_GEN)) { // f<int, char*>(...)
+ next(); ngbind = 0;
+ while (1) {
+ if (ngbind >= 8) die("too many type arguments");
+ gbind[ngbind] = ptype();
+ ngbind++;
+ if (!accept(s_comma)) break;
+ }
+ closeangle();
+ if (!is_op(s_lp)) { // f<int> alone is that instance, as a function value
+ rt.fill_w(bs0, 0, 8); rt.copy_w(bs0, gbind, ngbind); ngbind = 0;
+ ft = stype[s];
+ s = ginstance(s, bs0);
+ fnref(s);
+ xty = tword;
+ if (ft) xty = subst(ft, bs0);
+ return C_PLAIN;
+ }
+ }
+ if (is_op(s_lp)) return call(s);
+ fnref(s);
+ if (stype[s]) xty = stype[s];
+ return C_PLAIN;
+}
+// ++ and -- on the address at the top of the stack. post says the old value
+// 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)) {
+ cerrn(lvi, "owns an object and can't be incremented");
+ }
+ tcneed(isnum(xty) || xty[0] == TY_PTR || xty == tword, "++ and -- need a number or a pointer", xty);
+ if (lvk == LV_LOCAL) {
+ ltag[lvi] = ltag[lvi] | LASG;
+ setflt(lvi, 0); setstr(lvi, 0);
+ if (forstep && lvi == ft_iv && dlt == 1) fs_ok = 1;
+ if (dlt < 0) putfact(lvi, -1, -1); else putfact(lvi, getlo(lvi), -1);
+ bpoison(lvi);
+ }
+ lv = 0; lvk = LV_NONE; xzero = 0; xfloat = 0; xstr = 0;
+ t = xty;
+ lit(0); op(O_PICK);
+ if (ispk(t)) pkld(t); else { dbg_addr(); op(O_LD); }
+ // a sized integer wraps: the new value is cut to size before it is stored
+ if (post) {
+ lit(0); op(O_PICK); lit(dlt); op(O_ADD);
+ if (tbytes(t)) tnorm(t);
+ lit(2); op(O_PICK);
+ if (ispk(t)) pkst(t); else op(O_ST);
+ } else {
+ lit(dlt); op(O_ADD);
+ if (tbytes(t)) tnorm(t);
+ lit(0); op(O_PICK); lit(2); op(O_PICK);
+ if (ispk(t)) pkst(t); else op(O_ST);
+ }
+ op(O_SWAP); op(O_DROP);
+ return C_PLAIN;
+}
+// p.x and p->x: a field of a struct, or of the struct a pointer points at
+// the field access after a dot or arrow: c is the class of the left side. a
+// field of a typed struct is its offset added to the address. an untyped word
+// is searched for the one struct that has that field name (fieldscan). if the
+// 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, fieldpos, sv; word st;
+ 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;
+ }
+ fieldpos = tpack(); 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, borrowed, b, base_mask);
+ }
+ }
+ if (st[0] != TY_STRUCT && (st == tword || isnum(st))) {
+ k = fieldscan(n);
+ if (k == 1) { st = fldst; scanned = 1; }
+ else if (k > 1) {
+ if (!panic && !dry) {
+ errat();
+ 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;
+ }
+ }
+ if (st[0] != TY_STRUCT) {
+ if (!panic && !dry) {
+ errat();
+ rt.put_c('.');
+ rt.put_s(n);
+ rt.put_s(": that's a ");
+ tprint(xty);
+ 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) {
+ sv = tpack(); tunpack(fieldpos); // the error sits on the field name, where the quick fix replaces it
+ errat(); tprint(st); rt.put_s(" has no field "); rt.put_s(n); suggest_member(st[2], st, n, 0); errend();
+ tunpack(sv);
+ }
+ 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 (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;
+}
+// array indexing, address arithmetic. mulsz scales an index by the element
+// size. the addr_ functions build the address of element ix of a growable
+// array in the different situations the bounds-check removal produces: the
+// stack holds handle and index (addr_stk), the items pointer is cached in a
+// hidden o3 variable (addr_cached), or only the handle is available
+// (addr_hdr). range_jz is the inline bounds check, one "outside" enhancement
+// and a branch to the oob report.
+int mulsz(int et) {
+ if (tsz(et) != 1) { lit(tsz(et)); op(15); }
+ return 0;
+}
+// address of an element of a stack array, from the index on the stack.
+int addr_stk(int et) {
+ op(O_SWAP);
+ lit(2);
+ op(O_ADD);
+ op(O_LD);
+ op(O_SWAP);
+ mulsz(et);
+ op(O_ADD);
+ return 0;
+}
+// address of an element of an array whose base is cached in a register.
+int addr_cached(int ci, int ix, int et) {
+ vaddr(ci);
+ op(O_LD);
+ vaddr(ix);
+ op(O_LD);
+ mulsz(et);
+ op(O_ADD);
+ return 0;
+}
+// address of an element of a growable array, reading its header.
+int addr_hdr(int al, int ix, int et) {
+ vaddr(al); op(O_LD); lit(2); op(O_ADD); op(O_LD);
+ vaddr(ix); op(O_LD); mulsz(et); op(O_ADD);
+ return 0;
+}
+// reload the index and the length on each side of the branch, so neither is a stack temporary
+int range_jz(int ix, int src, int fld) {
+ int k, s;
+ vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD);
+ s = lookup(s_outside); op(XOP0 + sval[s]);
+ k = hole(); op(O_JZ);
+ vaddr(ix); op(O_LD); vaddr(src); op(O_LD); if (fld) op(O_LD);
+ callname(s_oob);
+ setlit(k, here);
+ return 0;
+}
+// suffixes after a primary, in a loop: e as T, [index], .field, ->field,
+// (call through a value), ++ and --. the index case is the long one. a byte
+// string takes an index or a slice. a growable array is checked: through dat()
+// normally, but inline when the compiler can prove or cheaply test the bound
+// (o3 code with the cache variables, or an index the loop test already
+// 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() { 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(); 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) {
+ 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;
+ if (o3on && lv && lvk == LV_LOCAL && o3cache[lvi]) ci = o3cache[lvi];
+ next();
+ rval();
+ if (was) {
+ c = expr(); rval(); plainuse(c);
+ if (accept(s_colon)) {
+ c = expr(); rval(); plainuse(c);
+ expect(s_rk);
+ 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); xvk = 0;
+ lv = 0; lvk = LV_NONE; xty = tint; xfloat = 0; xstr = 0; xzero = 0; xbor = -1;
+ c = C_PLAIN;
+ continue;
+ }
+ b = -1;
+ if (c == C_FRESH) cerr("index a new object only after binding it to a name");
+ else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; }
+ 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);
+ 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"));
+ et = tpk(et);
+ } else if (xty[0] == TY_DYN) { // a growable array: bounds-checked, through the runtime
+ et = xty[4];
+ al = -1;
+ if (lvk == LV_LOCAL) al = lvi;
+ if (ci && !mdbg && lookup(s_outside) >= 0) op(O_DROP);
+ c = expr();
+ ix = -1;
+ if (lv && lvk == LV_LOCAL) ix = lvi;
+ rval(); plainuse(c);
+ 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);
+ enh = k >= 0 && skind[k] == K_ENH;
+ if (!mdbg && ix >= 0 && al >= 0 && proven(al, ix)) { // the loop test already compared this index with the length
+ if (ci) { vaddr(ci); op(O_LD); op(O_SWAP); mulsz(et); op(O_ADD); }
+ else addr_stk(et);
+ } else if (ci && !mdbg && enh && ix >= 0) {
+ op(O_DROP);
+ range_jz(ix, ci + 1, 0);
+ addr_cached(ci, ix, et);
+ } else if (ci && !mdbg && enh) {
+ lit(0); op(O_PICK); vaddr(ci + 1); op(O_LD); op(XOP0 + sval[k]);
+ k = hole(); op(O_JZ);
+ vaddr(ci + 1); op(O_LD); callname(s_oob);
+ setlit(k, here);
+ vaddr(ci); op(O_LD); op(O_SWAP);
+ mulsz(et); op(O_ADD);
+ } else if (!mdbg && o3on && enh && ix >= 0 && al >= 0) {
+ op(O_DROP); op(O_DROP);
+ range_jz(ix, al, 1);
+ addr_hdr(al, ix, et);
+ } else if (!mdbg && o3on && enh && ix >= 0) {
+ op(O_DROP);
+ vaddr(ix); op(O_LD);
+ lit(1); op(O_PICK); op(O_LD); op(XOP0 + sval[k]);
+ k = hole(); op(O_JZ);
+ vaddr(ix); op(O_LD); lit(1); op(O_PICK); op(O_LD); callname(s_oob);
+ setlit(k, here);
+ lit(2); op(O_ADD); op(O_LD); vaddr(ix); op(O_LD);
+ mulsz(et); op(O_ADD);
+ } else if (!mdbg && o3on && enh) {
+ lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_LD); op(XOP0 + sval[k]);
+ k = hole(); op(O_JZ);
+ lit(1); op(O_PICK); op(O_LD); callname(s_oob);
+ setlit(k, here);
+ op(O_SWAP); lit(2); op(O_ADD); op(O_LD); op(O_SWAP);
+ mulsz(et); op(O_ADD);
+ } else callname(s_dat);
+ } else {
+ tcneed(isptr(xty) || xty == tword, "only pointers and arrays can be indexed", xty);
+ et = telem(xty);
+ n = 0 - 1; if (xty[0] == TY_ARR && xty[3] > 0) n = xty[3];
+ xconst = 0;
+ c = expr(); rval(); plainuse(c);
+ 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");
+ }
+ tcneed(isnum(xty) || xty == tword, "an index has to be a number", xty);
+ expect(s_rk);
+ if (tbytes(et)) { // packed: byte address = base * 8 + index * bytes
+ if (tbytes(et) != 1) { lit(tbytes(et)); op(15); }
+ op(O_SWAP); lit(8); op(15); op(O_ADD);
+ et = tpk(et);
+ } else {
+ if (tsz(et) != 1) { lit(tsz(et)); op(15); }
+ op(O_ADD);
+ }
+ }
+ 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;
+ xfloat = 0; if (isfl(xty)) xfloat = 1; xstr = 0;
+ continue;
+ }
+ if (tv == s_dot || tv == s_arrow) {
+ xconst = 0;
+ c = field(c);
+ xfloat = 0;
+ if (isfl(xty)) xfloat = 1;
+ 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);
+ 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);
+ lit(n); op(O_PICK); op(O_CALL);
+ cj = hole(); op(O_JMP);
+ setlit(cz, here);
+ lit(n); op(O_PICK); lit(3); op(O_ADD); // env: the captures start 3 words into the block
+ lit(n + 1); op(O_PICK); op(O_LD); // the body's entry, word 0 of the block
+ 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;
+ }
+ if (tv == s_inc) { next(); c = incdec(1, 1); continue; }
+ if (tv == s_dec) { next(); c = incdec(1, -1); continue; }
+ return c;
+ }
+ return c;
+}
+// prefix operators: the u*x / d*x / m*x recasts (the star has to touch the
+// 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, 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]) {
+ t = S_M; if (tv == s_u) t = S_U; if (tv == s_d) t = S_D;
+ next(); expect(s_star);
+ c = unary(); place = -1; if (lvk == LV_LOCAL) place = lvi; rval();
+ lvk = LV_NONE;
+ if (c < C_VAR && c != C_FRESH && place >= 0 && fchk) { cerrn(place, "isn't an owned object, so there is no ownership to recast"); return c; }
+ c = recast(c, t);
+ if (t == S_M && c >= C_VAR && lstate[c - C_VAR] == S_M && lown[c - C_VAR]) xlend = c - C_VAR;
+ return c;
+ }
+ }
+ if (tk == T_OP && tv == s_lp && caststart()) { // (int)x, (node*)x: same bits, the type changes
+ next();
+ t = ptype();
+ expect(s_rp);
+ 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(); 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);
+ 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;
+ xzero = 0;
+ xbor = -1;
+ xfloat = 0;
+ if (isfl(xty)) xfloat = 1;
+ xstr = 0;
+ return C_PLAIN;
+ }
+ if (tv == s_amp) {
+ place = tpack(); next(); c = unary();
+ 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) { 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;
+ if (!lv && !isagg(xty)) die("& needs something addressable");
+ if (lv && ispk(xty)) serr("an element of a sized-integer array has no address; it is a byte inside a word");
+ 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);
+ 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); }
+ if (tv == s_dec) { next(); unary(); return incdec(0, -1); }
+ un = unfind(tv);
+ 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);
+ emit(0);
+ op(O_SWAP);
+ op(OP_FSUB);
+ xty = tfloat;
+ }
+ else {
+ emitseq(useq[un], ulen[un]); xfloat = 0;
+ 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;
+ return C_PLAIN;
+ }
+ }
+ return postfix();
+}
+// binary operators by precedence climbing: parse an operand, then while the
+// next token is a binary operator of at least minp, parse its right side at one
+// 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; 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);
+ if (b < 0) return c;
+ p = bprec[b];
+ if (p < minp) return c;
+ lb = -1;
+ if (c >= C_VAR && tracked(c - C_VAR)) lb = lroot(c - C_VAR); else lb = xbor;
+ 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;
+ if (isfl(lt)) lf = 1;
+ src = -1;
+ if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1
+ af_forget();
+ ftest();
+ a = hole(); op(O_JZ);
+ c = bin(p + 1); rval(); plainuse(c);
+ ftest();
+ emitseq(useq[1], ulen[1]); // !! normalizes
+ j = hole(); op(O_JMP);
+ setlit(a, here); lit(0); setlit(j, here);
+ xfloat = 0; xstr = 0;
+ } else if (blen[b] == -2) { // || : 1 if the left side isn't 0, else the right side as 0 or 1
+ af_forget();
+ ftest();
+ a = hole(); op(O_JZ);
+ lit(1); j = hole(); op(O_JMP);
+ setlit(a, here);
+ c = bin(p + 1); rval(); plainuse(c);
+ ftest();
+ emitseq(useq[1], ulen[1]);
+ setlit(j, here);
+ xfloat = 0; xstr = 0;
+ } else {
+ 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; }
+ rval(); plainuse(c);
+ if (cmode && bname[b] == s_star && xconst && xcval >= 2 && !(xcval & (xcval - 1)) && !lf) {
+ shifthint(opp, xcval);
+ }
+ rf = xfloat; rs = xstr;
+ if (isfl(xty)) rf = 1;
+ if ((ls || rs) && (bname[b] == s_plus || bname[b] == s_eqeq || bname[b] == s_ne || bname[b] == s_lt || bname[b] == s_gt || bname[b] == s_le || bname[b] == s_ge)) {
+ if (!(ls && rs)) serr("a byte string needs a byte string on the other side");
+ if (bname[b] == s_plus) { callname(s_cat_s); xstr = 1; xfloat = 0; }
+ else if (bname[b] == s_eqeq) { callname(s_eq_s); xstr = 0; xfloat = 0; }
+ else if (bname[b] == s_ne) { callname(s_eq_s); lit(1); op(18); xstr = 0; xfloat = 0; }
+ else if (bname[b] == s_lt) { callname(s_lt_s); xstr = 0; xfloat = 0; }
+ else if (bname[b] == s_gt) { op(O_SWAP); callname(s_lt_s); xstr = 0; xfloat = 0; }
+ else if (bname[b] == s_le) { callname(s_le_s); xstr = 0; xfloat = 0; }
+ else { op(O_SWAP); callname(s_le_s); xstr = 0; xfloat = 0; }
+ } 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();
+ 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) {
+ ft_nops++;
+ 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; 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 ----
+
+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);
+ if (ispk(t)) pkst(t); else op(O_ST);
+ return 0;
+}
+// c ? a : b. both sides are analyzed from the same starting state (a snapshot,
+// 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; 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; 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; 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);
+ if (isfl(ta) && !isfl(xty)) { op(OP_I2F); xfloat = 1; xty = tfloat; }
+ 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;
+ 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;
+}
+// the top of the expression grammar: a binary expression, then possibly ?: or
+// an assignment. plain = stores the value and leaves it on the stack; the
+// analyzer's assigncheck decides what the store means for ownership (a fresh
+// object makes the target an owner, a u object moves up a block, and so on).
+// compound assignment (+= etc.) loads the old value, applies the operator
+// sequence from the table and stores the result. an o3 variable is stored with
+// the value first and the address after, so no address waits on the stack
+// across a branch.
+int expr() {
+ int c = bin(1), k, i, a, rc, lt, lf, rf, fk;
+ if (tk != T_OP) return c;
+ 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; 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];
+ if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch
+ xlend = -1;
+ rc = expr(); rval();
+ if (k != LV_LOCAL) af_escape(xaf);
+ fcoerce(lt);
+ if (k == LV_LOCAL) {
+ ltag[i] = ltag[i] | LASG;
+ setflt(i, xfloat);
+ setstr(i, xstr);
+ 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 {
+ if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see
+ storekeep(lt);
+ }
+ lv = 0; lvk = LV_NONE; xty = lt; xzero = 0;
+ return assigncheck(k, i, rc);
+ }
+ a = asgfind(tv);
+ if (a >= 0) {
+ if (!lv) die("this can't be assigned to");
+ if (lvk == LV_LOCAL && fchk && tracked(lvi) && !isalias(lvi)) {
+ cerrn(lvi, "owns an object; compound assignment would lose it");
+ }
+ lt = xty; require_mutable(lt);
+ fk = -1; lf = xfloat;
+ if (lvk == LV_LOCAL) fk = lvi;
+ if (isfl(lt)) lf = 1;
+ next();
+ i = o3on && lvk == LV_LOCAL && (ltag[lvi] & 16) && !larr[lvi];
+ if (i) { i = lvi; op(O_DROP); vaddr(i); op(O_LD); i = i + 1; }
+ else if (ispk(lt)) { lit(0); op(O_PICK); pkld(lt); }
+ else { lit(0); op(O_PICK); op(O_LD); }
+ lv = 0;
+ rc = expr(); rval(); plainuse(rc);
+ rf = xfloat;
+ if ((lf || rf) && isfop(bname[abin[a]])) femit(bname[abin[a]], lf, rf);
+ else { emitseq(bseq[abin[a]], blen[abin[a]]); xfloat = 0; }
+ fcoerce(lt);
+ if (fk >= 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;
+ if (bname[abin[a]] == s_plus && xconst && xcval > 0) putfact(fk, getlo(fk), -1);
+ 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);
+ lv = 0; lvk = LV_NONE; xty = lt; xzero = 0;
+ return C_PLAIN;
+ }
+ return c;
+}
+// does the current token start a statement rather than an expression?
+// a block as an expression: { stmts; tail }. it opens a scope like any block,
+// so its variables are freed when it closes, and the tail is how a u object
+// leaves the block with a value (handup).
+word stmtstart() {
+ int s;
+ if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart();
+ if (tk != T_ID || findloc(tv) >= 0) return 0;
+ if (tstart() || typedef_ahead()) return 1;
+ s = lookup(tv);
+ return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR);
+}
+// { stmts; tail } as an expression: its value is the tail, or 0
+int blockexpr() {
+ int save = nloc, c = C_PLAIN, tail = 0, t = tint;
+ next();
+ fdepth++; bxdepth++;
+ while (!is_op(s_rb)) {
+ if (tk == T_EOF) { die("missing }"); break; }
+ if (stmtstart()) { stmt(); continue; }
+ c = expr(); rval();
+ if (accept(s_semi)) { op(O_DROP); discard(c); c = C_PLAIN; continue; }
+ if (!is_op(s_rb)) die("expected ; or } after an expression");
+ tail = 1; t = xty;
+ }
+ next();
+ 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--;
+ lv = 0; lvk = LV_NONE; xty = t; xzero = 0;
+ return c;
+}
+
+// ---- 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
+// initializers and asm operands.
+word cprim() {
+ int v, s, mc;
+ if (tk == T_NUM) { v = tv; next(); return v; }
+ if (accept(s_minus)) return 0 - cprim();
+ if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); }
+ if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; }
+ if (tk == T_ID) {
+ s = lookup(tv);
+ if (mstruct && !ginst) { mc = lookup(methname(mstruct, tv)); if (mc >= 0 && skind[mc] == K_CONST) s = mc; }
+ if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) {
+ next();
+ return sval[s];
+ }
+ if (s >= 0 && skind[s] == K_TYPE) { // Enum.MEMBER in a constant expression
+ peekat(0);
+ if (pk_tk == T_OP && pk_tv == s_dot) {
+ peekat(1);
+ if (pk_tk == T_ID) {
+ mc = lookup(methname(tv, pk_tv));
+ if (mc >= 0 && skind[mc] == K_CONST) { next(); next(); next(); return sval[mc]; }
+ }
+ }
+ }
+ }
+ die("expected a constant");
+ return 0;
+}
+// compile an expression: a primary, then the operators that follow it.
+int cexpr() {
+ int v = cprim(), o;
+ while (tk == T_OP) {
+ o = tv;
+ if (o == bname[3]) { next(); v = v + cprim(); }
+ else if (o == bname[4]) { next(); v = v - cprim(); }
+ else if (o == bname[0]) { next(); v = v * cprim(); }
+ else if (o == bname[15]) { next(); v = v | cprim(); }
+ else if (o == bname[13]) { next(); v = v & cprim(); }
+ else if (o == bname[5]) { next(); v = v << cprim(); }
+ else if (o == bname[6]) { next(); v = v >> cprim(); }
+ else return v;
+ }
+ return v;
+}
+
diff --git a/src/compiler/image.zc b/src/compiler/image.zc
new file mode 100644
index 0000000..dfe5f77
--- /dev/null
+++ b/src/compiler/image.zc
@@ -0,0 +1,95 @@
+// ---- 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,
+// constants, enhancements) as the image's trailing symbol table
+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 + rt.len_w(sname[i]);
+ i++;
+ }
+ reserve_code(words); i = from;
+ // the docs first: each one's bytes go in the image here, and its symbol
+ // takes their address (the bytes come after the code, so no code moves)
+ while (i < nsym) {
+ if (skind[i] == K_DOC) {
+ b = sval[i];
+ sval[i] = here;
+ emit(b[0]);
+ k = 0;
+ while (k < ((b[0] + 7) >> 3)) { emit(b[1 + k]); k++; }
+ if (!(b[0] & 7)) emit(0);
+ }
+ i++;
+ }
+ table = here; i = from;
+ while (i < nsym) {
+ if ((skind[i] >= K_FN && skind[i] <= K_CONST) || skind[i] == K_ENH || skind[i] == K_DOC) {
+ emit(skind[i]); emit(sval[i]); emit(ssig[i]);
+ p = sname[i];
+ while (*p) { emit(*p); p++; }
+ emit(0);
+ }
+ i++;
+ }
+ emit(0);
+ return table;
+}
+// the reverse: read a blob and define its names, so a running genome knows its
+// own functions by name
+// hide: leave out the language server's names. functions are entered before
+// constants and globals in the blob, so the server's functions come after
+// chk_mark but the runtime's constants and globals come after it too: the
+// 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("run.chk_mark"), srvn = intern("lsp.LSP_SERVER");
+ while (*p) {
+ n = intern(p + 3);
+ if (hide && n == srvn) after = 1;
+ 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; rt.install(p[2] >> 8); }
+ }
+ p = p + 3 + rt.len_w(p + 3) + 1;
+ }
+ return 0;
+}
+// compile the rest of stdin for org into a growing output buffer. the image's
+// addresses remain logical addresses at org while its temporary storage grows.
+// image_words and image_size expose the finished image; initial is its first
+// buffer reservation. the matching host layout must still fit the finished image.
+int build_image_grow(int org, int initial) {
+ int sh = here, sd = hdelta, sl = hlimit, sub = ubase, svl = vislo, sns = nsym, sen = nenh, n;
+ word saved_buffer = image_buffer; int saved_active = image_active, saved_org = image_org, saved_capacity = image_capacity;
+ if (org < 0 || initial < 7 || initial > 0x7fffffff - org) fatal("bad image reservation");
+ 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(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;
+ image_buffer[6] = here;
+ n = here - org; image_words = image_buffer; image_size = n;
+ popsyms(sns);
+ ubase = sub; vislo = svl; here = sh; hdelta = sd; hlimit = sl; nenh = sen;
+ image_buffer = saved_buffer; image_active = saved_active; image_org = saved_org; image_capacity = saved_capacity;
+ return n;
+}
+// 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 = rt.syscall(9, 0, 0, 0);
+ if (dst < 0 || n > words - dst) fatal("image destination too small");
+ rt.copy_w(dst, image_words, n);
+ return n;
+}
+
diff --git a/src/compiler/lex.zc b/src/compiler/lex.zc
new file mode 100644
index 0000000..0b509e4
--- /dev/null
+++ b/src/compiler/lex.zc
@@ -0,0 +1,652 @@
+// ---- 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
+// a backtick ends the source: it is how a program tells the compiler where its
+// own text stops and the program's input begins. but only a backtick the
+// lexer would read as a token counts. srcend is fed every character of a
+// stdin source in order and follows just enough of the lexer's rules to know
+// whether it is inside a // comment, a /* */ comment, a string or a char
+// literal (a backslash escapes the next character in the last two). it
+// returns 1 for a backtick outside all of them. readsrc and nextch both use
+// it, so a backtick in a comment or a string is just a character.
+int srcend(int c) {
+ if (qst == 0) {
+ if (c == 96) return 1;
+ if (c == 34) qst = 3;
+ else if (c == 39) qst = 4;
+ else if (c == '*' && qp == '/') { qst = 2; qp = 0; return 0; }
+ else if (c == '/' && qp == '/') { qst = 1; qp = 0; return 0; }
+ qp = c;
+ return 0;
+ }
+ if (qst == 1) {
+ if (c == 10) { qst = 0; qp = 0; }
+ return 0;
+ }
+ if (qst == 2) {
+ if (c == '/' && qp == '*') { qst = 0; qp = 0; return 0; }
+ qp = c;
+ return 0;
+ }
+ if (qesc) { qesc = 0; return 0; }
+ if (c == 92) { qesc = 1; return 0; }
+ if ((qst == 3 && c == 34) || (qst == 4 && c == 39)) { qst = 0; qp = 0; }
+ return 0;
+}
+// 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 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 || 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;
+ if (ch) chptr++; else ch = -1;
+ }
+ if (ch == 10) line++;
+ return 0;
+}
+// the place of a token travels with its line: line in the low 24 bits, then
+// the length (16 bits), the offset + 1 (21 bits) and its glue (2 bits, so a
+// replayed token list still tells u*x from a product)
+int tpack() { return tline | (toklen << 24) | ((toff + 1) << 40) | ((tglue & 3) << 61); }
+// restore the token position that tpack saved in a word.
+int tunpack(int x) {
+ tline = x & 0xFFFFFF;
+ toklen = (x >> 24) & 0xFFFF;
+ toff = ((x >> 40) & 0x1FFFFF) - 1;
+ tglue = (x >> 61) & 3;
+ return 0;
+}
+// chars consumed so far in the source being read: the index of the char in ch
+// (or of the end, once the source is used up). a stdin source reports -1.
+int cpos() {
+ if (chkind == 3) { if (ch < 0) return chptr; return chptr - 1; }
+ if (chkind == 2) { if (ch < 0) return chptr - chbase; return chptr - chbase - 1; }
+ return 0 - 1;
+}
+// push a new input source. the current token, char state and lookahead are
+// 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
+// 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.
+int src_push(int kind, word obj, int trans, int owned) {
+ word f; int i;
+ isp++;
+ reserve_R_SRC((isp + 1) * 32);
+ f = R_SRC + isp * 32;
+ f[0] = kind; f[1] = obj; f[2] = 0; f[3] = 0; f[4] = trans; f[11] = owned;
+ f[5] = tk; f[6] = tv; f[7] = tglue; f[8] = ch; f[9] = chkind; f[10] = chptr;
+ f[12] = line; f[13] = tpack(); f[14] = pq_n; f[31] = chbase;
+ i = 0;
+ while (i < pq_n) {
+ f[15 + i * 4] = pq_tk[i];
+ f[16 + i * 4] = pq_tv[i];
+ f[17 + i * 4] = pq_gl[i];
+ f[18 + i * 4] = pq_ln[i];
+ i++;
+ }
+ pq_n = 0;
+ if (kind == 1) f[3] = obj[0];
+ if (kind == 0) { chkind = 0; qdone = 0; qst = 0; qp = 0; qesc = 0; line = 1; nextch(); }
+ if (kind == 2) { chkind = 2; chptr = obj; chbase = obj; line = 1; nextch(); }
+ if (kind == 3) { chkind = 3; chbase = obj; chptr = 0; line = 1; nextch(); }
+ return 0;
+}
+// leave the current source and restore the one beneath it
+int src_pop() {
+ word f = R_SRC + isp * 32; int i = 0;
+ tk = f[5]; tv = f[6]; tglue = f[7]; ch = f[8]; chkind = f[9]; chptr = f[10];
+ line = f[12]; tunpack(f[13]); pq_n = f[14]; chbase = f[31];
+ while (i < pq_n) {
+ pq_tk[i] = f[15 + i * 4];
+ pq_tv[i] = f[16 + i * 4];
+ pq_gl[i] = f[17 + i * 4];
+ pq_ln[i] = f[18 + i * 4];
+ i++;
+ }
+ isp--;
+ if (f[11]) rt.free(f[1]);
+ f[1] = 0; // a dead source pointer would pin a quarantined block
+ return 0;
+}
+// the lexer's character classes. an operator is built one character at a time
+// for as long as the text so far is still a prefix of some known operator
+// (opprefix), so a program that adds an operator with add_binop changes how
+// the next source is split into tokens without any other bookkeeping.
+// the operator characters are + - * / % = < > ! & | ^ ~ ? : . @ # $ . those below
+// 64 are a bit mask indexed from 33, the rest are four compares
+int isopch(int c) {
+ if (c < 33 || c > 126) return 0;
+ if (c < 64) return (0x7a00763d >> (c - 33)) & 1;
+ return c == 64 || c == 94 || c == 124 || c == 126;
+}
+// is buf[0..n) followed by c the start of some operator the lexer knows?
+int opprefix(int n, int c) {
+ int i = 0; word k; int j;
+ while (i < nkop) {
+ k = kops[i];
+ j = 0;
+ while (j < n && k[j] == tbuf[j]) j++;
+ if (j == n && k[n] == c) return 1;
+ i++;
+ }
+ return 0;
+}
+// true for a decimal digit.
+int isdig(int c) { return c >= '0' && c <= '9'; }
+// true for a letter or an underscore.
+int isal(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; }
+// a letter, digit or _ : the characters after the first one of a name
+int isidc(int c) {
+ if (c >= 'a') return c <= 'z';
+ if (c >= 'A') return c <= 'Z' || c == '_';
+ return c >= '0' && c <= '9';
+}
+// the value of a hex digit, or a value past 15 when c is not one.
+int hexv(int c) {
+ if (isdig(c)) return c - '0';
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ return -1;
+}
+// one possibly escaped character of a string or char literal
+int escch() {
+ int c = ch;
+ nextch();
+ if (c != 92) return c;
+ c = ch;
+ nextch();
+ if (c == 'n') return 10;
+ if (c == 't') return 9;
+ if (c == 'r') return 13;
+ if (c == '0') return 0;
+ if (c == 'e') return 27;
+ return c;
+}
+// finish lexing an operator whose first n characters are in tbuf
+int oplex(int n) {
+ while (ch >= 0 && isopch(ch) && opprefix(n, ch)) { n = token_char(n, ch); nextch(); }
+ tbuf[n] = 0;
+ tk = T_OP; tv = intern(tbuf);
+ return 0;
+}
+// per-local type flags kept in ltag. these are tiny accessors; the whole bit
+// layout is: 0..3 the u/m/d tag, 4 o3, 5 float, 6 byte string, 8..10 the o3
+// register, 12.. the hint bits (see LASG and friends).
+// bit 5 of ltag: this local holds a float. the low bits are the u/m/d tag and bit 4 is o3.
+word getflt(int i) { return (ltag[i] >> 5) & 1; }
+// mark local i as holding a float, or clear the mark.
+int setflt(int i, int f) {
+ if (f) ltag[i] = ltag[i] | 32;
+ else ltag[i] = ltag[i] - (ltag[i] & 32);
+ return 0;
+}
+// bit 6 of ltag: this local holds a byte string. bit 5 is the float.
+word getstr(int i) { return (ltag[i] >> 6) & 1; }
+// mark local i as holding a string, or clear the mark.
+int setstr(int i, int f) {
+ if (f) ltag[i] = ltag[i] | 64;
+ else ltag[i] = ltag[i] - (ltag[i] & 64);
+ return 0;
+}
+// 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 bstrdata(char* s) {
+ int a, n = 0, i = 0, w, k;
+ while (s[n]) n++;
+ reserve_code((n >> 3) + 2); a = here;
+ emit(n);
+ while (i < n) {
+ w = 0; k = 0;
+ while (k < 8 && i < n) { w = w | (s[i] << (k * 8)); k++; i++; }
+ emit(w);
+ }
+ if (!(n & 7)) emit(0);
+ return a;
+}
+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
+// opcodes (OP_FADD ...). an int operand is converted on the fly, and a
+// comparison leaves a plain 0/1 behind. xfloat says whether the expression
+// just compiled is a float.
+int fbits() { return asm { tbuf @ #1222 }; }
+// collect an exponent (e+3) onto the literal in tbuf; n is the length so far
+int takeexp(int n) {
+ if (ch != 'e' && ch != 'E') return n;
+ n = token_char(n, ch);
+ nextch();
+ if (ch == '+' || ch == '-') { n = token_char(n, ch); nextch(); }
+ if (!isdig(ch)) { die("bad float"); return n; }
+ while (isdig(ch)) { n = token_char(n, ch); nextch(); }
+ return n;
+}
+// the literal in tbuf[0..n) is a float: hand it back as a T_FLT token
+int floatok(int n) {
+ tbuf[n] = 0;
+ tk = T_FLT;
+ tv = fbits();
+ return 0;
+}
+// the binary operators that have a float form
+int isfop(int o) {
+ if (o == s_plus || o == s_minus || o == s_star || o == s_slash) return 1;
+ if (o == s_lt || o == s_gt || o == s_le || o == s_ge || o == s_eqeq || o == s_ne) return 1;
+ return 0;
+}
+// emit float arithmetic or comparison o for operands that are floats (lf, rf).
+// an int left operand sits under the right one, so it is converted with
+// swap / i2f / swap. > and >= are < and <= with the operands swapped, and !=
+// is == followed by not.
+int femit(int o, int lf, int rf) {
+ if (!lf) { op(O_SWAP); op(OP_I2F); op(O_SWAP); }
+ if (!rf) op(OP_I2F);
+ if (o == s_plus) { op(OP_FADD); xfloat = 1; return 0; }
+ if (o == s_minus) { op(OP_FSUB); xfloat = 1; return 0; }
+ if (o == s_star) { op(OP_FMUL); xfloat = 1; return 0; }
+ if (o == s_slash) { op(OP_FDIV); xfloat = 1; return 0; }
+ if (o == s_eqeq) { op(OP_FEQ); xfloat = 0; return 0; }
+ if (o == s_ne) { op(OP_FEQ); lit(1); op(18); xfloat = 0; return 0; }
+ if (o == s_lt) { op(OP_FLT); xfloat = 0; return 0; }
+ if (o == s_gt) { op(O_SWAP); op(OP_FLT); xfloat = 0; return 0; }
+ if (o == s_le) { op(OP_FLE); xfloat = 0; return 0; }
+ if (o == s_ge) { op(O_SWAP); op(OP_FLE); xfloat = 0; return 0; }
+ xfloat = 0;
+ return 0;
+}
+// true when a type is a float, the same as isfl.
+word isfloat(int t) { return isfl(t); }
+// float, f32, and f32's packed twin: everything computed as a double
+word isfl(word t) { return t && t[0] == TY_FLOAT; }
+// ±0 is false, like 0.0. every other float, including NaN, is true. the vm's
+// jz stays an integer test, so a float is compared with +0 first.
+int ftest() {
+ if (!xfloat) return 0;
+ op(OP_FLIT); emit(0); op(OP_FEQ); lit(1); op(18);
+ xfloat = 0; xty = tbool;
+ return 0;
+}
+// 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; }
+ // an f32 variable holds a value rounded to single precision; a packed
+ // element is rounded when pkst stores it
+ if (dst == tf32 && xty != tf32 && !ispk(xty)) { xenh("zf2s"); xenh("zs2f"); xty = tf32; }
+ return 0;
+ }
+ if (!xfloat) {
+ if (tbytes(dst) && !samesz(dst, xty)) tnorm(dst);
+ return 0;
+ }
+ if (isnum(dst)) { op(OP_F2I); xfloat = 0; xty = dst; if (tbytes(dst)) tnorm(dst); return 0; }
+ if (dst == tword) xfloat = 0;
+ return 0;
+}
+// sized integers. a value stored into a variable, field, parameter or array
+// element of type t is cut to size first: unsigned types are masked and signed
+// ones are masked and then sign-extended (xor the sign bit, subtract it).
+// samesz says a value of type b is already in range for type a.
+word samesz(word a, word b) { return tbytes(a) && tbytes(b) == tbytes(a) && (a[6] & 1) == (b[6] & 1); }
+// narrow the integer on the stack to the width of its type.
+int tnorm(word t) {
+ int b = tbytes(t), s;
+ if (!b || t[0] != TY_INT) return 0;
+ lit((1 << (8 * b)) - 1); op(16);
+ if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); }
+ return 0;
+}
+// [byte address] -> [value]: load a packed element of type t (the twin)
+int pkld(word t) {
+ int b = t[5];
+ if (b == 1) op(612); else if (b == 2) op(614); else op(616);
+ pkfix(t);
+ return 0;
+}
+// a zero extended packed element on the stack becomes the value of type t
+int pkfix(word t) {
+ int b = t[5], s;
+ if (t[0] == TY_FLOAT) { xenh("zs2f"); return 0; } // f32: the bits widen to a double
+ if (t[6] & 1) { s = 1 << (8 * b - 1); lit(s); op(18); lit(s); op(O_SUB); }
+ return 0;
+}
+// [value byte-address] -> []: store a packed element of type t
+int pkst(word t) {
+ int b = t[5];
+ if (t[0] == TY_FLOAT) { op(O_SWAP); xenh("zf2s"); op(O_SWAP); op(617); return 0; } // f32: round the double to single bits
+ if (b == 1) op(613); else if (b == 2) op(615); else op(617);
+ return 0;
+}
+// an integer address and a field name: the name has to belong to one struct.
+// field lookup on a bare address, for untyped code such as p.x where p is a
+// plain word. if exactly one struct in the type area has a field called n, that
+// is the one meant; with two or more it is ambiguous (the caller reports it).
+int fieldscan(int n) {
+ int i = 0; word t; int k, hit = 0;
+ fldst = 0; fldi = -1;
+ while (i < ntl) {
+ t = typelist[i];
+ if (t[0] == TY_STRUCT && t[3] >= 0) {
+ k = 0;
+ while (k < t[3]) {
+ if (t[4][k * 3] == n) {
+ if (hit) return 2;
+ hit = 1; fldst = t; fldi = k;
+ }
+ k++;
+ }
+ }
+ i++;
+ }
+ return hit;
+}
+// a // comment, with ch on its second slash. one that starts a line joins the
+// 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, 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 (dlast != cl - 1) { dn = 0; dsect = 0; }
+ if (ch == 32) nextch();
+ start = dn;
+ while (ch >= 0 && ch != 10) { doc_char(ch); nextch(); }
+ 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;
+}
+// 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, 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) {
+ if (bol) {
+ while (ch == 32 || ch == 9) nextch();
+ if (ch == '*') { nextch(); if (ch == '/') { cl = line; nextch(); break; } if (ch == 32) nextch(); }
+ bol = 0;
+ }
+ c = ch; nextch();
+ if (c == '*' && ch == '/') { cl = line; nextch(); break; }
+ doc_char(c);
+ if (c == 10) bol = 1;
+ }
+ 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
+// tk / tv with its place in tline / toff. numbers (decimal, hex, floats),
+// names, char and string literals (b"..." is a byte string), and operators
+// all start here. tglue is 1 when the token touches the one before it with no
+// 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);
+ return 0;
+ }
+ tglue = 1;
+ nl = tline != line;
+ while (1) {
+ while (ch == 32 || ch == 9 || ch == 10 || ch == 13) { if (ch == 10) nl = 1; nextch(); tglue = 0; }
+ if (ch != '/') break;
+ nextch();
+ if (ch == '/') {
+ if (docs_on) doccomment(nl); else while (ch >= 0 && ch != 10) nextch();
+ 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(); }
+ nextch(); tglue = 0; continue;
+ }
+ tline = line; toff = cpos() - 1; tbuf[0] = '/'; oplex(1); return 0;
+ }
+ tline = line; toff = cpos();
+ if (docpending) {
+ dline = 0;
+ if (dn && (tline == dlast || tline == dlast + 1)) dline = tline;
+ docpending = 0;
+ }
+ if (ch < 0) { tk = T_EOF; tv = 0; return 0; }
+ if (isdig(ch)) {
+ v = 0; n = 0;
+ if (ch == '0') {
+ nextch();
+ if (ch == 'x' || ch == 'X') {
+ nextch();
+ while ((dg = hexv(ch)) >= 0) { v = v * 16 + dg; nextch(); }
+ tk = T_NUM; tv = v; return 0;
+ }
+ tbuf[0] = '0'; n = 1;
+ }
+ while (isdig(ch)) { n = token_char(n, ch); v = v * 10 + ch - '0'; nextch(); }
+ if (ch == 'e' || ch == 'E') { n = takeexp(n); return floatok(n); }
+ if (ch == '.') {
+ nextch();
+ if (isdig(ch) || ch == 'e' || ch == 'E') {
+ n = token_char(n, '.');
+ while (isdig(ch)) { n = token_char(n, ch); nextch(); }
+ return floatok(takeexp(n));
+ }
+ if (isal(ch)) { pendot = 1; tk = T_NUM; tv = v; return 0; } // 1.field
+ n = token_char(n, '.');
+ return floatok(n);
+ }
+ tk = T_NUM; tv = v; return 0;
+ }
+ if (isal(ch)) {
+ while (isidc(ch)) { n = token_char(n, ch); nextch(); }
+ tbuf[n] = 0;
+ if (n == 1 && tbuf[0] == 'b' && ch == 34) {
+ n = 0; nextch();
+ while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
+ nextch();
+ 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) {
+ nextch();
+ tv = escch();
+ if (ch != 39) die("bad character literal");
+ nextch();
+ tk = T_NUM;
+ tglue = 3;
+ return 0;
+ }
+ if (ch == 34) {
+ nextch();
+ while (ch >= 0 && ch != 34) { v = escch(); n = token_char(n, v); }
+ nextch();
+ tbuf[n] = 0;
+ tk = T_STR; tv = intern(tbuf); return 0;
+ }
+ if (ch == '.') {
+ nextch();
+ if (isdig(ch)) {
+ n = 0; tbuf[0] = '.'; n = 1;
+ while (isdig(ch)) { n = token_char(n, ch); nextch(); }
+ return floatok(takeexp(n));
+ }
+ tbuf[0] = '.';
+ if (isopch(ch) && opprefix(1, ch)) return oplex(1);
+ tbuf[1] = 0;
+ tk = T_OP; tv = intern(tbuf);
+ return 0;
+ }
+ tbuf[0] = ch;
+ nextch();
+ if (isopch(tbuf[0])) return oplex(1);
+ tbuf[1] = 0;
+ tk = T_OP; tv = intern(tbuf);
+ return 0;
+}
+// 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;
+}
+// one token of lookahead: peek() fills pk_*, next() takes it first
+int next() {
+ int i = 1;
+ ntok++;
+ if (toff >= 0) { tpend = toff + toklen; tpline = tline; }
+ if (pq_n) {
+ tk = pq_tk[0]; tv = pq_tv[0]; tglue = pq_gl[0]; tunpack(pq_ln[0]);
+ while (i < pq_n) {
+ pq_tk[i - 1] = pq_tk[i];
+ pq_tv[i - 1] = pq_tv[i];
+ pq_gl[i - 1] = pq_gl[i];
+ pq_ln[i - 1] = pq_ln[i];
+ i++;
+ }
+ pq_n--;
+ if (grec) grec_add();
+ return 0;
+ }
+ next0();
+ if (grec) grec_add();
+ return 0;
+}
+// the k-th token after the current one (k from 0 to 3), into pk_tk and pk_tv
+int peekat(int k) {
+ int a, b, c, e;
+ while (pq_n <= k) {
+ a = tk; b = tv; c = tglue; e = tpack();
+ next0();
+ pq_tk[pq_n] = tk; pq_tv[pq_n] = tv; pq_gl[pq_n] = tglue; pq_ln[pq_n] = tpack(); pq_n++;
+ tk = a; tv = b; tglue = c; tunpack(e);
+ }
+ pk_tk = pq_tk[k]; pk_tv = pq_tv[k];
+ return 0;
+}
+// look at the next token without consuming it.
+int peek() { return peekat(0); }
+// the lowest level of "next token": from a token list if one is the current
+// source, otherwise from the lexer. in check mode after an abort it returns
+// end of file forever, which unwinds every parsing loop without any special
+// case in them.
+int next0() {
+ word f, q; int i;
+ 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];
+ if (i < f[3]) {
+ q = f[1];
+ tk = q[1 + i * 3];
+ tv = q[2 + i * 3];
+ tunpack(q[3 + i * 3]);
+ f[2] = i + 1;
+ return 0;
+ }
+ if (!f[4]) { tk = T_EOF; tv = 0; return 0; }
+ src_pop(); // a fed list ran out: continue with the token that followed it
+ return 0;
+}
+// token tests. s is an interned spelling, so these are pointer compares.
+int is_op(int s) { return tk == T_OP && tv == s; }
+// true when the current token is the name s.
+int is_id(int s) { return tk == T_ID && tv == s; }
+// consume the current token if it is the operator or name s, and say whether it did.
+int accept(int s) {
+ if ((tk == T_OP || tk == T_ID) && tv == s) { next(); return 1; }
+ return 0;
+}
+// accept(s) consumes the token if it is s. expect reports "expected s" at the
+// end of the previous token (dgprev) when it is missing, and goes into panic.
+int expect(int s) {
+ if (!accept(s)) {
+ if (!panic && !dry) {
+ dgprev = 1;
+ errat();
+ dgprev = 0;
+ rt.put_s("expected '"); rt.put_s(s); rt.put_s("', found "); tokshow();
+ errend();
+ }
+ panic = 1;
+ if (tk == T_EOF) giveup();
+ }
+ return 0;
+}
+// read an identifier and return its interned handle
+int name() {
+ int n = tv;
+ if (tk != T_ID) diefound("expected a name");
+ nameoff = toff;
+ next();
+ return n;
+}
+
diff --git a/src/compiler/ops.zc b/src/compiler/ops.zc
new file mode 100644
index 0000000..a396f72
--- /dev/null
+++ b/src/compiler/ops.zc
@@ -0,0 +1,218 @@
+// ---- 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(char* s) {
+ int start = seqtop, n, neg, v, k, c;
+ while (1) {
+ while (*s == 32) s++;
+ if (!*s) break;
+ n = 0;
+ while (s[n] && s[n] != 32) { n = token_char(n, s[n]); }
+ tbuf[n] = 0;
+ s = s + n;
+ reserve_R_SEQ(seqtop + 2);
+ neg = tbuf[0] == '-' && isdig(tbuf[1]);
+ if (isdig(tbuf[neg])) {
+ v = 0; k = neg;
+ while (tbuf[k]) { v = v * 10 + tbuf[k] - '0'; k++; }
+ if (neg) v = 0 - v;
+ if ((v >> 62) != 0 && (v >> 62) != -1) { R_SEQ[seqtop] = SEQWORD; R_SEQ[seqtop + 1] = v; seqtop = seqtop + 2; }
+ else { R_SEQ[seqtop] = v + v + 1; seqtop++; }
+ } else if (n == 1 && (c = mnemidx(tbuf[0])) >= 0) {
+ R_SEQ[seqtop] = c + c; seqtop++;
+ } else {
+ R_SEQ[seqtop] = SEQREF; R_SEQ[seqtop + 1] = intern(tbuf); seqtop = seqtop + 2;
+ }
+ }
+ return start;
+}
+// the index of an assembler mnemonic in the table, or -1 when there is none.
+int mnemidx(int c) {
+ int i = 0;
+ while (mnem[i]) {
+ if (mnem[i] == c) return i;
+ i++;
+ }
+ return -1;
+}
+// emit n words of a stored sequence, resolving its references.
+int emitseq(int a, int n) {
+ int e = a + n, s;
+ while (a < e) {
+ if (R_SEQ[a] == SEQWORD) { lit(R_SEQ[a + 1]); a = a + 2; }
+ else if (R_SEQ[a] == SEQREF) {
+ s = lookup(R_SEQ[a + 1]);
+ if (s < 0) s = fwdfn(R_SEQ[a + 1]);
+ if (skind[s] == K_FN) {
+ fnref(s);
+ } else if (skind[s] == K_ENH) op(XOP0 + sval[s]); else lit(sval[s]);
+ a = a + 2;
+ } else { emit(R_SEQ[a]); a++; }
+ }
+ return 0;
+}
+// add an operator name to the table of known operators, once.
+int addkop(int p) {
+ int i = 0;
+ while (i < nkop) {
+ if (kops[i] == p) return 0;
+ i++;
+ }
+ reserve_operators(nkop + 1);
+ kops[nkop] = p;
+ nkop++;
+ return 0;
+}
+// find a binary operator by its name.
+int binfind(int p) {
+ int i = nbin;
+ while (i) {
+ i--;
+ if (bname[i] == p) return i;
+ }
+ return -1;
+}
+// find a unary operator by its name.
+int unfind(int p) {
+ int i = nun;
+ while (i) {
+ i--;
+ if (uname[i] == p) return i;
+ }
+ return -1;
+}
+// find an assignment operator by its name.
+int asgfind(int p) {
+ int i = nasg;
+ while (i) {
+ i--;
+ if (aname[i] == p) return i;
+ }
+ return -1;
+}
+// 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(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;
+ // && (-1) and || (-2) short-circuit: they bind below |, and && above ||, as in c
+ if (prec < 0) { bprec[i] = 3 + prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; }
+ bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i];
+ addkop(p);
+ // and its compound assignment, x op= y
+ n = rt.len_w(nm);
+ while (1002 + n > tbufcap) { tbuf = chk_resize(tbuf, 0, tbufcap * 2); tbufcap = tbufcap * 2; }
+ 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) {
+ if (asgfind(a) < 0) { reserve_assignment(nasg + 1); aname[nasg] = a; nasg++; }
+ abin[asgfind(a)] = i;
+ addkop(a);
+ }
+ return i;
+}
+// register a unary operator and the sequence it expands to.
+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];
+ addkop(p);
+ return i;
+}
+// register a keyword with its handler and 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;
+}
+
diff --git a/src/compiler/parts b/src/compiler/parts
new file mode 100644
index 0000000..a10c6b3
--- /dev/null
+++ b/src/compiler/parts
@@ -0,0 +1,12 @@
+base.zc
+lex.zc
+ops.zc
+analyzer.zc
+types.zc
+builtins.zc
+expr.zc
+stmt.zc
+decl.zc
+image.zc
+setup.zc
+check.zc
diff --git a/src/compiler/setup.zc b/src/compiler/setup.zc
new file mode 100644
index 0000000..4986fc3
--- /dev/null
+++ b/src/compiler/setup.zc
@@ -0,0 +1,191 @@
+// ---- 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 > 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
+// spellings the parser compares against; zc_tables fills the operator and
+// keyword tables. an operator is a name, a precedence and a code sequence in
+// 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;
+ 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; ntd = 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("--");
+ s_star = intern("*");
+ s_slash = intern("/");
+ s_amp = intern("&");
+ s_colon = intern(":");
+ s_hash = intern("#");
+ s_minus = intern("-");
+ s_dot = intern(".");
+ s_q = intern("?");
+ s_case = intern("case");
+ s_default = intern("default");
+ s_andand = intern("&&");
+ s_oror = intern("||");
+ 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("run.start");
+ s_main = intern("main");
+ s_free = intern("rt.free");
+ s_recycle = intern("rt.recycle"); s_copy_w = intern("rt.copy_w");
+ s_do = intern("do");
+ s_while = intern("while");
+ s_enum = intern("enum");
+ s_include = intern("include");
+ 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;
+}
+// operators and keywords. these are plain tables in memory: a program can
+// add operators, change precedences or replace keywords with its own.
+// (the order of the first binary operators matters: cexpr refers to * + - << >>
+// & | by their index, and the comments mark which ones)
+int zc_tables() {
+ add_unop("!", "0 $ 0 ~ - | 63 > 1 +"); // index 0
+ add_unop("!!", "0 $ 0 ~ - | 63 > 0 ~ -"); // index 1: normalize to 0 or 1
+ add_unop("~", "-1 ^"); // index 2
+ add_unop("-", "0 ~ -");
+ add_binop("*", 10, "*"); // the first few indexes are used by cexpr
+ add_binop("/", 10, "idiv"); // native divide, wired in by rt.zc
+ add_binop("%", 10, "imod");
+ add_binop("+", 9, "+"); // 3
+ add_binop("-", 9, "-"); // 4
+ add_binop("<<", 8, "ishl"); // 5
+ add_binop(">>", 8, ">"); // 6
+ add_binop("<", 7, "<");
+ add_binop(">", 7, "~ <");
+ add_binop("<=", 7, "~ < 1 ^");
+ add_binop(">=", 7, "< 1 ^");
+ add_binop("==", 6, "- 0 $ 0 ~ - | 63 > 1 +");
+ add_binop("!=", 6, "- 0 $ 0 ~ - | 63 > 0 ~ -");
+ add_binop("&", 5, "&"); // 13
+ add_binop("^", 4, "^");
+ add_binop("|", 3, "|"); // 15
+ add_binop("&&", -1, 0);
+ add_binop("||", -2, 0);
+ addkop(s_eq); addkop(s_arrow); addkop(s_inc); addkop(s_dec);
+ add_kw("let", kw_var, F_STMT);
+ add_kw("if", kw_if, F_STMT);
+ add_kw("while", kw_while, F_STMT);
+ 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 | F_STMT);
+ add_kw("embed", kw_embed, 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);
+ add_kw("return", kw_return, F_STMT);
+ add_kw("unchecked", kw_unchecked, F_STMT);
+ add_kw("asm", kw_asm, F_STMT | F_EXPR);
+ add_kw("quote", kw_quote, F_EXPR);
+ add_kw("sizeof", kw_sizeof, F_EXPR);
+ add_kw("typeof", kw_typeof, F_EXPR);
+ add_kw("extern", kw_extern, F_TOP);
+ add_kw("cfunc", kw_cfunc, F_EXPR);
+ add_kw("o3", kw_o3, F_STMT);
+ add_kw("len", kw_len, F_EXPR);
+ add_kw("cap", kw_cap, F_EXPR);
+ add_kw("push", kw_push, F_EXPR);
+ add_kw("pop", kw_pop, F_EXPR);
+ 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.
+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 && 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 <= 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 + 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) { 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
+// the compiler, then either run the language server (a genome that carries it)
+// or compile the unit on stdin, collect the garbage that compiling made, and
+// call main().
+int start() {
+ int s, f;
+ rt.heap_init(); chk_heap = 1;
+ shield_types();
+ zc_init();
+ blob_load(rt.GORG[5], 0);
+ zc_tables();
+ types_init();
+ 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.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;
+ 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) 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("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(); }
+ rt.halt();
+ return 0;
+}
+
diff --git a/src/compiler/stmt.zc b/src/compiler/stmt.zc
new file mode 100644
index 0000000..a2ad0d1
--- /dev/null
+++ b/src/compiler/stmt.zc
@@ -0,0 +1,816 @@
+// ---- 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
+// expression statement whose value is dropped. after a syntax error panic is
+// set and stmt calls resync to skip to the next ; or }.
+int resync() {
+ while (tk != T_EOF && !is_op(s_semi) && !is_op(s_rb)) next();
+ if (is_op(s_semi)) next();
+ panic = 0;
+ return 0;
+}
+// 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);
+ if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; }
+ }
+ if (untyped_const()) { next(); localdecl(2); return 0; }
+ if (typedef_ahead() && findloc(tv) < 0) return kw_typedef();
+ if (tstart()) { localdecl(1); return 0; }
+ if (tk == T_OP) {
+ if (tv == s_semi) { next(); return 0; }
+ if (tv == s_lb) { block(); return 0; }
+ }
+ c = expr(); rval(); op(O_DROP);
+ expect(s_semi);
+ discard(c);
+ return 0;
+}
+// compile a block, with its own scope for the locals it declares.
+int block() {
+ int save = nloc;
+ 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--;
+ return 0;
+}
+// the body of if/while/for: its own block, even without braces
+// (the body of if/while/for is always its own block, even without braces, so
+// variables declared in it die with it)
+int substmt() {
+ int save = nloc;
+ if (is_op(s_lb)) return block();
+ fdepth++;
+ stmt();
+ block_close(save);
+ fdepth--;
+ return 0;
+}
+// let a = e, b[10]; or, typed: int a = 1, b; node*.m p = new(node); .u r;
+// let infers pointer, struct and function types from the initializer and
+// keeps numbers as plain words.
+// (each declared name gets a slot; an initializer is compiled and stored, and
+// assigncheck gives the new local its analyzer state. an initializer that is a
+// constant number marks the local as a const candidate for the hints, and a
+// new records the size and leaf-ness for the matching recycle.)
+// the type of each untyped local while the language server checks a document:
+// the byte offset of its name in the document, and the type's text in ltxt
+// (one character per word). the server turns it into the hover of the local.
+// record a parsed type expression so arrays, stars, and function types have hovers.
+int ht_note(int start, int end, word t) {
+ if (!lt_on || isp != 0 || dry || !t || start < 0 || end <= start) return 0;
+ if (!ht_start || ht_n == htcap) {
+ if (ht_start) htcap = htcap * 2;
+ ht_start = chk_resize(ht_start, ht_n, htcap);
+ ht_end = chk_resize(ht_end, ht_n, htcap);
+ ht_type = chk_resize(ht_type, ht_n, htcap);
+ }
+ ht_start[ht_n] = start; ht_end[ht_n] = end; ht_type[ht_n] = t; ht_n++;
+ return 0;
+}
+// record the type of a local at byte offset o for the language server.
+int lt_note(int o, int t) {
+ int sm, sb, sn, sc, sl, need;
+ if (!lt_on || isp != 0 || dry || o < 0 || !t) return 0;
+ if (!lt_off || lt_n == ltcap) {
+ if (lt_off) ltcap = ltcap * 2;
+ 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 = 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);
+ }
+ rt.outmode = 2; rt.outbuf = ltxt; rt.outn = ltxn; rt.outcap = ltxtcap;
+ tprint(t);
+ 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.
+// does `const name =` start here? a const with no type takes the initializer's type.
+int untyped_const() {
+ if (tk != T_ID || tv != s_const) return 0;
+ peekat(0); if (pk_tk != T_ID || istypename(pk_tv)) return 0;
+ peekat(1); return pk_tk == T_OP && (pk_tv == s_eq || pk_tv == s_semi || pk_tv == s_comma);
+}
+// typed is 1 after a type, 0 after let, and 2 after an untyped const.
+int localdecl(int typed) {
+ int n, i, c, k, t = 0, tg = 0, pp, kc, kp = ifkp, gty = 0, gok = !typed && ifon && !dry, o, vr, ro = typed == 2; word iq, it;
+ if (ro) typed = 0;
+ 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); vr = rsn - 1;
+ n = name();
+ i = newloc(n);
+ lrsoff[i] = o;
+ ltag[i] = tg | LDECL; lpos[i] = pp; lnsz[i] = 0; lleaf[i] = 0;
+ if (o3on || o3one) ltag[i] = ltag[i] | 16;
+ if (accept(s_lk)) {
+ k = cexpr(); expect(s_rk);
+ if (!t) t = tword;
+ larr[i] = 1; ltype[i] = tarr(t, k); fslots = fslots + tsz(ltype[i]);
+ 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] && 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;
+ 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);
+ vaddr(i);
+ op(O_ST);
+ o3refresh(i);
+ setflt(i, xfloat);
+ setstr(i, xstr);
+ 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);
+ else if (xty[0] == TY_STRUCT) ltype[i] = tptr(xty);
+ else ltype[i] = xty;
+ }
+ assigncheck(LV_LOCAL, i, c);
+ if (ro) { k = ltype[i]; if (!k) { k = tword; if (isnum(xty)) k = xty; } ltype[i] = tconst(k); }
+ if (gok) { k = xty; if (isagg(k)) k = ltype[i]; if (!ifpure(k) || (gty && gty != k)) gok = 0; else gty = k; }
+ }
+ } else if (ro) serr("a const needs an initializer");
+ 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]); 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);
+ if (!accept(s_comma)) break;
+ }
+ expect(s_semi);
+ if (gok && gty && kp) fact_add(kp, 0 - 1, gty, "");
+ return 0;
+}
+// compile a variable declaration.
+int kw_var() {
+ stmtonly();
+ ifkp = 0;
+ if (isp == 0) ifkp = tpack(); // only the document itself: an include has other offsets
+ next();
+ return localdecl(0);
+}
+// if / else. the shape of every branching construct is the same: snapshot the
+// analyzer state, compile one path, restore the snapshot, compile the other,
+// then join the two end states (join2). a path that does not reach the join
+// (it returned or broke) is left out. without an else the "other path" is
+// the state before the branch.
+int kw_if() {
+ int a, b, c, s0, sa, live0, livea, n0 = nloc, nt, cj;
+ stmtonly();
+ next();
+ expect(s_lp); c = borrow_condition(); expect(s_rp);
+ a = hole(); op(O_JZ);
+ live0 = live;
+ s0 = snap_push();
+ substmt();
+ livea = live; sa = snap_push();
+ if (is_id(s_else)) {
+ next();
+ b = hole(); op(O_JMP);
+ setlit(a, here);
+ snap_restore(s0); live = live0;
+ substmt();
+ join2(sa, livea, b, live, n0, "ends the branches of an if in different states");
+ } else {
+ // the path that skips the then branch still has the states from before it
+ njps = 0; if (livea) jadd(R_SNAP + sa); if (live0) jadd(R_SNAP + s0);
+ join(n0, "ends the branches of an if in different states");
+ if (livea) fixups(R_SNAP + sa, n0, 1);
+ nt = 0; if (live0) nt = fixups(R_SNAP + s0, n0, 0);
+ if (nt) {
+ cj = hole();
+ op(O_JMP);
+ setlit(a, here);
+ fixups(R_SNAP + s0, n0, 1);
+ setlit(cj, here);
+ }
+ else setlit(a, here);
+ live = livea || live0;
+ }
+ snap_pop(s0);
+ return 0;
+}
+// loop frame: 0 continue target (-1 for a switch) 2 depth 3 snapshot at the top
+// 4 block-expression depth 5 locals at the start 6 its first exit record
+// loops and switches share a frame stack (R_LOOP, 8 words each). a break adds
+// an exit record (the analyzer state at that point) with a jump to patch;
+// loop_end joins all of them with the normal fall-out and patches the jumps.
+int loop_push(int cont, int s0) {
+ word f;
+ reserve_R_LOOP((lsp + 1) * 8);
+ if (lsp >= loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, loopfacts_cap * 2); loopfacts_cap = loopfacts_cap * 2; }
+ f = R_LOOP + lsp * 8;
+ f[0] = cont;
+ f[1] = 0;
+ f[2] = fdepth;
+ f[3] = s0;
+ f[4] = bxdepth;
+ f[5] = nloc;
+ f[6] = extop;
+ f[7] = 0;
+ lpfact[lsp] = -1;
+ lsp++;
+ return 0;
+}
+// the end of a loop or switch: join its exits (the fall-out path sc through
+// the jump at a, if there is one, and every break) and lay out each path's
+// fixups on its way to the end
+int loop_end(int a, int sc) {
+ word f, p; int e, n0, outs = 0;
+ lsp--;
+ f = R_LOOP + lsp * 8;
+ n0 = f[5];
+ njps = 0;
+ if (sc >= 0) jadd(R_SNAP + sc);
+ p = R_EXIT + f[6]; e = R_EXIT + extop;
+ while (p < e) { jadd(p + 1); p = p + 3 + 3 * (p[2] - p[1]); }
+ join(n0, "leaves the loop in different states on different paths");
+ live = njps > 0;
+ p = R_EXIT + f[6];
+ while (p < e) {
+ if (fixups(p + 1, n0, 0)) {
+ setlit(p[0], here);
+ fixups(p + 1, n0, 1);
+ outs = fixnew(hole(), outs);
+ op(O_JMP);
+ }
+ else outs = fixnew(p[0], outs);
+ p = p + 3 + 3 * (p[2] - p[1]);
+ }
+ if (a >= 0) setlit(a, here);
+ if (sc >= 0) fixups(R_SNAP + sc, n0, 1);
+ fixall(outs, here);
+ extop = f[6];
+ return 0;
+}
+// while. the back edge is checked against the snapshot taken at the top, so a
+// loop body that gives something away and comes around again is an error. a
+// literal while (1) has no fall-out path, only its breaks.
+int kw_while() {
+ int c0 = here, a, s0, sc = -1, c, inf = 0;
+ stmtonly();
+ next();
+ s0 = snap_push();
+ expect(s_lp);
+ // while (1): no way out but break
+ if (tk == T_NUM && tv) {
+ peek();
+ if (pk_tk == T_OP && pk_tv == s_rp) inf = 1;
+ }
+ c = borrow_condition(); expect(s_rp);
+ a = hole(); op(O_JZ);
+ if (!inf && live) sc = snap_push();
+ loop_push(c0, s0);
+ substmt();
+ backedge(s0);
+ jmp(c0);
+ loop_end(a, sc);
+ snap_pop(s0);
+ return 0;
+}
+// for (init; test; step) body. the code is laid out test first, then a jump
+// over the step into the body, and the step jumps back to the test, so the
+// step is compiled before the body but runs after it. while the test and the
+// step are parsed, fortest and forstep record the shape "i < bound ... i++":
+// 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; word borrow_mark = borrow_slots;
+ stmtonly();
+ next();
+ expect(s_lp);
+ fdepth++;
+ 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 = borrow_condition();
+ } else { lit(1); inf = 1; xfloat = 0; }
+ fortest = 0;
+ expect(s_semi);
+ a = hole(); op(O_JZ);
+ if (!inf && live) sc = snap_push();
+ b = hole(); op(O_JMP);
+ st = here;
+ 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);
+ setlit(b, here);
+ loop_push(st, s0);
+ if (ft_iv >= 0 && ft_bd >= 0 && fs_ok && getlo(ft_iv) >= 0 && getln(ft_bd) >= 0) {
+ lpfact[lsp - 1] = pfact(ft_iv, ft_bd, getln(ft_bd));
+ }
+ substmt();
+ backedge(s0);
+ jmp(st);
+ loop_end(a, sc);
+ snap_pop(s0);
+ block_close(save);
+ fdepth--;
+ return 0;
+}
+// do stmt while (cond); the body runs once before the test. continue jumps
+// to the test, which is patched in after the body (f[0] == -2 marks that).
+int kw_do() {
+ int body, s0, sc = -1, a, c; word f; int inf = 0;
+ stmtonly();
+ next();
+ s0 = snap_push();
+ body = here;
+ loop_push(-2, s0);
+ substmt();
+ f = R_LOOP + (lsp - 1) * 8;
+ fixall(f[7], here);
+ backedge(s0);
+ if (!is_id(s_while)) die("expected while after do");
+ next();
+ expect(s_lp);
+ if (tk == T_NUM && tv) { peek(); if (pk_tk == T_OP && pk_tv == s_rp) inf = 1; }
+ c = borrow_condition();
+ expect(s_rp);
+ expect(s_semi);
+ if (!inf && live) sc = snap_push();
+ a = hole(); op(O_JZ);
+ jmp(body);
+ loop_end(a, sc);
+ snap_pop(s0);
+ return 0;
+}
+// break and continue unwind the scopes they leave (exit_vars frees what those
+// blocks own on the way out). break records the analyzer state for the join
+// at the end of the loop; continue checks the state against the loop top.
+int kw_break() {
+ word f;
+ stmtonly();
+ next(); expect(s_semi);
+ if (lsp <= loopbase) { serr("break outside a loop or a switch"); return 0; }
+ f = R_LOOP + (lsp - 1) * 8;
+ if (f[4] != bxdepth) { serr("break can't leave a block expression"); return 0; }
+ exit_vars(f[2] + 1, 0);
+ if (live) exit_rec(hole()); else hole();
+ op(O_JMP);
+ live = 0;
+ return 0;
+}
+// compile continue: jump to the test of the innermost loop.
+int kw_continue() {
+ int j = lsp - 1; word f;
+ stmtonly();
+ next(); expect(s_semi);
+ while (j >= loopbase && R_LOOP[j * 8] == -1) j--; // past any switch
+ if (j < loopbase) { serr("continue outside a loop"); return 0; }
+ f = R_LOOP + j * 8;
+ if (f[4] != bxdepth) { serr("continue can't leave a block expression"); return 0; }
+ backedge(f[3]);
+ exit_vars(f[2] + 1, 0);
+ if (f[0] == -2) { f[7] = fixnew(hole(), f[7]); op(O_JMP); } // do/while: the condition isn't placed yet
+ else jmp(f[0]);
+ live = 0;
+ return 0;
+}
+// switch (e) { case 1, 2: ... case 'x': ... default: ... }
+// cases don't fall through: each one ends where the next begins, and break
+// leaves the switch early. case labels are constants.
+// one case's statements, analyzed from the state at the top of the switch.
+// the end of a case jumps out of the switch, so cases never fall through.
+int casebody(int s0, int live0) {
+ int save = nloc;
+ snap_restore(s0); live = live0;
+ fdepth++;
+ while (!is_op(s_rb) && !is_id(s_case) && !is_id(s_default)) {
+ if (tk == T_EOF) { die("missing }"); break; }
+ stmt();
+ }
+ block_close(save);
+ fdepth--;
+ if (live) { exit_rec(hole()); op(O_JMP); } // the end of a case leaves the switch
+ live = 0;
+ return 0;
+}
+// compile a switch statement.
+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;
+ word[8] pbn; word[8] pbl;
+ stmtonly();
+ next();
+ expect(s_lp); c = expr(); st = xty; rval(); plainuse(c); expect(s_rp);
+ tg = ptagged(st);
+ fdepth++;
+ tmp = newloc(0); fslots++; // a hidden local holds the value
+ laddr(lslot[tmp]); op(O_ST);
+ live0 = live;
+ s0 = snap_push();
+ loop_push(-1, s0);
+ expect(s_lb);
+ while (!is_op(s_rb)) {
+ 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();
+ if (tg && tk == T_ID && pk_tk == T_OP && pk_tv == s_lp) gi = pm_find(tv);
+ if (gi >= 0) {
+ // case member(a, b): a member of a tagged enum, its fields bound to the names
+ next(); next();
+ nb = 0;
+ while (!is_op(s_rp)) {
+ if (nb >= 8) { die("too many names in a case"); break; }
+ pbn[nb] = name(); nb++;
+ if (!accept(s_comma)) break;
+ }
+ expect(s_rp);
+ if (nb != pm_nf[gi]) serr("a case binds every field of its member");
+ v = pm_tag[gi];
+ if (v < 63) cov = cov | (1 << v);
+ laddr(lslot[tmp]); op(O_LD); op(O_LD); lit(v); op(O_SUB);
+ bj = fixnew(hole(), bj); op(O_JZ);
+ expect(s_colon);
+ arm = nloc;
+ for (j = 0; j < nb && j < pm_nf[gi]; j++) {
+ bi = newloc(pbn[j]); fslots++;
+ ltype[bi] = pm_ft[gi * 8 + j];
+ if (isfl(ltype[bi])) setflt(bi, 1);
+ pbl[j] = bi;
+ }
+ 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);
+ }
+ casebody(s0, live0);
+ block_close(arm);
+ setlit(nxt, here);
+ } else {
+ while (1) {
+ v = cexpr();
+ if (tg && v >= 0 && v < 63) cov = cov | (1 << v);
+ laddr(lslot[tmp]); op(O_LD);
+ if (tg) op(O_LD);
+ lit(v); op(O_SUB);
+ bj = fixnew(hole(), bj); op(O_JZ);
+ if (!accept(s_comma)) break;
+ }
+ expect(s_colon);
+ nxt = hole(); op(O_JMP);
+ fixall(bj, here);
+ casebody(s0, live0);
+ setlit(nxt, here);
+ }
+ } else if (is_id(s_default)) {
+ next(); expect(s_colon);
+ if (defent >= 0) serr("a switch has one default");
+ skip = hole(); op(O_JMP); // only reached when nothing matched
+ defent = here;
+ casebody(s0, live0);
+ setlit(skip, here);
+ } else die("expected case or default");
+ }
+ next();
+ snap_restore(s0); live = live0;
+ if (tg && defent < 0) cover_warn(tg - 1, cov, sl);
+ if (defent >= 0) { jmp(defent); loop_end(-1, -1); }
+ else { a = hole(); op(O_JMP); loop_end(a, s0); } // nothing matched: out with the states from the top
+ snap_pop(s0);
+ block_close(save);
+ fdepth--;
+ return 0;
+}
+// return: coerce and check the value, run the analyzer's return rule, free
+// everything the function owns (exit_vars from depth 1), and jump to the
+// epilogue, which is a patch chain resolved when the function ends.
+int kw_return() {
+ int c, returned = tint;
+ stmtonly();
+ next();
+ if (!infn) serr("return outside a function");
+ if (bxdepth) serr("return can't leave a block expression");
+ if (is_op(s_semi)) { lit(0); c = C_PLAIN; if_ret(0, 1); }
+ 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);
+ else if (fty && !compat(fty, xty)) tcerr("return value", fty, xty);
+ }
+ if (nclo) {
+ if (!clo_ret[nclo - 1]) clo_ret[nclo - 1] = returned;
+ else if (clo_ret[nclo - 1] != returned) clo_ret[nclo - 1] = 0 - 1;
+ }
+ expect(s_semi);
+ retcheck(c);
+ borrow_cleanup();
+ exit_vars(1, 0);
+ epichain = fixnew(here, epichain);
+ lit(rt.MW - 1); op(O_JMP);
+ live = 0;
+ return 0;
+}
+// o3 { ... }: an optimized region. o3 T x = e;: one o3 variable
+// (a block is compiled as normal code behind a jump and handed to the core
+// as a region once the function is complete: o3pend queues them. variables
+// declared outside that are o3 get registers too, and anything with a pointer
+// in it is fattened when control leaves the region.)
+int kw_o3() {
+ int lo, i; word p; int save, outer;
+ stmtonly();
+ next();
+ if (!is_op(s_lb)) { o3one = 1; localdecl(1); o3one = 0; return 0; }
+ if (o3on || !infn) return block();
+ reserve_code(2); lo = here + 2; lit(lo); op(O_JMP);
+ o3on = 1; o3reset();
+ save = nloc; outer = nloc;
+ i = lbase;
+ // o3 variables declared outside
+ while (i < outer) {
+ if (ltag[i] & 16) {
+ o3promote(i);
+ o3mkcache(i);
+ o3refresh(i);
+ }
+ i++;
+ }
+ block();
+ o3on = 0;
+ 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;
+ 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;
+ while (i < outer) {
+ ltag[i] = ltag[i] - (ltag[i] & 1792); o3cache[i] = 0;
+ // leaving o3: fatten what they hold
+ if ((ltag[i] & 16) && hasptr(ltype[i])) {
+ vaddr(i);
+ op(O_LD);
+ lit(ltype[i]);
+ callname(s_fatten);
+ op(O_DROP);
+ }
+ i++;
+ }
+ return 0;
+}
+// unchecked turns the analyzer off for one statement
+int kw_unchecked() {
+ int old = fchk;
+ stmtonly();
+ next();
+ fchk = 0;
+ substmt();
+ fchk = old;
+ 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)
+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;
+int reserve_asm_labels(int need) {
+ int cap = asm_labelcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ asm_names = chk_resize(asm_names, asm_labelcap, cap); asm_sites = chk_resize(asm_sites, asm_labelcap, cap);
+ asm_labelcap = cap; return cap;
+}
+int reserve_asm_references(int need) {
+ int cap = asm_refcap;
+ if (need <= cap) return cap;
+ while (cap < need) cap = cap * 2;
+ asm_refs = chk_resize(asm_refs, asm_refcap, cap); asm_fixes = chk_resize(asm_fixes, asm_refcap, cap);
+ asm_refcap = cap; return cap;
+}
+int asmblock() {
+ af_forget();
+ int nl = 0, nr = 0, i, k, c, s; word ln = asm_names, la = asm_sites, rn = asm_refs, ra = asm_fixes;
+ expect(s_lb);
+ while (!is_op(s_rb)) {
+ if (tk == T_EOF) { die("unterminated asm block"); break; }
+ if (tk == T_FLT) { op(OP_FLIT); emit(tv); next(); continue; }
+ if (tk == T_NUM) { lit(tv); next(); continue; }
+ if (tk == T_BSTR) { bstrlit(tv); next(); continue; }
+ if (tk == T_STR) { strlit(tv); next(); continue; }
+ if (is_op(s_hash)) {
+ next();
+ if (tk != T_NUM) die("# needs a number");
+ emit(tv);
+ next();
+ continue;
+ }
+ if (is_op(s_minus)) {
+ next();
+ if (tk == T_NUM && tglue) {
+ lit(0 - tv);
+ next();
+ } else {
+ op(O_SUB);
+ }
+ continue;
+ }
+ if (is_op(s_colon)) {
+ next();
+ reserve_asm_labels(nl + 1); ln = asm_names; la = asm_sites;
+ ln[nl] = name();
+ la[nl] = here;
+ nl++;
+ continue;
+ }
+ if (tv[0] && !tv[1] && (c = mnemidx(tv[0])) >= 0) { op(c); next(); continue; }
+ 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; 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; }
+ s = lookup(tv);
+ if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); next(); continue; }
+ if (s >= 0 && skind[s] != K_FN && skind[s] != K_KW) { lit(sval[s]); next(); continue; }
+ if (s >= 0 && skind[s] == K_FN) { fnref(s); next(); continue; }
+ reserve_asm_references(nr + 1); rn = asm_refs; ra = asm_fixes;
+ rn[nr] = tv; ra[nr] = hole(); nr++; next(); // a label further down this block
+ }
+ next();
+ i = 0;
+ while (i < nr) {
+ k = -1; c = 0;
+ while (c < nl) { if (ln[c] == rn[i]) k = c; c++; }
+ if (k < 0) serrn(rn[i], "unknown name in asm");
+ setlit(ra[i], la[k]);
+ i++;
+ }
+ return 0;
+}
+// virtual enhancement bodies are recipes read as a flat range by the host.
+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 + 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();
+ 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);
+ 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 ----
+
+// 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,
+// stopping at the matching brace. it buffers the tokens in R_QBUF, then copies
+// them to a fresh object. quote compiles a list into the program as data and
+// returns a copy at run time; feed (below) pushes a list back as input.
+word .u grab() {
+ int n = 0, depth = 0; word q;
+ if (!is_op(s_lb)) die("expected {");
+ next();
+ while (1) {
+ if (tk == T_EOF) { die("unterminated quote"); break; }
+ if (is_op(s_lb)) depth++;
+ if (is_op(s_rb)) { if (!depth) break; depth--; }
+ reserve_R_QBUF((n + 1) * 3);
+ R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++;
+ next();
+ }
+ next();
+ q = rt.new(n * 3 + 1);
+ q[0] = n;
+ rt.copy_w(q + 1, R_QBUF, n * 3);
+ return u*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++; }
+ return 0;
+}
+// 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 quote_tokens() {
+ word q; int j, a, n;
+ q = grab();
+ j = hole(); op(O_JMP);
+ n = q[0] * 3 + 1; reserve_code(n); a = here;
+ wcopyto(q, n);
+ 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(char* s) {
+ int n = 0; word q;
+ src_push(2, s, 0, 0);
+ next();
+ while (tk != T_EOF) {
+ reserve_R_QBUF((n + 1) * 3);
+ R_QBUF[n * 3] = tk; R_QBUF[n * 3 + 1] = tv; R_QBUF[n * 3 + 2] = tline; n++;
+ next();
+ }
+ src_pop();
+ q = rt.new(n * 3 + 1);
+ q[0] = n;
+ 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.
+int feed(.d q) {
+ src_push(1, q, 1, 1);
+ next();
+ return 0;
+}
+
diff --git a/src/compiler/types.zc b/src/compiler/types.zc
new file mode 100644
index 0000000..c643f0b
--- /dev/null
+++ b/src/compiler/types.zc
@@ -0,0 +1,1003 @@
+// ---- 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
+// stay in memory after compiling, so programs can inspect them.
+// t[0] kind t[1] size in words t[2] name t[3] n t[4] a t[5] b t[6] c t[7..] extra
+// ptr: a = element arr: a = element, n = length
+// fn: a = return type, n = parameter count, b = signature (u m d tags), extra = parameter types
+// struct: n = field count (-1 until laid out), a = fields (name, type, offset),
+// b = generic template, c = type arguments [n, t1, t2 ...]
+// var: a type parameter, n = its index
+// gen: a generic struct: n = parameter count, a = fields (name, type), b = field count, c = parameter names
+
+
+// the type area is a bump allocator of its own (tytop .. R_TYEND). talloc
+// hands out zeroed words, tnew makes a record and adds it to the list of all
+// types (R_TY), and the constructors below look that list up first, which is
+// 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.
+int talloc(int n) {
+ int p = tytop;
+ if (tytop + n > tyend) {
+ p = chk_resize(0, 0, n + 16385); p[0] = typeblocks; typeblocks = p;
+ p++; tytop = p; tyend = p + n + 16384;
+ }
+ tytop = tytop + 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, 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 && !treadonly(t)) return t; i++; }
+ t = tnew(TY_PTR, 1, 0, 0); t[4] = e;
+ return t;
+}
+// sized integers (i8 u8 i16 u16 i32 u32) are TY_INT records with t[5] = bytes,
+// t[6] bit 0 = signed, and t[4] = the "packed view" twin: the same name with
+// t[6] bit 1 set. a variable, parameter or field of a sized type is an
+// ordinary word that is cut to size whenever it is stored. an element of an
+// array or a pointer of a sized type is stored packed, 8, 4 or 2 to a word,
+// and indexing yields the twin, whose lvalue is a byte address.
+word tbytes(word t) { if (t[0] == TY_INT || t[0] == TY_FLOAT) return t[5]; return 0; }
+// 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 (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(char* name) {
+ int s = lookup(intern(name));
+ if (s >= 0 && skind[s] == K_ENH) { op(XOP0 + sval[s]); return 0; }
+ callname(intern(name));
+ return 0;
+}
+// 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 && !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(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;
+ addsym(t[2], K_TYPE, t);
+ return t;
+}
+// the function type with this return, parameter list and signature.
+word tfn(int ret, int np, word ps, int sig) {
+ int i = 0, j; word t;
+ while (i < ntl) {
+ t = typelist[i];
+ if (t[0] == TY_FN && t[4] == ret && t[3] == np && t[5] == sig) {
+ j = 0; while (j < np && t[7 + j] == ps[j]) j++;
+ if (j == np) return t;
+ }
+ i++;
+ }
+ t = tnew(TY_FN, 1, 0, np); t[4] = ret; t[3] = np; t[5] = sig;
+ rt.copy_w(t + 7, ps, np);
+ return 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 && !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, 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;
+ return t;
+}
+// the instance of generic struct g with type arguments args[0..n). found if it
+// already exists, otherwise created unlaid-out (n = -1), so that mentioning
+// vec<T> inside vec<T>'s own methods does not recurse; tresolve lays it out.
+word tinst(word g, word args, int n) {
+ int i = 0, j; word t, v;
+ while (i < ntl) {
+ t = typelist[i];
+ if (t[0] == TY_STRUCT && t[5] == g) {
+ v = t[6]; j = 0; while (j < n && v[1 + j] == args[j]) j++;
+ if (j == n) return t;
+ }
+ i++;
+ }
+ 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; 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];
+ 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) {
+ ft = subst(f[i + i + 1], bs);
+ fs[i * 3] = f[i + i]; fs[i * 3 + 1] = ft; fs[i * 3 + 2] = off;
+ off = off + tsz(ft);
+ i++;
+ }
+ t[4] = fs; t[1] = off; t[3] = n;
+ return t;
+}
+// size of a type in words, laying out a generic instance first if it needs it
+word tsz(word t) {
+ if (t[0] == TY_STRUCT && t[3] < 0) tresolve(t);
+ return t[1];
+}
+// replace type parameters with what they were bound to (unbound ones become word)
+word subst(word t, word bs) {
+ 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;
+ while (i < n) { v[i] = subst(t[7 + i], bs); i++; }
+ return tfn(subst(t[4], bs), n, v, t[5]);
+ }
+ if (k == TY_STRUCT && t[5]) {
+ vec = t[6];
+ n = vec[0];
+ i = 0;
+ while (i < n) { v[i] = subst(vec[1 + i], bs); i++; }
+ return tinst(t[5], v, n);
+ }
+ return t;
+}
+// small type predicates. an array decays to a pointer to its element where a
+// value is wanted, and telem is what indexing a pointer or array yields.
+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 || 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;
+}
+// can a value of type src go where dst is expected?
+// type compatibility, used by the optional typechecker. word is compatible with
+// everything (untyped code stays legal), numbers convert freely, pointers need
+// 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 (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;
+ if (isen(dst)) return 0;
+ }
+ if (isnum(dst) && isnum(src)) return 1;
+ if (isfl(dst) && isfl(src)) return 1;
+ src = decay(src);
+ if (dst[0] == TY_PTR && src[0] == TY_PTR) {
+ if (dst[4] == tword || src[4] == tword || dst[4] == tvoid || src[4] == tvoid) return 1;
+ return dst[4] == src[4];
+ }
+ if ((dst[0] == TY_PTR || dst[0] == TY_FN || dst[0] == TY_DYN) && xzero) return 1; // a literal 0 is a null pointer
+ if (dst[0] == TY_DYN && src[0] == TY_DYN && dst[4] == src[4]) return dst[3] || !src[3]; // a growable array can't hold a fixed one
+ return dst == src;
+}
+// match pattern type p (which may contain type parameters) against actual type
+// a, filling the bindings bs. this is how a call to a generic function infers
+// 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++; }
+ return 1;
+ }
+ return compat(p, a);
+}
+// 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) { // postfix pointers; only function pointers keep the display-only prefix form
+ v = t[4][0];
+ if (v == TY_FN) { rt.put_c('*'); return tprint(t[4]); }
+ tprint(t[4]); rt.put_c('*');
+ return 0;
+ }
+ 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) 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) rt.put_s(", ");
+ tprint(t[7 + i]);
+ if (i < 12 && tparamtag(t, i) == 1) rt.put_s(".m");
+ if (i < 12 && tparamtag(t, i) == 2) rt.put_s(".d");
+ i++;
+ }
+ rt.put_c(')');
+ return 0;
+ }
+ if (t[2]) rt.put_s(t[2]); else rt.put_s("?");
+ if (k == TY_STRUCT && t[5]) {
+ 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;
+}
+// typechecker errors. they only fire when "typecheck on" is set, in reachable
+// code, and never during a dry run.
+int tcerr(int what, int want, int got) {
+ if (!tc_on || !live || dry || panic) return 0;
+ 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(); rt.put_s("type error: "); rt.put_s(what); rt.put_s(", got "); tprint(got);
+ errend();
+ return 0;
+}
+// the type parameters in scope (tpn): a generic function or struct body sees
+// T as a type. istypename says whether a name starts a type here, which is how
+// the parser tells a declaration from an expression.
+int tpfind(int n) {
+ int i = ntp;
+ while (i) {
+ i--;
+ if (tpn[i] == n) return i;
+ }
+ return -1;
+}
+// true when the name n is a type, a type parameter or a generic type.
+word istypename(char* n) {
+ int s;
+ 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;
+ s = lookup(n);
+ return s >= 0 && skind[s] == K_TYPE;
+}
+// the closing > of a type argument list; splits >> in two
+int closeangle() {
+ if (is_op(s_shr)) { tv = s_gt; return 0; }
+ 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 ----
+
+// grammar, outermost first:
+// ptype a type, then any number of (params): int(int, int) is a function type
+// 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, tags, tag; word[24] ps;
+ while (is_op(s_lp)) {
+ next(); np = 0; tags = 0;
+ if (!is_op(s_rp)) {
+ while (1) {
+ if (np >= 24) fatal("too many parameters");
+ 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 + 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(), 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; 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) { 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;
+ }
+ if (is_id(s_fn)) {
+ next();
+ if (!is_op(s_lp)) die("fn as a type needs its parameters: fn(int)");
+ return tword;
+ }
+ if (tk != T_ID) { diefound("expected a type"); return tword; }
+ n = tv;
+ if (ginst) {
+ i = gfind(n);
+ if (i >= 0) { next(); return gib[i]; }
+ }
+ i = tpfind(n);
+ if (i >= 0) { next(); return tvar(i, n); }
+ s = source_lookup(n);
+ // T in T first<T>(...)
+ if (tvauto && (s < 0 || skind[s] != K_TYPE) && ntp < 8) {
+ tpn[ntp] = n;
+ lsmark(2, n);
+ ntp++;
+ next();
+ 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;
+ expect(s_lt);
+ while (1) {
+ if (na >= 8) die("too many type arguments");
+ args[na] = ptype();
+ if (args[na] == tvoid) serr("a type argument can't be void");
+ na++;
+ if (!accept(s_comma)) break;
+ }
+ closeangle();
+ if (na != t[3]) serrn(n, "wrong number of type arguments");
+ return tinst(t, args, na);
+}
+// does a declaration start here? (a type, or a bare .tag)
+// does a declaration start at the current token? (a type name, [, or a bare .tag)
+int tstart() {
+ if (tk == T_OP) {
+ if (tv == s_lk || tv == s_dot) return 1;
+ return 0;
+ }
+ if (tk != T_ID) return 0;
+ if (tv == s_fn) return 1;
+ if (!istypename(tv)) return 0;
+ // Type.name(...) at the start of a statement is a static call, not a declaration:
+ // a declaration's dot is followed by a tag, u, m or d
+ peekat(0);
+ if (pk_tk == T_OP && pk_tv == s_dot) {
+ peekat(1);
+ if (pk_tk == T_ID && pk_tv != s_u && pk_tv != s_m && pk_tv != s_d) return 0;
+ }
+ return 1;
+}
+// [type] [.tag]
+// parse a declaration's [type] [.tag] into dty and dtag
+int dtype() {
+ dty = 0; dtag = 0;
+ if (!is_op(s_dot)) dty = ptype();
+ if (accept(s_dot)) {
+ if (tv == s_m) {
+ dtag = S_M;
+ } else if (tv == s_u) {
+ dtag = S_U;
+ } else if (tv == s_d) dtag = S_D; else die("a tag is .u, .m or .d");
+ next();
+ }
+ return 0;
+}
+// the struct whose body or methods are being compiled, when it declares constants: a bare
+// name there is looked up as Struct.name before the global scope
+char* mstruct;
+// does a constant declaration start in a struct body? const NAME = e; const T NAME = e;
+// (a field of a const type has no initializer, so the = tells them apart)
+int struct_const_start() {
+ if (!is_id(s_const)) return 0;
+ peekat(0); if (pk_tk != T_ID) return 0;
+ peekat(1);
+ if (pk_tk == T_OP && pk_tv == s_eq) return 1;
+ if (pk_tk == T_ID) { peekat(2); return pk_tk == T_OP && pk_tv == s_eq; }
+ if (pk_tk == T_OP && pk_tv == s_star) { peekat(2); if (pk_tk != T_ID) return 0; peekat(3); return pk_tk == T_OP && pk_tv == s_eq; }
+ return 0;
+}
+// const NAME = e, NAME = e; inside struct sn: a compile-time constant named Struct.NAME that takes no
+// space in the layout. the optional type is read and dropped, as in gconst.
+int struct_const(char* sn) {
+ int off, s; char* cn;
+ next();
+ if (tk == T_ID) { peekat(0); if (pk_tk == T_ID || (pk_tk == T_OP && pk_tv == s_star)) ptype(); }
+ mstruct = sn;
+ while (1) {
+ off = toff; cn = name();
+ s = lookup(methname(sn, cn));
+ if (s >= ubase && s >= 0) serrn(cn, "is defined twice in this struct");
+ expect(s_eq);
+ s = addsym(methname(sn, cn), K_CONST, cexpr());
+ rs_put(off, RS_SYM, rs_sym(s), s);
+ if (!is_op(s_comma) && !is_op(s_semi)) { // a call or a variable's value follows what cprim could read
+ serr("a struct constant must be a constant expression");
+ while (!is_op(s_semi) && !is_op(s_rb) && tk != T_EOF) next();
+ break;
+ }
+ if (!accept(s_comma)) break;
+ }
+ accept(s_semi);
+ return 0;
+}
+// struct name { type field, ... } or struct name<A, B> { ... }
+// the struct keyword. fields go in a scratch buffer first (name, type pairs),
+// then are laid out: each field takes tsz(type) words, so offsets are words.
+// methods are collected as token lists while the struct is read (grabfn) and
+// compiled only when the struct is complete, so a method can use every field
+// 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() {
+ 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; char* msv = mstruct;
+ next();
+ n = module_symbol(name());
+ if (is_op(s_lt)) { // generic
+ next(); ntp = 0;
+ while (1) {
+ if (ntp >= 8) die("too many type parameters");
+ tpn[ntp] = name();
+ lsmark(2, tpn[ntp]);
+ ntp++;
+ if (!accept(s_comma)) break;
+ }
+ closeangle();
+ gen = 1;
+ t = tnew(TY_GEN, 0, n, 0); t[3] = ntp;
+ 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 (struct_const_start()) { struct_const(n); continue; }
+ 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 = stat | 1;
+ }
+ if (is_id(s_fn)) { // fn name(params) { body }: a method, compiled once the struct is complete
+ next();
+ if (nm >= mcap) {
+ mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap);
+ mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap);
+ mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap);
+ }
+ mn[nm] = name();
+ mst[nm] = stat;
+ mq[nm] = grabfn();
+ mty[nm] = 0;
+ k = methdecl(n, mn[nm]);
+ 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 & 1) die("static must be followed by fn or a typed method");
+ next();
+ ft = tword;
+ } else {
+ // T name(params) { body } is a typed method; T name, ...; is a typed field
+ rettype();
+ fname = name();
+ if (is_op(s_lp)) {
+ if (nm >= mcap) {
+ mcap = mcap * 2; mn = chk_resize(mn, nm, mcap); mq = chk_resize(mq, nm, mcap);
+ mst = chk_resize(mst, nm, mcap); mty = chk_resize(mty, nm, mcap);
+ mrt = chk_resize(mrt, nm, mcap); mrg = chk_resize(mrg, nm, mcap);
+ }
+ mn[nm] = fname;
+ mst[nm] = stat;
+ mq[nm] = grabfn();
+ 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 & 1) sflag[k] = sflag[k] | F_STATIC;
+ nm++;
+ continue;
+ }
+ if (stat & 1) die("static must be followed by fn or a typed method");
+ if (dtag) serr("a field has no tag");
+ ft = dty;
+ }
+ while (1) {
+ if ((nf + 1) * 2 > bufcap) { buf = chk_resize(buf, nf * 2, bufcap * 2); bufcap = bufcap * 2; }
+ if (fname) { buf[nf + nf] = fname; fname = 0; } else buf[nf + nf] = name();
+ lsmark(3, buf[nf + nf]); buf[nf + nf + 1] = ft;
+ field_note(t, buf[nf + nf], nameoff); lt_note(nameoff, ft); rs_put(nameoff, RS_FIELD, nameoff, 0); nf++;
+ if (!accept(s_comma)) break;
+ }
+ expect(s_semi);
+ }
+ next();
+ if (gen) {
+ fs = talloc(nf + nf); rt.copy_w(fs, buf, nf + nf);
+ t[4] = fs; t[5] = nf;
+ mdg = ntp; rt.copy_w(mdn, tpn, ntp);
+ ntp = 0;
+ } else {
+ fs = talloc(nf * 3 + 1);
+ i = 0;
+ bo = 0;
+ while (i < nf) {
+ ft = buf[i + i + 1];
+ if (ft == t) { serr("a struct can't contain itself by value; use a pointer"); ft = tword; }
+ fs[i * 3] = buf[i + i]; fs[i * 3 + 1] = ft;
+ // a sized field packs into the current word at its own alignment; its
+ // offset is recorded as minus (byte offset + 1). anything else starts a word
+ k = tbytes(ft);
+ if (k) { bo = (bo + k - 1) / k * k; fs[i * 3 + 2] = 0 - bo - 1; bo = bo + k; }
+ else { bo = (bo + 7) / 8 * 8; fs[i * 3 + 2] = bo / 8; bo = bo + tsz(ft) * 8; }
+ i++;
+ }
+ off = (bo + 7) / 8;
+ t[4] = fs; t[1] = off; t[3] = nf;
+ }
+ accept(s_semi);
+ i = 0;
+ 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;
+ mstruct = msv;
+ return s;
+}
+// ---- 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"
+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;
+ while (mn[j]) { b[i] = mn[j]; i++; j++; }
+ b[i] = 0;
+ return intern(b);
+}
+// 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, 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(char* n) {
+ int i = 0; word t; int s, hit = 0 - 1;
+ while (i < ntl) {
+ t = typelist[i];
+ if (t[0] == TY_STRUCT && t[3] >= 0) {
+ s = methof(t, n);
+ if (s >= 0) { if (hit >= 0) return 0 - 2; hit = s; }
+ }
+ i++;
+ }
+ return hit;
+}
+// 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(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");
+ return s;
+ }
+ s = addsym(nm, K_FN, 0);
+ sflag[s] = 0;
+ return s;
+}
+// the tokens of a method, from its ( to the } that closes its body, as a token list
+// a method body is not parsed when the struct is read. its tokens are copied
+// 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.
+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 = rt.new(cap + cap);
+ rt.copy_w(nq, q, cap);
+ rt.free(q);
+ q = nq;
+ cap = cap + cap;
+ }
+ q[1 + n * 3] = tk; q[2 + n * 3] = tv; q[3 + n * 3] = tpack(); n++;
+ if (is_op(s_lb)) depth++;
+ else if (is_op(s_rb)) { depth--; if (depth <= 0) { next(); break; } }
+ next();
+ }
+ if (tk == T_EOF && depth > 0) die("missing } at the end of a method");
+ q[0] = n;
+ return q;
+}
+// compile a method by feeding the parser fn Struct.name(Struct* this, rest...)
+// build the token list "fn Struct.name ( Struct* this , rest..." from the
+// 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.
+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.
+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];
+ 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);
+ i = 0;
+ while (i < mdg) {
+ if (i) k = tkput(r, k, T_OP, s_comma, lnp);
+ k = tkput(r, k, T_ID, mdn[i], lnp);
+ i++;
+ }
+ k = tkput(r, k, T_OP, s_gt, lnp);
+ }
+ r[1 + k * 3] = q[1]; r[2 + k * 3] = q[2]; r[3 + k * 3] = q[3]; k++;
+ if (stat && mdg) die("a static function can't be in a generic struct");
+ if (!stat) {
+ r[1 + k * 3] = T_ID; r[2 + k * 3] = sn; r[3 + k * 3] = lnp; k++;
+ if (mdg) {
+ k = tkput(r, k, T_OP, s_lt, lnp);
+ i = 0;
+ while (i < mdg) {
+ if (i) k = tkput(r, k, T_OP, s_comma, lnp);
+ k = tkput(r, k, T_ID, mdn[i], lnp);
+ i++;
+ }
+ k = tkput(r, k, T_OP, s_gt, lnp);
+ }
+ r[1 + k * 3] = T_OP; r[2 + k * 3] = s_star; r[3 + k * 3] = lnp; k++;
+ r[1 + k * 3] = T_ID; r[2 + k * 3] = s_this; r[3 + k * 3] = lnp; k++;
+ }
+ if (!stat && n > 1 && !(q[1 + 3] == T_OP && q[2 + 3] == s_rp)) {
+ r[1 + k * 3] = T_OP;
+ r[2 + k * 3] = s_comma;
+ r[3 + k * 3] = lnp;
+ k++;
+ }
+ i = 1;
+ while (i < n) {
+ r[1 + k * 3] = q[1 + i * 3];
+ r[2 + k * 3] = q[2 + i * 3];
+ r[3 + k * 3] = q[3 + i * 3];
+ k++;
+ i++;
+ }
+ r[0] = k;
+ 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);
+ rt.free(r); r = joined; rt.free(return_tokens); rt.free(buffer);
+ }
+ src_push(1, r, 0, 1);
+ next();
+ if (typed && !mdg) {
+ // a typed method: skip the fn and the name, then compile from the ( with the declared return type
+ next();
+ next();
+ ifkp = tpack(); ifkk = 0 - 1; if (isp != 0) ifkk = 0;
+ o3nxt = 0; ntp = 0;
+ fndone(fndef(1, rty, rtag, methname(sn, mn)), 0);
+ } else topitem();
+ if (tk != T_EOF) die("unexpected tokens after a method");
+ src_pop();
+ return 0;
+}
+// the call e.m(args): the receiver is on the stack and becomes this
+// 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, word receiver_fact, int receiver, word receiver_mask) {
+ af_forget();
+ 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;
+ 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); effect_specialize(original, s, requirements, edges); }
+ fnref(s); op(O_CALL);
+ lv = 0;
+ lvk = LV_NONE;
+ xzero = 0;
+ xbor = -1;
+ xconst = 0;
+ xfloat = 0;
+ xstr = 0;
+ xlenloc = -1;
+ xstamp = 0;
+ 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;
+}
+// type name = T;
+// the type keyword makes an alias, which is also how a pointer to an array or
+// function type gets a name.
+int kw_type() {
+ int n;
+ next();
+ n = name();
+ expect(s_eq);
+ addsym(n, K_TYPE, ptype());
+ expect(s_semi);
+ return 0;
+}
+// does a parameter start with a type? a type name straight before , or ) is the parameter's
+// own name (fn f(string)), not a type, since a typed parameter always has a name after it.
+int paramstart() {
+ if (tk == T_OP) return tv == s_lk;
+ if (tk != T_ID) return 0;
+ if (tv == s_fn) return 1;
+ if (!istypename(tv)) return 0;
+ peek();
+ return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp));
+}
+// does a typedef start here? typedef followed by a name is one, so typedef can still be
+// a variable or a function's name.
+int typedef_ahead() {
+ if (tk != T_ID || tv != s_typedef) return 0;
+ peekat(0);
+ return pk_tk == T_ID;
+}
+// unlink the symbol s from its name, leaving the rest of the name's chain as it was.
+int td_unlink(int s) {
+ char* nm = sname[s]; int i = nm[-1] - 1;
+ if (i == s) { nm[-1] = snext[s]; return 0; }
+ while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1;
+ if (i >= 0) snext[i] = snext[s];
+ return 0;
+}
+// end the scope of the local typedefs made at block depth depth or deeper in this
+// compile context (and of any made in a context nested inside it).
+int td_end(int depth) {
+ int k;
+ while (ntd) {
+ k = ntd * 3 - 3;
+ if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break;
+ td_unlink(tdstk[k]);
+ ntd--;
+ }
+ return 0;
+}
+// is the name q (as a symbol is named) already a typedef of the block being compiled?
+int td_here(char* q) {
+ int k = ntd;
+ while (k) {
+ k--;
+ if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break;
+ if (sname[tdstk[k * 3]] == q) return 1;
+ }
+ return 0;
+}
+// typedef T name;
+// the C spelling of type name = T;. name becomes another way to write T: the very same
+// type record, so the two are interchangeable everywhere. at the top level the name is
+// global; in a function it lasts to the end of its block, like a local. a name that is
+// already in use is an error, except the same type under the same name again. a tag
+// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none.
+int kw_typedef() {
+ char* n; char* q; word t; int s, at, clash = 0, local = infn;
+ next();
+ t = ptype();
+ if (panic) return 0;
+ if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; }
+ at = toff; n = name();
+ if (panic) return 0;
+ q = module_symbol(n);
+ if (local) {
+ s = findloc(n);
+ if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1;
+ } else {
+ s = lookup_raw(q);
+ if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; }
+ if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again
+ if (s >= 0) clash = 1;
+ }
+ if (clash) serrn(n, "already defined");
+ else {
+ s = addsym(n, K_TYPE, t);
+ rs_put(at, RS_SYM, rs_sym(s), s);
+ if (local) {
+ if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); }
+ tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++;
+ }
+ }
+ expect(s_semi);
+ return 0;
+}
+// (T) or (T*) ahead of the current '(', within the lookahead we have
+// the parser sees ( and has to decide between a parenthesized expression and a
+// cast. a cast is ( type stars ) so peek ahead through the stars to the ).
+int caststart() {
+ int k;
+ 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;
+}