aboutsummaryrefslogtreecommitdiff
path: root/src/compiler/decl.zc
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/decl.zc')
-rw-r--r--src/compiler/decl.zc1413
1 files changed, 1413 insertions, 0 deletions
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;
+}
+