// ---- 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 in a type, or first( 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(); xc_src = src + 0; // a plain copy of the address, not a second owner 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 = 50; 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; f[48] = dfr_base; f[49] = fbnd; 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]); defer_drop(dfr_base); dfr_base = f[48]; 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]; fbnd = f[49]; 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); dfr_base = ndfr; lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fbnd = !bnxt && bound_on; bnxt = 0; 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; } // the bounds flag of the function that was being compiled when the innermost context was pushed int ctx_bnd() { return R_CTX[(ctxsp - 1) * CTX_WORDS + 49]; } // 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; } defer_run(1, 1); // the function's own deferred statements, before its locals are checked 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) { ... } *list.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, lt, lend, lfl = tline, redef, amb = 0, nonm = 0; o3nxt = 0; if (rty || ntp) typed = 1; if (!dnm) dnm = n; s = lookup(n); redef = 0; // a function this module already defined: a prototype after it keeps it (b.2.4), a second body is defined twice (b.2.3) if (s >= ubase && skind[s] == K_FN && (sflag[s] & F_DEF) && !(sflag[s] & F_TPL) && !ginst && !ntp) { word qn = n; if (module_name) qn = module_symbol(n); redef = rt.eq_w(sname[s], qn); } if (!redef && (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); if (pk && (is_op(s_comma) || is_op(s_rp))) { pn[np] = 0; nonm = 1; } // int f(int*, int): a prototype's parameters may be unnamed else { if (!pk && !t && istypename(tv)) amb = amb | (1 << np); pn[np] = name(); } // int f(int): a name, or a type if this is a prototype 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 (!accept(s_comma)) break; if (np >= 24) { die("too many parameters"); break; } // a 25th would not fit pn, pt, pty } } expect(s_rp); if (!is_op(s_semi)) { if (nonm) serr("a parameter needs a name"); amb = 0; } i = 0; while (amb) { // a prototype: a lone type name is an unnamed parameter of that type (b.2.13) if (amb & 1) { t = lookup(pn[i]); if (t >= 0 && skind[t] == K_TYPE) { pty[i] = sval[t]; typed = 1; } } amb = amb >> 1; i++; } 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); lt = lt_begin(sval[s], sname[s], lfl); xc_fn = sname[s]; 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(); lend = here; if (o3f) { o3on = 0; o3fs = fslots; o3na = np; o3fn0 = sval[s]; o3region(lo, hi, o3sl, o3rg, no3sl); } o3flush(); lt_end(lt, lend); 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 if (t && isfl(t) && tk == T_FLT) { // float g = 1.5; (appendix b.2.7) vv = tv; next(); if (t == tf32) vv = rt.zs2f(rt.zf2s(vv)); *(a + hdelta) = vv; // an f32 scalar is a rounded double } else { vv = cnorm(t, cexpr()); if (t && isfl(t)) { vv = vf_i2f(vv); if (t == tf32) vv = rt.zs2f(rt.zf2s(vv)); } // float g = 2; is 2.0 *(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_lines || tv == s_unchecked || tv == s_unbounded || 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: 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; lt_apply_marks(); 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(); o3nxt = 0; bnxt = 0; while (1) { // the prefixes, in any order 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; } else if (is_id(s_unbounded)) { next(); if (accept(s_semi)) { bound_on = 0; return 0; } // unbounded; switches the index bounds checks off from here on bnxt = 1; } else if (is_id(s_immediate)) { next(); imm = 1; } else if (is_id(s_o3)) { next(); o3nxt = 1; } else break; } 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 || bnxt) { // 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_lines)) { peek(); if (pk_tk == T_ID && (pk_tv == s_on || pk_tv == s_off)) return kw_lines(); } 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(...) { }, 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(...) 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, lt = 0 - 1; ctx_push(); ifcur = 0; ifcall = -1; effect_floor = from; module_name = module; module_imports = imports; if (infn) { hop = hole(); op(O_JMP); } if (top) prescan(1, q); src_push(1, q, 0, 0); next(); if (top) unit_top(); else { entry = here; fn_begin(1, 0); lt = lt_begin(entry, "", tline); prologue(); borrow_prologue(); borrow_begin(); while (tk != T_EOF) stmt(); defer_run(1, 1); unused_check(lbase); if (fchk && live) exit_vars(1, 1); epilogue(); lt_end(lt, here); } 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; }