diff options
| author | 2026-10-14 10:05:40 +0000 | |
|---|---|---|
| committer | 2026-10-14 10:05:40 +0000 | |
| commit | ce920b6ff47f32bc06dd27124e1b882f82f3d6ae (patch) | |
| tree | 89f3dac0aa670294053320599a742d670c0ec5a4 /src/compiler/decl.zc | |
| parent | todo: tick the parity runner; zc0 follow-ups it found (diff) | |
split compiler.zc into files by phase
src/compiler/compiler.zc is now twelve files under src/compiler/, listed in
order in src/compiler/parts. cat of the parts in order is byte-identical to
the old file, so every compiler and image except the language server's is
unchanged. no function was edited.
consumers read the list: the makefile, bootstrap-check, genmap (which still
writes one compiler.zc into a generated map directory), tinyvm.bat,
test.sh, test_win.ps1 and the lsp drivers. the language server embeds each
part and serves them joined as the one document tinyvm:/compiler.zc.
Diffstat (limited to 'src/compiler/decl.zc')
| -rw-r--r-- | src/compiler/decl.zc | 1413 |
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; +} + |
