diff options
| author | 2026-10-14 09:09:21 +0330 | |
|---|---|---|
| committer | 2026-10-14 09:09:21 +0330 | |
| commit | dc7f7407e08f54e4a2a3c1d1723b2a7d6959532d (patch) | |
| tree | d1de9f471a99951c57896f85b5e5523eeb96cee1 /docs/history/10-zc/code/src/compiler.zc | |
| parent | todo: sync docs, examples and fixtures with the include format, and test acto... (diff) | |
docs: archive the frozen stage code as one compressed tarball
docs/history kept six stages with a full copy of the project under code/,
3.8 mb of sources duplicated from stage to stage and already superseded. store
them as docs/history/code-snapshots.tar.gz, 0.66 mb, and extract one in place
to read it. every stage's notes.md stays browsable, and the index says where the
code went and how to get it back.
the top-level README points at the archive too. its other two links into
docs/history, 21-micro and 35-bench, were notes that were never kept and do not
resolve; the index now records that.
Diffstat (limited to 'docs/history/10-zc/code/src/compiler.zc')
| -rw-r--r-- | docs/history/10-zc/code/src/compiler.zc | 1591 |
1 files changed, 0 insertions, 1591 deletions
diff --git a/docs/history/10-zc/code/src/compiler.zc b/docs/history/10-zc/code/src/compiler.zc deleted file mode 100644 index 29116fa..0000000 --- a/docs/history/10-zc/code/src/compiler.zc +++ /dev/null @@ -1,1591 +0,0 @@ -// ============================================================================ -// compiler.zc: the resident zc compiler. it lives in the genome next to the -// runtime, compiles source straight to vm words in memory, and is visible to -// the programs it compiles: every function and global here can be called or -// patched by name, and parse words (immediate fns) run inside it. -// ============================================================================ - -// ---- compiler data region (fixed addresses, 0xA0100 .. 0xBE100) ---- -const IHN = 8192; -const R_IH = 0xA0100; // intern hash table -const R_POOL = 0xA2100; // interned strings: [symbol head] chars 0 -const R_POOLEND = 0xAC000; -const NS = 4096; // symbol table, one array per field -const R_SYM = 0xAC100; -const sname = 0xAC100, skind = 0xAD100, sval = 0xAE100, ssig = 0xAF100; -const sflag = 0xB0100, sfix = 0xB1100, snext = 0xB2100; -const NL = 512; // locals, one array per field -const lname = 0xB3100, lslot = 0xB3300, lhome = 0xB3500, lstate = 0xB3700; -const lowner = 0xB3900, lused = 0xB3B00, larr = 0xB3D00, lown = 0xB3F00; // lown: has owned an object -const NF = 8192; // patch nodes: a site and a link -const fsite = 0xB4100, fnext = 0xB6100; -const R_SNAP = 0xB8100; // analyzer snapshots -const R_QBUF = 0xBA100; // token buffer for quote and grab -const R_SRC = 0xBC100; // input source stack, 12 words per frame -const R_SEQ = 0xBC500; // operator code sequences -const bname = 0xBCD00, bprec = 0xBCD40, bseq = 0xBCD80, blen = 0xBCDC0; // binary operators -const uname = 0xBCE00, useq = 0xBCE20, ulen = 0xBCE40; // unary operators -const aname = 0xBCE60, abin = 0xBCEA0; // compound assignment -const kops = 0xBCF00; // every operator spelling the lexer knows -const R_CTX = 0xBD100; // saved compiler contexts for nested compiles -const R_LOOP = 0xBD900; // loop stack, 6 words per loop -const R_ASM = 0xBDD00; // asm block labels - -// ---- token kinds, symbol kinds and flags ---- -const T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4; -const K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5; -const F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32; -// variable states for the lifetime analyzer -const S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6; -// expression classes: plain word, a new object, or a bare variable (C_VAR + index) -const C_PLAIN = 0, C_FRESH = 1, C_VAR = 2; -// lvalue kinds -const LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4; -// opcodes -const O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7; -const O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14; -const SEQREF = -2; // in an operator sequence: the next word names a symbol - -// ---- state ---- -var pooltop, nsym, ubase, vislo, nfixu, fixfree; -var nbin, nun, nasg, nkop, seqtop; -var mnem = "q@!$_~cjzr,?.+-*&|^><"; -var check_on = 1; // set to 0 to switch the analyzer off for what follows -var tk, tv, tglue, tline, line = 1, ch, chkind, chptr, qdone, isp = -1; -var tbuf[1100]; -var here, hdelta, hlimit, ptop; -var nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn; -var lsp, loopbase, ctxsp, snaptop; -var lv, lvk, lvi, kw_expr, xcls; -// interned spellings the parser compares against -var s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec; -var s_star, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror; -var s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_immediate, s_m, s_d, s_u; -var s_start, s_main, s_free, s_own, s_qnew; - -// ---- errors ---- -fn die(msg) { puts("zc: line "); putn(tline); puts(": "); puts(msg); putc(10); halt(); return 0; } -fn dien(name, msg) { puts("zc: line "); putn(tline); puts(": "); puts(name); puts(": "); puts(msg); putc(10); halt(); return 0; } -// analyzer errors only count in checked, reachable code -fn cerr(msg) { if (fchk && live) die(msg); return 0; } -fn cerrn(i, msg) { if (fchk && live) dien(lname[i], msg); return 0; } - -// ---- interning ---- -fn hashs(s) { var h = 0; while (*s) { h = h * 31 + *s; s++; } return h & (IHN - 1); } -fn intern(s) -{ - var h = hashs(s), p, n; - while (1) { - p = R_IH[h]; - if (!p) break; - if (streq(p, s)) return p; - h = (h + 1) & (IHN - 1); - } - n = strlen(s); - if (pooltop + n + 2 >= R_POOLEND) die("string pool full"); - pooltop[0] = 0; - p = pooltop + 1; - wcopy(p, s, n + 1); - pooltop = pooltop + n + 2; - R_IH[h] = p; - return p; -} - -// ---- symbols ---- -// each interned name keeps the index (+1) of its newest symbol in p[-1], and -// each symbol links to the one it shadows, so lookup is one step. -fn addsym(name, kind, val) -{ - var i = nsym; - if (nsym >= NS) die("symbol table full"); - nsym++; - sname[i] = name; skind[i] = kind; sval[i] = val; ssig[i] = 0; sflag[i] = F_DEF; sfix[i] = 0; - snext[i] = name[-1]; - name[-1] = i + 1; - return i; -} -// while an image builds, names below vislo are hidden, except keywords -fn lookup(name) -{ - var i = name[-1] - 1; - while (i >= 0) { - if (i >= vislo || skind[i] == K_KW) return i; - i = snext[i] - 1; - } - return -1; -} -fn popsyms(to) { while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; } return 0; } -fn lookups(s) { return lookup(intern(s)); } - -// patch nodes -fn fixnew(site, link) -{ - var i = fixfree; - if (i) fixfree = fnext[i]; - else { nfixu++; i = nfixu; if (i >= NF) die("too many forward references"); } - fsite[i] = site; fnext[i] = link; - return i; -} -// patch every lit in a chain to point at v, and recycle the nodes -fn fixall(head, v) -{ - var n; - while (head) { setlit(fsite[head], v); n = fnext[head]; fnext[head] = fixfree; fixfree = head; head = n; } - return 0; -} - -// ---- code emission ---- -fn emit(w) { if (here >= hlimit) die("program too big"); *(here + hdelta) = w; here++; return 0; } -fn lit(v) { emit(v + v + 1); return 0; } -fn op(k) { emit(k + k); return 0; } -fn setlit(at, v) { *(at + hdelta) = v + v + 1; return 0; } -fn jmp(t) { lit(t); op(O_JMP); return 0; } -// a lit that will be patched later; returns its address -fn hole() { var a = here; lit(MW - 1); return a; } -fn fwdfn(name) { var s = addsym(name, K_FN, 0); sflag[s] = 0; return s; } -fn fnref(s) -{ - if (sflag[s] & F_DEF) { lit(sval[s]); return 0; } - sfix[s] = fixnew(here, sfix[s]); - lit(MW - 1); // a jump to MW-1 halts if it ever runs unpatched - return 0; -} -fn callname(name) { var s = lookup(name); if (s < 0) s = fwdfn(name); fnref(s); op(O_CALL); return 0; } -fn laddr(k) { lit(ZFP); op(O_LD); if (k) { lit(k); op(O_ADD); } return 0; } -fn strlit(s) -{ - var j = hole(), a; - op(O_JMP); - a = here; - while (*s) { emit(*s); s++; } - emit(0); - setlit(j, here); - lit(a); - return 0; -} - -// ---- input sources ---- -// frame: 0 kind (0 stdin, 1 token list, 2 string) 1 object 2 index 3 end -// 4 transparent 5..7 saved token 8..10 saved char state 11 owned -fn nextch() -{ - if (chkind == 0) { - if (qdone || eof()) { ch = -1; return 0; } - ch = getc(); - if (ch == 96) { qdone = 1; ch = -1; return 0; } // a backtick ends the source - } else { - ch = *chptr; - if (ch) chptr++; else ch = -1; - } - if (ch == 10) line++; - return 0; -} -fn src_push(kind, obj, trans, owned) -{ - var f; - isp++; - if (isp >= 32) die("sources nested too deeply"); - f = R_SRC + isp * 12; - f[0] = kind; f[1] = obj; f[2] = 0; f[3] = 0; f[4] = trans; f[11] = owned; - f[5] = tk; f[6] = tv; f[7] = tglue; f[8] = ch; f[9] = chkind; f[10] = chptr; - if (kind == 1) f[3] = obj[0]; - if (kind == 0) { chkind = 0; qdone = 0; nextch(); } - if (kind == 2) { chkind = 2; chptr = obj; nextch(); } - return 0; -} -fn src_pop() -{ - var f = R_SRC + isp * 12; - tk = f[5]; tv = f[6]; tglue = f[7]; ch = f[8]; chkind = f[9]; chptr = f[10]; - isp--; - if (f[11]) free(f[1]); - return 0; -} -fn isopch(c) -{ - var s = "+-*/%=<>!&|^~?:.@#$"; - while (*s) { if (*s == c) return 1; s++; } - return 0; -} -// is buf[0..n) followed by c the start of some operator the lexer knows? -fn opprefix(n, c) -{ - var i = 0, k, j; - while (i < nkop) { - k = kops[i]; - j = 0; - while (j < n && k[j] == tbuf[j]) j++; - if (j == n && k[n] == c) return 1; - i++; - } - return 0; -} -fn isdig(c) { return c >= '0' && c <= '9'; } -fn isal(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } -fn hexv(c) -{ - if (isdig(c)) return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} -fn escch() -{ - var c = ch; - nextch(); - if (c != 92) return c; - c = ch; - nextch(); - if (c == 'n') return 10; - if (c == 't') return 9; - if (c == 'r') return 13; - if (c == '0') return 0; - if (c == 'e') return 27; - return c; -} -fn oplex(n) -{ - while (ch >= 0 && isopch(ch) && opprefix(n, ch)) { tbuf[n] = ch; n++; nextch(); } - tbuf[n] = 0; - tk = T_OP; tv = intern(tbuf); - return 0; -} -fn lex() -{ - var n = 0, v, d; - tglue = 1; - while (1) { - while (ch == 32 || ch == 9 || ch == 10 || ch == 13) { nextch(); tglue = 0; } - if (ch != '/') break; - nextch(); - if (ch == '/') { while (ch >= 0 && ch != 10) nextch(); tglue = 0; continue; } - if (ch == '*') { - nextch(); - while (ch >= 0) { if (ch == '*') { nextch(); if (ch == '/') break; } else nextch(); } - nextch(); tglue = 0; continue; - } - tline = line; tbuf[0] = '/'; oplex(1); return 0; - } - tline = line; - if (ch < 0) { tk = T_EOF; tv = 0; return 0; } - if (isdig(ch)) { - v = 0; - if (ch == '0') { - nextch(); - if (ch == 'x' || ch == 'X') { - nextch(); - while ((d = hexv(ch)) >= 0) { v = v * 16 + d; nextch(); } - tk = T_NUM; tv = v; return 0; - } - } - while (isdig(ch)) { v = v * 10 + ch - '0'; nextch(); } - tk = T_NUM; tv = v; return 0; - } - if (isal(ch)) { - while (isal(ch) || isdig(ch)) { if (n < 1000) { tbuf[n] = ch; n++; } nextch(); } - tbuf[n] = 0; - tk = T_ID; tv = intern(tbuf); return 0; - } - if (ch == 39) { nextch(); tv = escch(); if (ch != 39) die("bad character literal"); nextch(); tk = T_NUM; return 0; } - if (ch == 34) { - nextch(); - while (ch >= 0 && ch != 34) { v = escch(); if (n < 1000) { tbuf[n] = v; n++; } } - nextch(); - tbuf[n] = 0; - tk = T_STR; tv = intern(tbuf); return 0; - } - tbuf[0] = ch; - nextch(); - if (isopch(tbuf[0])) return oplex(1); - tbuf[1] = 0; - tk = T_OP; tv = intern(tbuf); - return 0; -} -fn next() -{ - var f, q, i; - f = R_SRC + isp * 12; - if (f[0] != 1) { lex(); return 0; } - i = f[2]; - if (i < f[3]) { q = f[1]; tk = q[1 + i + i]; tv = q[2 + i + i]; tglue = 0; f[2] = i + 1; return 0; } - if (!f[4]) { tk = T_EOF; tv = 0; return 0; } - src_pop(); // a fed list ran out: continue with the token that followed it - return 0; -} -fn is_op(s) { return tk == T_OP && tv == s; } -fn is_id(s) { return tk == T_ID && tv == s; } -fn accept(s) { if ((tk == T_OP || tk == T_ID) && tv == s) { next(); return 1; } return 0; } -fn expect(s) { if (!accept(s)) { puts("zc: line "); putn(tline); puts(": expected "); puts(s); putc(10); halt(); } return 0; } -fn name() -{ - var n = tv; - if (tk != T_ID) die("expected a name"); - next(); - return n; -} - -// ---- operator tables ---- -// a sequence is written like asm: one-char mnemonics, numbers, and names -// (resolved when the operator is used, so image builds stay self-contained) -fn seqasm(s) -{ - var start = seqtop, n, neg, v, k, c; - while (1) { - while (*s == 32) s++; - if (!*s) break; - n = 0; - while (s[n] && s[n] != 32) { tbuf[n] = s[n]; n++; } - tbuf[n] = 0; - s = s + n; - neg = tbuf[0] == '-' && isdig(tbuf[1]); - if (isdig(tbuf[neg])) { - v = 0; k = neg; - while (tbuf[k]) { v = v * 10 + tbuf[k] - '0'; k++; } - if (neg) v = 0 - v; - R_SEQ[seqtop] = v + v + 1; seqtop++; - } else if (n == 1 && (c = mnemidx(tbuf[0])) >= 0) { - R_SEQ[seqtop] = c + c; seqtop++; - } else { - R_SEQ[seqtop] = SEQREF; R_SEQ[seqtop + 1] = intern(tbuf); seqtop = seqtop + 2; - } - } - return start; -} -fn mnemidx(c) { var i = 0; while (mnem[i]) { if (mnem[i] == c) return i; i++; } return -1; } -fn emitseq(a, n) -{ - var e = a + n, s; - while (a < e) { - if (R_SEQ[a] == SEQREF) { - s = lookup(R_SEQ[a + 1]); - if (s < 0) s = fwdfn(R_SEQ[a + 1]); - if (skind[s] == K_FN) fnref(s); else lit(sval[s]); - a = a + 2; - } else { emit(R_SEQ[a]); a++; } - } - return 0; -} -fn addkop(p) { var i = 0; while (i < nkop) { if (kops[i] == p) return 0; i++; } kops[nkop] = p; nkop++; return 0; } -fn binfind(p) { var i = nbin; while (i) { i--; if (bname[i] == p) return i; } return -1; } -fn unfind(p) { var i = nun; while (i) { i--; if (uname[i] == p) return i; } return -1; } -fn asgfind(p) { var i = nasg; while (i) { i--; if (aname[i] == p) return i; } return -1; } -// add or replace a binary operator. code is a sequence string, or 0 for the -// built-in && (prec 2) and || (prec 1) behaviour, chosen by -1 / -2 in prec. -fn add_binop(nm, prec, code) -{ - var p = intern(nm), i = binfind(p), a, n; - if (i < 0) { i = nbin; nbin++; } - bname[i] = p; bprec[i] = prec; - if (prec < 0) { bprec[i] = 0 - prec; blen[i] = prec; bseq[i] = 0; addkop(p); return i; } - bseq[i] = seqasm(code); blen[i] = seqtop - bseq[i]; - addkop(p); - // and its compound assignment, x op= y - n = strlen(nm); - wcopy(tbuf + 1000, nm, n); - tbuf[1000 + n] = '='; tbuf[1001 + n] = 0; - a = intern(tbuf + 1000); - if (prec >= 8 || prec == 5 || prec == 4 || prec == 3) { - if (asgfind(a) < 0) { aname[nasg] = a; nasg++; } - abin[asgfind(a)] = i; - addkop(a); - } - return i; -} -fn add_unop(nm, code) -{ - var p = intern(nm), i = unfind(p); - if (i < 0) { i = nun; nun++; } - uname[i] = p; useq[i] = seqasm(code); ulen[i] = seqtop - useq[i]; - addkop(p); - return i; -} -fn add_kw(nm, handler, flags) { var s = addsym(intern(nm), K_KW, handler); sflag[s] = F_DEF | flags; return s; } - -// ---- locals and the analyzer's view of them ---- -fn findloc(n) { var i = nloc; while (i > lbase) { i--; if (lname[i] == n) return i; } return -1; } -fn newloc(n) -{ - var i = nloc; - if (nloc >= NL) die("too many locals"); - nloc++; - lname[i] = n; lslot[i] = fslots; lhome[i] = fdepth; lstate[i] = S_PLAIN; - lowner[i] = 0; lused[i] = 0; larr[i] = 0; lown[i] = 0; - return i; -} -fn lroot(i) { while (lstate[i] == S_CHILD) i = lowner[i]; return i; } -fn tracked(i) { return lstate[i] != S_PLAIN; } -// a use that doesn't give the object away -fn touch(i) -{ - var r; - lused[i] = 1; - if (!fchk) return 0; - r = lroot(i); - lused[r] = 1; - if (lstate[r] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - // a d object that shows up in a deeper block is given to that block - if (lstate[r] == S_D && lhome[r] < fdepth) { lhome[r] = fdepth; lstate[r] = S_M; lown[r] = 1; } - return 0; -} -fn plainuse(c) -{ - if (c == C_FRESH) cerr("a new object can't be used as a plain value; bind it to a name first"); - if (c >= C_VAR) touch(c - C_VAR); - return C_PLAIN; -} -fn freevar(i) { laddr(lslot[i]); op(O_LD); callname(s_free); op(O_DROP); return 0; } -// leaving every block deeper than or equal to dep: free what is owned, and -// complain about what should have left already. final means the block really -// ends here rather than an early exit. -fn exit_vars(dep, final) -{ - var i = nloc, st; - if (!fchk || !live) return 0; - while (i > lbase) { - i--; - if (lhome[i] < dep) continue; - st = lstate[i]; - if (st == S_M) freevar(i); else if (st == S_U) cerrn(i, "u object must leave this block (block value, assignment to the enclosing block, or return)"); - else if (st == S_D) cerrn(i, "d object was never given away"); - } - return 0; -} -// an m object has to be used somewhere in the block that owns it, on any path -fn unused_check(from) -{ - while (from < nloc) { if (fchk && lown[from] && !lused[from]) dien(lname[from], "m object is never used"); from++; } - return 0; -} -fn block_close(save) -{ - var i = lbase; - unused_check(save); - exit_vars(fdepth, 1); - while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; } - nloc = save; - return 0; -} - -// snapshots: [lbase] [nloc] then state, owner, home per local -fn snap_push() -{ - var b = snaptop, i = lbase, p; - p = R_SNAP + b; - p[0] = lbase; p[1] = nloc; - p = p + 2; - while (i < nloc) { p[0] = lstate[i]; p[1] = lowner[i]; p[2] = lhome[i]; p = p + 3; i++; } - snaptop = p - R_SNAP; - if (snaptop >= 8000) die("nesting too deep for the analyzer"); - return b; -} -fn snap_pop(b) { snaptop = b; return 0; } -fn snap_restore(b) -{ - var p = R_SNAP + b, i = p[0], e = p[1]; - if (e > nloc) e = nloc; - p = p + 2; - while (i < e) { lstate[i] = p[0]; lowner[i] = p[1]; lhome[i] = p[2]; p = p + 3; i++; } - return 0; -} -fn empty(s) { return s == S_PLAIN || s == S_DEAD; } -// compare the current states with a snapshot: -1 if they agree, else the -// first local that differs. plain and dead agree and merge to dead. -fn snap_cmp(b) -{ - var p = R_SNAP + b, i = p[0], e = p[1], s; - if (e > nloc) e = nloc; - p = p + 2; - while (i < e) { - s = lstate[i]; - if (empty(s) && empty(p[0])) { if (p[0] == S_DEAD) lstate[i] = S_DEAD; } - else if (s != p[0] || lhome[i] != p[2] || (s == S_CHILD && lowner[i] != p[1])) return i; - p = p + 3; i++; - } - return -1; -} -fn statename(s) -{ - if (s == S_PLAIN) return "empty"; - if (s == S_M) return "m"; - if (s == S_U) return "u"; - if (s == S_D) return "d"; - if (s == S_DEAD) return "given away"; - if (s == S_CHILD) return "owned by a container"; - return "borrowed"; -} -fn merge_err(i, sa, what) -{ - if (!fchk) return 0; - puts("zc: line "); putn(tline); puts(": "); puts(lname[i]); puts(": "); puts(what); - puts(" (one path leaves it "); puts(statename(sa)); puts(", another "); puts(statename(lstate[i])); puts(")\n"); - halt(); - return 0; -} -// after an if: the current state is the else path, sa is the then path -fn merge(sa, livea) -{ - var i, p; - if (!livea) return 0; - if (!live) { snap_restore(sa); live = 1; return 0; } - i = snap_cmp(sa); - if (i >= 0) { p = R_SNAP + sa + 2 + 3 * (i - R_SNAP[sa]); merge_err(i, p[0], "ends the branches of an if in different states"); } - return 0; -} -fn loopcheck(s0) -{ - var i, p; - if (!fchk || !live) return 0; - i = snap_cmp(s0); - if (i >= 0) { p = R_SNAP + s0 + 2 + 3 * (i - R_SNAP[s0]); merge_err(i, p[0], "changes state inside a loop; each pass has to leave it as it found it"); } - return 0; -} - -// ---- analyzer rules for the places values flow ---- -// recast: x as u / x as d / x as m -fn recast(c, t) -{ - var i, st; - if (!fchk) return c; - if (c < C_VAR) { cerr("only a named owned object can be recast"); return c; } - i = c - C_VAR; - st = lstate[i]; - lused[i] = 1; - if (st == S_CHILD) cerrn(i, "belongs to a container; recast the container instead"); - else if (st == S_BORROW) cerrn(i, "is borrowed and can't be recast"); - else if (st == S_PLAIN) cerrn(i, "isn't an owned object"); - else if (st == S_DEAD) cerrn(i, "used after it was given away"); - else if (t == S_M && st != S_M) cerrn(i, "can't be recast back to m"); - else lstate[i] = t; - return c; -} -// an argument for a parameter tagged t (0 plain, 1 m, 2 d) -fn argcheck(c, t) -{ - var i, st; - if (!fchk) return 0; - if (c == C_FRESH) { if (t != 2) cerr("a new object can only go straight to a d parameter; bind it to a name first"); return 0; } - if (c < C_VAR || !tracked(c - C_VAR)) return 0; - i = c - C_VAR; - st = lstate[i]; - if (lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - if (t == 0) { cerrn(i, "is an owned object; it can only be passed to an m or d parameter"); return 0; } - if (t == 1) { - if (st == S_D) { cerrn(i, "is d; pass it to a d parameter (lending it would lose it)"); return 0; } - touch(i); - return 0; - } - if (st == S_D) { lstate[i] = S_DEAD; lused[i] = 1; return 0; } - if (st == S_M) cerrn(i, "is m; write 'as d' to give it away"); - else if (st == S_U) cerrn(i, "is u; write 'as d' to give it away instead of returning it"); - else if (st == S_CHILD) cerrn(i, "belongs to a container and can't be given away alone"); - else cerrn(i, "is borrowed and can't be given away"); - return 0; -} -fn emitown(p) { laddr(lslot[p]); op(O_LD); callname(s_own); return 0; } -// a store of class rc into an lvalue of kind k (local or container index i) -fn assigncheck(k, i, rc) -{ - var st, j, r, js; - if (!fchk) return C_PLAIN; - j = -1; - if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR; - if (j >= 0 && lstate[lroot(j)] == S_DEAD) { cerrn(j, "used after it was given away"); return C_PLAIN; } - if (k == LV_LOCAL) { - st = lstate[i]; - if (j < 0 && rc != C_FRESH) { - if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it"); - else lstate[i] = S_PLAIN; - return C_PLAIN; - } - if (!empty(st)) { cerrn(i, "still owns an object; overwriting it would leak it"); return C_PLAIN; } - if (rc == C_FRESH) { lstate[i] = S_M; lused[i] = 0; lown[i] = 1; return C_PLAIN; } - js = lstate[j]; - if (js == S_U && lhome[i] == lhome[j] - 1) { // handing a u object up one block - lstate[i] = S_M; lused[i] = 0; lown[i] = 1; lstate[j] = S_DEAD; lused[j] = 1; - return C_PLAIN; - } - if (js == S_U) cerrn(j, "is u; it can only be handed to a variable of the enclosing block, or returned"); - else cerrn(j, "is an owned object and can't be copied into another variable"); - return C_PLAIN; - } - if (k == LV_ELEM) { - r = lroot(i); - st = lstate[r]; - touch(i); - if (rc == C_FRESH) { emitown(i); return C_PLAIN; } - if (j < 0) return C_PLAIN; - js = lstate[j]; - if (js == S_CHILD) cerrn(j, "already belongs to a container"); - else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored"); - else if (st == S_BORROW) { - if (js != S_D) cerrn(j, "must be d to be stored into a borrowed object"); - else { lstate[j] = S_DEAD; lused[j] = 1; emitown(i); } - } else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)"); - else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into"); - else { lstate[j] = S_CHILD; lowner[j] = i; lused[j] = 1; emitown(i); } - return C_PLAIN; - } - // a global, or memory the analyzer doesn't track: the collector takes it - if (j >= 0) { - if (lstate[j] == S_D) { lstate[j] = S_DEAD; lused[j] = 1; } - else cerrn(j, "must be d to be stored in a global or in untracked memory (the collector takes it over)"); - } - return C_PLAIN; -} -fn retcheck(c) -{ - var i; - if (!fchk) return 0; - i = -1; - if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR; - if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } - if (fret) { - if (i >= 0) { - if (lstate[i] == S_U) { lstate[i] = S_DEAD; lused[i] = 1; } - else cerrn(i, "has to be u to be returned; write 'return x as u'"); - } - return 0; - } - if (c == C_FRESH) cerr("returns a new object; declare the function with -> u"); - else if (i >= 0) cerrn(i, "is owned and can't be returned from a function without -> u"); - return 0; -} -// the last expression of a block expression -fn handup(c) -{ - var i; - if (!fchk) return C_PLAIN; - if (c == C_FRESH) return C_FRESH; - if (c < C_VAR || !tracked(c - C_VAR)) return C_PLAIN; - i = c - C_VAR; - if (lstate[i] == S_U && lhome[i] == fdepth) { lstate[i] = S_DEAD; lused[i] = 1; return C_FRESH; } - if (lstate[i] == S_U) cerrn(i, "only a u object of this block can be its value"); - else cerrn(i, "has to be u to be handed up as the block's value"); - return C_PLAIN; -} -fn discard(c) -{ - if (c == C_FRESH) cerr("a new object (or a u result) can't be thrown away"); - else if (c >= C_VAR) touch(c - C_VAR); - return 0; -} - -// ---- expressions ---- -fn rval() { if (lv) { op(O_LD); lv = 0; } return 0; } -fn kwcall(s, e) -{ - var f = sval[s], old = kw_expr, c; - kw_expr = e; xcls = C_PLAIN; - f(); - kw_expr = old; - c = xcls; - lv = 0; lvk = LV_NONE; - return c; -} -fn stmtonly() { if (kw_expr) die("this can't be used inside an expression"); return 0; } -fn call(s) -{ - var np = 0, tags = 0, ret = 0, c; - if (sflag[s] & F_SIG) { tags = ssig[s] >> 6; ret = ssig[s] & 1; } - else sflag[s] = sflag[s] | F_UNTYPED; - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - c = expr(); rval(); - argcheck(c, (tags >> (np + np)) & 3); - np++; - if (!accept(s_comma)) break; - } - } - expect(s_rp); - if ((sflag[s] & F_SIG) && np != ((ssig[s] >> 1) & 31)) dien(sname[s], "wrong number of arguments"); - fnref(s); op(O_CALL); - lv = 0; lvk = LV_NONE; - if (ret) return C_FRESH; - return C_PLAIN; -} -fn primary() -{ - var n, i, s, c; - lv = 0; lvk = LV_NONE; - if (tk == T_NUM) { lit(tv); next(); return C_PLAIN; } - if (tk == T_STR) { strlit(tv); next(); return C_PLAIN; } - if (tk == T_OP) { - if (tv == s_lp) { next(); c = expr(); expect(s_rp); return c; } - if (tv == s_lb) return blockexpr(); - die("expected an expression"); - } - if (tk != T_ID) die("expected an expression"); - n = tv; - i = findloc(n); - if (i >= 0) { - next(); - if (!fchk && tracked(i)) dien(n, "is tracked by the analyzer and can't be used in unchecked code"); - laddr(lslot[i]); - if (larr[i]) return C_PLAIN; - lv = 1; lvk = LV_LOCAL; lvi = i; - if (tracked(i)) return C_VAR + i; - return C_PLAIN; - } - s = lookup(n); - if (s >= 0 && skind[s] == K_KW) { - if (!(sflag[s] & F_EXPR)) dien(n, "can't be used inside an expression"); - return kwcall(s, 1); - } - next(); - if (s >= 0 && skind[s] == K_CONST) { lit(sval[s]); return C_PLAIN; } - if (s >= 0 && skind[s] == K_GVAR) { lit(sval[s]); lv = 1; lvk = LV_GLOBAL; return C_PLAIN; } - if (s >= 0 && skind[s] == K_GARR) { lit(sval[s]); return C_PLAIN; } - if (s < 0) s = fwdfn(n); - if (is_op(s_lp)) return call(s); - fnref(s); - return C_PLAIN; -} -// ++ and -- on the address at the top of the stack -fn incdec(post, d) -{ - if (!lv) die("++ and -- need something to assign to"); - if (lvk == LV_LOCAL && fchk && tracked(lvi)) cerrn(lvi, "owns an object and can't be incremented"); - lv = 0; lvk = LV_NONE; - lit(0); op(O_PICK); op(O_LD); - if (post) { lit(0); op(O_PICK); lit(d); op(O_ADD); lit(2); op(O_PICK); op(O_ST); } - else { lit(d); op(O_ADD); lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_ST); } - op(O_SWAP); op(O_DROP); - return C_PLAIN; -} -fn postfix() -{ - var c = primary(), b, n, t; - while (1) { - if (tk == T_ID && tv == s_as) { - next(); - if (tk != T_ID) die("expected u, d or m after as"); - t = S_M; - if (tv == s_u) t = S_U; else if (tv == s_d) t = S_D; else if (tv != s_m) die("expected u, d or m after as"); - next(); - rval(); - c = recast(c, t); - lvk = LV_NONE; - continue; - } - if (tk != T_OP) return c; - if (tv == s_lk) { - next(); - rval(); - b = -1; - if (c == C_FRESH) cerr("index a new object only after binding it to a name"); - else if (c >= C_VAR) { touch(c - C_VAR); b = c - C_VAR; } - c = expr(); rval(); plainuse(c); - expect(s_rk); - op(O_ADD); - lv = 1; - if (b >= 0) { lvk = LV_ELEM; lvi = b; } else lvk = LV_OTHER; - c = C_PLAIN; - continue; - } - if (tv == s_lp) { // call through a value - rval(); plainuse(c); - next(); - n = 0; - if (!is_op(s_rp)) { while (1) { c = expr(); rval(); argcheck(c, 0); n++; if (!accept(s_comma)) break; } } - expect(s_rp); - lit(n); op(O_PICK); op(O_CALL); op(O_SWAP); op(O_DROP); - lv = 0; lvk = LV_NONE; c = C_PLAIN; - continue; - } - if (tv == s_inc) { next(); c = incdec(1, 1); continue; } - if (tv == s_dec) { next(); c = incdec(1, -1); continue; } - return c; - } - return c; -} -fn unary() -{ - var c, u; - if (tk == T_OP) { - if (tv == s_star) { next(); c = unary(); rval(); plainuse(c); lv = 1; lvk = LV_OTHER; return C_PLAIN; } - if (tv == s_amp) { - next(); c = unary(); - if (!lv) die("& needs something addressable"); - if (c >= C_VAR) touch(c - C_VAR); - lv = 0; lvk = LV_NONE; - return C_PLAIN; - } - if (tv == s_inc) { next(); unary(); return incdec(0, 1); } - if (tv == s_dec) { next(); unary(); return incdec(0, -1); } - u = unfind(tv); - if (u >= 0) { - next(); c = unary(); rval(); plainuse(c); - emitseq(useq[u], ulen[u]); - lv = 0; lvk = LV_NONE; - return C_PLAIN; - } - } - return postfix(); -} -fn bin(minp) -{ - var c = unary(), b, p, a, j; - while (tk == T_OP) { - b = binfind(tv); - if (b < 0) return c; - p = bprec[b]; - if (p < minp) return c; - next(); rval(); plainuse(c); - if (blen[b] == -1) { // && : 0 if the left side is 0, else the right side as 0 or 1 - a = hole(); op(O_JZ); - c = bin(p + 1); rval(); plainuse(c); - emitseq(useq[1], ulen[1]); // !! normalizes - j = hole(); op(O_JMP); - setlit(a, here); lit(0); setlit(j, here); - } else if (blen[b] == -2) { // || : 1 if the left side isn't 0, else the right side as 0 or 1 - a = hole(); op(O_JZ); - lit(1); j = hole(); op(O_JMP); - setlit(a, here); - c = bin(p + 1); rval(); plainuse(c); - emitseq(useq[1], ulen[1]); - setlit(j, here); - } else { - c = bin(p + 1); rval(); plainuse(c); - emitseq(bseq[b], blen[b]); - } - lv = 0; lvk = LV_NONE; c = C_PLAIN; - } - return c; -} -// [addr v] -> [v], storing v at addr -fn storekeep() { op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); op(O_ST); return 0; } -fn expr() -{ - var c = bin(1), k, i, a, rc; - if (tk != T_OP) return c; - if (tv == s_eq) { - if (!lv) die("this can't be assigned to"); - k = lvk; i = lvi; - next(); - rc = expr(); rval(); - storekeep(); - lv = 0; lvk = LV_NONE; - return assigncheck(k, i, rc); - } - a = asgfind(tv); - if (a >= 0) { - if (!lv) die("this can't be assigned to"); - if (lvk == LV_LOCAL && fchk && tracked(lvi)) cerrn(lvi, "owns an object; compound assignment would lose it"); - next(); - lit(0); op(O_PICK); op(O_LD); - lv = 0; - rc = expr(); rval(); plainuse(rc); - emitseq(bseq[abin[a]], blen[abin[a]]); - storekeep(); - lv = 0; lvk = LV_NONE; - return C_PLAIN; - } - return c; -} -// does the current token start a statement rather than an expression? -fn stmtstart() -{ - var s; - if (tk == T_OP) return tv == s_semi || tv == s_lb; - if (tk != T_ID || findloc(tv) >= 0) return 0; - s = lookup(tv); - return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR); -} -// { stmts; tail } as an expression: its value is the tail, or 0 -fn blockexpr() -{ - var save = nloc, c = C_PLAIN, tail = 0; - next(); - fdepth++; bxdepth++; - while (!is_op(s_rb)) { - if (tk == T_EOF) die("missing }"); - if (stmtstart()) { stmt(); continue; } - c = expr(); rval(); - if (accept(s_semi)) { op(O_DROP); discard(c); c = C_PLAIN; continue; } - if (!is_op(s_rb)) die("expected ; or } after an expression"); - tail = 1; - } - next(); - if (tail) c = handup(c); else lit(0); - block_close(save); - fdepth--; bxdepth--; - lv = 0; lvk = LV_NONE; - return c; -} - -// ---- constant expressions ---- -fn cprim() -{ - var v, s; - if (tk == T_NUM) { v = tv; next(); return v; } - if (accept(s_minus)) return 0 - cprim(); - if (tk == T_OP && tv == uname[2]) { next(); return ~cprim(); } - if (accept(s_lp)) { v = cexpr(); expect(s_rp); return v; } - if (tk == T_ID) { - s = lookup(tv); - if (s >= 0 && (skind[s] == K_CONST || skind[s] == K_GARR || (skind[s] == K_FN && (sflag[s] & F_DEF)) || skind[s] == K_GVAR)) { next(); return sval[s]; } - } - die("expected a constant"); - return 0; -} -fn cexpr() -{ - var v = cprim(), o; - while (tk == T_OP) { - o = tv; - if (o == bname[3]) { next(); v = v + cprim(); } - else if (o == bname[4]) { next(); v = v - cprim(); } - else if (o == bname[0]) { next(); v = v * cprim(); } - else if (o == bname[15]) { next(); v = v | cprim(); } - else if (o == bname[13]) { next(); v = v & cprim(); } - else if (o == bname[5]) { next(); v = v << cprim(); } - else if (o == bname[6]) { next(); v = v >> cprim(); } - else return v; - } - return v; -} - -// ---- statements ---- -fn stmt() -{ - var s, c; - if (tk == T_ID && findloc(tv) < 0) { - s = lookup(tv); - if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; } - } - if (tk == T_OP) { - if (tv == s_semi) { next(); return 0; } - if (tv == s_lb) { block(); return 0; } - } - c = expr(); rval(); op(O_DROP); - expect(s_semi); - discard(c); - return 0; -} -fn block() -{ - var save = nloc; - expect(s_lb); - fdepth++; - while (!is_op(s_rb)) { if (tk == T_EOF) die("missing }"); stmt(); } - next(); - block_close(save); - fdepth--; - return 0; -} -// the body of if/while/for: its own block, even without braces -fn substmt() -{ - var save = nloc; - if (is_op(s_lb)) return block(); - fdepth++; - stmt(); - block_close(save); - fdepth--; - return 0; -} -fn kw_var() -{ - var n, i, c, k; - stmtonly(); - next(); - while (1) { - n = name(); - i = newloc(n); - if (accept(s_lk)) { k = cexpr(); expect(s_rk); larr[i] = 1; fslots = fslots + k; } - else fslots++; - if (accept(s_eq)) { c = expr(); rval(); laddr(lslot[i]); op(O_ST); assigncheck(LV_LOCAL, i, c); } - if (!accept(s_comma)) break; - } - expect(s_semi); - return 0; -} -fn kw_if() -{ - var a, b, c, s0, sa, live0, livea; - stmtonly(); - next(); - expect(s_lp); c = expr(); rval(); plainuse(c); expect(s_rp); - a = hole(); op(O_JZ); - live0 = live; - s0 = snap_push(); - substmt(); - livea = live; sa = snap_push(); - if (is_id(s_else)) { - next(); - b = hole(); op(O_JMP); - setlit(a, here); - snap_restore(s0); live = live0; - substmt(); - setlit(b, here); - } else { setlit(a, here); snap_restore(s0); live = live0; } - merge(sa, livea); - snap_pop(s0); - return 0; -} -// loop frame: 0 continue target 1 break chain 2 depth 3 snapshot 4 block-expression depth -fn loop_push(cont, s0) -{ - var f = R_LOOP + lsp * 6; - if (lsp >= 60) die("loops nested too deeply"); - f[0] = cont; f[1] = 0; f[2] = fdepth; f[3] = s0; f[4] = bxdepth; - lsp++; - return 0; -} -fn loop_pop() { lsp--; fixall(R_LOOP[lsp * 6 + 1], here); return 0; } -fn kw_while() -{ - var c0 = here, a, s0, c; - stmtonly(); - next(); - s0 = snap_push(); - expect(s_lp); c = expr(); rval(); plainuse(c); expect(s_rp); - a = hole(); op(O_JZ); - loop_push(c0, s0); - substmt(); - loopcheck(s0); - jmp(c0); - setlit(a, here); - loop_pop(); - snap_restore(s0); live = 1; - snap_pop(s0); - return 0; -} -fn kw_for() -{ - var save = nloc, s0, c0, a, b, d, c; - stmtonly(); - next(); - expect(s_lp); - fdepth++; - if (is_id(s_var)) kw_var(); - else { if (!is_op(s_semi)) { c = expr(); rval(); op(O_DROP); discard(c); } expect(s_semi); } - s0 = snap_push(); - c0 = here; - if (!is_op(s_semi)) { c = expr(); rval(); plainuse(c); } else lit(1); - expect(s_semi); - a = hole(); op(O_JZ); - b = hole(); op(O_JMP); - d = here; - if (!is_op(s_rp)) { c = expr(); rval(); op(O_DROP); discard(c); } - expect(s_rp); - jmp(c0); - setlit(b, here); - loop_push(d, s0); - substmt(); - loopcheck(s0); - jmp(d); - setlit(a, here); - loop_pop(); - snap_restore(s0); live = 1; - snap_pop(s0); - block_close(save); - fdepth--; - return 0; -} -fn loopexit(f) -{ - if (lsp <= loopbase) die("break or continue outside a loop"); - if (f[4] != bxdepth) die("break and continue can't leave a block expression"); - loopcheck(f[3]); - exit_vars(f[2] + 1, 0); - return 0; -} -fn kw_break() -{ - var f = R_LOOP + (lsp - 1) * 6; - stmtonly(); - next(); expect(s_semi); - loopexit(f); - f[1] = fixnew(here, f[1]); - lit(MW - 1); op(O_JMP); - live = 0; - return 0; -} -fn kw_continue() -{ - var f = R_LOOP + (lsp - 1) * 6; - stmtonly(); - next(); expect(s_semi); - loopexit(f); - jmp(f[0]); - live = 0; - return 0; -} -fn kw_return() -{ - var c; - stmtonly(); - next(); - if (!infn) die("return outside a function"); - if (bxdepth) die("return can't leave a block expression"); - if (is_op(s_semi)) { lit(0); c = C_PLAIN; } else { c = expr(); rval(); } - expect(s_semi); - retcheck(c); - exit_vars(1, 0); - epichain = fixnew(here, epichain); - lit(MW - 1); op(O_JMP); - live = 0; - return 0; -} -fn kw_unchecked() -{ - var old = fchk; - stmtonly(); - next(); - fchk = 0; - substmt(); - fchk = old; - return 0; -} - -// ---- asm blocks: raw vm words, one per token ---- -// numbers push, -n pushes a negative number, #n emits a raw word, one-char -// tokens from the mnemonic table are opcodes, names push the address of a -// variable or function, :name defines a label for this block. -fn asmblock() -{ - var nl = 0, nr = 0, i, k, c, s, ln = R_ASM, la = R_ASM + 64, rn = R_ASM + 128, ra = R_ASM + 384; - expect(s_lb); - while (!is_op(s_rb)) { - if (tk == T_EOF) die("unterminated asm block"); - if (tk == T_NUM) { lit(tv); next(); continue; } - if (tk == T_STR) { strlit(tv); next(); continue; } - if (is_op(s_hash)) { next(); if (tk != T_NUM) die("# needs a number"); emit(tv); next(); continue; } - if (is_op(s_minus)) { next(); if (tk == T_NUM && tglue) { lit(0 - tv); next(); } else op(O_SUB); continue; } - if (is_op(s_colon)) { next(); if (nl >= 64) die("too many asm labels"); ln[nl] = name(); la[nl] = here; nl++; continue; } - if (tv[0] && !tv[1] && (c = mnemidx(tv[0])) >= 0) { op(c); next(); continue; } - if (tk != T_ID) die("bad token in asm"); - i = findloc(tv); - if (i >= 0) { lused[i] = 1; laddr(lslot[i]); next(); continue; } - k = -1; i = 0; - while (i < nl) { if (ln[i] == tv) k = i; i++; } - if (k >= 0) { lit(la[k]); next(); continue; } - s = lookup(tv); - if (s >= 0 && skind[s] != K_FN && skind[s] != K_KW) { lit(sval[s]); next(); continue; } - if (s >= 0 && skind[s] == K_FN) { fnref(s); next(); continue; } - if (nr >= 250) die("too many asm label references"); - rn[nr] = tv; ra[nr] = hole(); nr++; next(); // a label further down this block - } - next(); - i = 0; - while (i < nr) { - k = -1; c = 0; - while (c < nl) { if (ln[c] == rn[i]) k = c; c++; } - if (k < 0) dien(rn[i], "unknown name in asm"); - setlit(ra[i], la[k]); - i++; - } - return 0; -} -fn kw_asm() { next(); asmblock(); xcls = C_PLAIN; return 0; } - -// ---- quote: code as data ---- -// a token list is an object: [n] then kind, value for each token. names, -// strings and operators are interned pointers. -fn grab() -> u -{ - var n = 0, depth = 0, q; - if (!is_op(s_lb)) die("expected {"); - next(); - while (1) { - if (tk == T_EOF) die("unterminated quote"); - if (is_op(s_lb)) depth++; - if (is_op(s_rb)) { if (!depth) break; depth--; } - if (n >= 4000) die("quote too long"); - R_QBUF[n + n] = tk; R_QBUF[n + n + 1] = tv; n++; - next(); - } - next(); - q = new(n + n + 1); - q[0] = n; - wcopy(q + 1, R_QBUF, n + n); - return q as u; -} -unchecked fn qnew(src) -> u { var n = src[0] * 2 + 1, q = new(n); wcopy(q, src, n); return q; } -fn qdup(m q) -> u { var n = q[0] * 2 + 1, r = new(n); wcopy(r, q, n); return r as u; } -fn wcopyto(m q, n) { var i = 0; while (i < n) { emit(q[i]); i++; } return 0; } -fn kw_quote() -{ - var q, j, a, n; - next(); - q = grab(); - j = hole(); op(O_JMP); - a = here; - n = q[0] * 2 + 1; - wcopyto(q, n); - free(q as d); - setlit(j, here); - lit(a); - callname(s_qnew); - xcls = C_FRESH; - return 0; -} -// tokens of a string, as a token list -fn tokens(s) -> u -{ - var n = 0, q; - src_push(2, s, 0, 0); - next(); - while (tk != T_EOF) { - if (n >= 4000) die("too many tokens"); - R_QBUF[n + n] = tk; R_QBUF[n + n + 1] = tv; n++; - next(); - } - src_pop(); - q = new(n + n + 1); - q[0] = n; - wcopy(q + 1, R_QBUF, n + n); - return q as u; -} -// feed a token list into the input: the parser reads it next, then carries -// on where it was. the list is freed once it has been read. -unchecked fn feed(d q) { src_push(1, q, 1, 1); next(); 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. -fn ctx_push() -{ - var f = R_CTX + ctxsp * 24; - if (ctxsp >= 30) die("compiles nested too deeply"); - 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; - return 0; -} -fn ctx_pop() -{ - var f; - ctxsp--; - f = R_CTX + ctxsp * 24; - nloc = 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]; - return 0; -} - -// ---- functions and the top level ---- -fn fn_begin(chk, ret) -{ - lbase = nloc; fdepth = 1; fslots = 0; fchk = chk && check_on; fret = ret; - live = 1; bxdepth = 0; epichain = 0; infn = 1; loopbase = lsp; lv = 0; lvk = LV_NONE; - return 0; -} -fn prologue() { lit(ZFP); op(O_LD); fframe = hole(); op(O_SUB); lit(ZFP); op(O_ST); return 0; } -fn epilogue() -{ - lit(0); - fixall(epichain, here); - lit(ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(ZFP); op(O_ST); op(O_RET); - setlit(fframe, fslots); - infn = 0; - nloc = lbase; - return 0; -} -fn body() -{ - expect(s_lb); - while (!is_op(s_rb)) { if (tk == T_EOF) die("missing } at the end of a function"); stmt(); } - next(); - unused_check(lbase); - if (fchk && live) { exit_vars(1, 1); if (fret) cerr("a -> u function has to end with a return"); } - return 0; -} -fn fndef(chk) -{ - var n, s, np = 0, tags = 0, t, ret = 0, sig, i, pn[24], pt[24]; - next(); - n = name(); - 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; } - expect(s_lp); - if (!is_op(s_rp)) { - while (1) { - t = 0; - if (tk == T_ID && (tv == s_m || tv == s_d)) { - t = 1; if (tv == s_d) t = 2; - pn[np] = tv; - next(); - if (tk == T_ID) pn[np] = name(); else t = 0; // a parameter called m or d - } else pn[np] = name(); - 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); - if (accept(s_arrow)) { if (!is_id(s_u)) die("the only return tag is -> u"); next(); ret = 1; } - sig = ret + np + np + tags * 64; - if (sflag[s] & F_SIG) { if (ssig[s] != sig) dien(n, "doesn't match its earlier declaration"); } - else if ((sflag[s] & F_UNTYPED) && (tags || ret)) dien(n, "was called before its tags were declared; put a prototype above the first call"); - ssig[s] = sig; - sflag[s] = sflag[s] | F_SIG; - if (accept(s_semi)) return s; - if (sflag[s] & F_DEF) dien(n, "is defined twice"); - sval[s] = here; - sflag[s] = sflag[s] | F_DEF; - fixall(sfix[s], here); sfix[s] = 0; - fn_begin(chk, ret); - prologue(); - i = 0; - while (i < np) { - t = newloc(pn[i]); fslots++; - 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); } - body(); - epilogue(); - return s; -} -fn gvar() -{ - var n, a, k; - next(); - while (1) { - n = name(); - if (accept(s_lk)) { - k = cexpr(); expect(s_rk); - addsym(n, K_GARR, here); - while (k) { emit(0); k--; } - } else { - a = here; - addsym(n, K_GVAR, a); - emit(0); - if (accept(s_eq)) { - if (tk == T_STR) { *(a + hdelta) = here; k = tv; while (*k) { emit(*k); k++; } emit(0); next(); } - else *(a + hdelta) = cexpr(); - } - } - if (!accept(s_comma)) break; - } - expect(s_semi); - return 0; -} -fn gconst() -{ - var n; - next(); - while (1) { n = name(); expect(s_eq); addsym(n, K_CONST, cexpr()); if (!accept(s_comma)) break; } - expect(s_semi); - return 0; -} -fn topitem() -{ - var chk = 1, imm = 0, s, k; - 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; } - if (is_id(s_fn)) { - s = fndef(chk); - if (imm) { - if (hdelta) die("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; - } - if (!chk || imm) die("expected fn"); - if (is_id(s_var)) return gvar(); - if (is_id(s_const)) 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; } - } - die("expected fn, var or const"); - return 0; -} -fn unit_top() { while (tk != T_EOF) topitem(); return 0; } -fn unit_end(from) -{ - var i = from; - while (i < nsym) { - if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i]) dien(sname[i], "is used but never defined"); - i++; - } - 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. -unchecked fn compile_list(m q, top) -{ - var hop = -1, entry = 0, from = nsym; - ctx_push(); - if (infn) { hop = hole(); op(O_JMP); } - src_push(1, q, 0, 0); - next(); - if (top) unit_top(); - else { - entry = here; - fn_begin(1, 0); - prologue(); - 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; -} -fn compile(m q) { return compile_list(q, 0); } -fn load(m q) { compile_list(q, 1); return 0; } -fn eval(m q) { var f = compile_list(q, 0); return f(); } -fn evals(s) { var q = tokens(s), r = eval(q); free(q as d); return r; } - -// ---- symbol blob ---- -// an image ends with its own symbol table so the genome knows its names: -// kind, value, signature, name chars, 0 ... then a 0 kind. -fn blob_emit(from) -{ - var i = from, p; - while (i < nsym) { - if (skind[i] >= K_FN && skind[i] <= K_CONST) { - emit(skind[i]); emit(sval[i]); emit(ssig[i]); - p = sname[i]; - while (*p) { emit(*p); p++; } - emit(0); - } - i++; - } - emit(0); - return 0; -} -fn blob_load(p) -{ - var s, n; - while (*p) { - n = intern(p + 3); - s = addsym(n, p[0], p[1]); - ssig[s] = p[2]; - sflag[s] = F_DEF | F_SIG; - p = p + 3 + strlen(p + 3) + 1; - } - return 0; -} -// compile the rest of stdin as a genome image that will run at org, writing -// it to dst. only the source's own names are visible while it compiles. -// returns the image length in words. -fn build_image(org, dst) -{ - var sh = here, sd = hdelta, sl = hlimit, sub = ubase, svl = vislo, sns = nsym, n; - ubase = nsym; vislo = nsym; - here = org; hdelta = dst - org; hlimit = org + 0x30000; - lit(FTOP); lit(ZFP); op(O_ST); fnref(fwdfn(s_start)); op(O_JMP); emit(0); emit(0); - src_push(0, 0, 0, 0); - next(); - unit_top(); - unit_end(ubase); - src_pop(); - *(dst + 5) = here; - blob_emit(ubase); - *(dst + 6) = here; - n = here - org; - popsyms(sns); - ubase = sub; vislo = svl; here = sh; hdelta = sd; hlimit = sl; - return n; -} - -// ---- setup ---- -fn gc_roots() -{ - markrange(0, ptop); - if (!hdelta && here > ptop) markrange(ptop, here); // globals of a unit still compiling - markrange(GORG, GORG[6]); - markrange(R_SRC, R_SRC + 400); - markrange(*ZFP, FTOP); - markrange(DSLO, DSHI); - return 0; -} -fn zc_init() -{ - pooltop = R_POOL; - wfill(R_IH, 0, IHN); - nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0; - lsp = 0; ctxsp = 0; snaptop = 0; nloc = 0; lbase = 0; - s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}"); - s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";"); - s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--"); - s_star = intern("*"); s_amp = intern("&"); s_colon = intern(":"); s_hash = intern("#"); - s_minus = intern("-"); s_andand = intern("&&"); s_oror = intern("||"); - s_fn = intern("fn"); s_var = intern("var"); s_const = intern("const"); s_else = intern("else"); - s_as = intern("as"); s_unchecked = intern("unchecked"); s_immediate = intern("immediate"); - s_m = intern("m"); s_d = intern("d"); s_u = intern("u"); - s_start = intern("start"); s_main = intern("main"); s_free = intern("free"); - s_own = intern("own"); s_qnew = intern("qnew"); - return 0; -} -// operators and keywords. these are plain tables in memory: a program can -// add operators, change precedences or replace keywords with its own. -fn zc_tables() -{ - add_unop("!", "0 $ 0 ~ - | 31 > 1 +"); // index 0 - add_unop("!!", "0 $ 0 ~ - | 31 > 0 ~ -"); // index 1: normalize to 0 or 1 - add_unop("~", "-1 ^"); // index 2 - add_unop("-", "0 ~ -"); - add_binop("*", 10, "*"); // the first few indexes are used by cexpr - add_binop("/", 10, "zdiv c"); - add_binop("%", 10, "zmod c"); - add_binop("+", 9, "+"); // 3 - add_binop("-", 9, "-"); // 4 - add_binop("<<", 8, "zshl c"); // 5 - add_binop(">>", 8, ">"); // 6 - add_binop("<", 7, "<"); - add_binop(">", 7, "~ <"); - add_binop("<=", 7, "~ < 1 ^"); - add_binop(">=", 7, "< 1 ^"); - add_binop("==", 6, "- 0 $ 0 ~ - | 31 > 1 +"); - add_binop("!=", 6, "- 0 $ 0 ~ - | 31 > 0 ~ -"); - add_binop("&", 5, "&"); // 13 - add_binop("^", 4, "^"); - add_binop("|", 3, "|"); // 15 - add_binop("&&", -1, 0); - add_binop("||", -2, 0); - addkop(s_eq); addkop(s_arrow); addkop(s_inc); addkop(s_dec); - add_kw("var", kw_var, F_STMT); - add_kw("if", kw_if, F_STMT); - add_kw("while", kw_while, F_STMT); - add_kw("for", kw_for, F_STMT); - add_kw("break", kw_break, F_STMT); - add_kw("continue", kw_continue, F_STMT); - add_kw("return", kw_return, F_STMT); - add_kw("unchecked", kw_unchecked, F_STMT); - add_kw("asm", kw_asm, F_STMT | F_EXPR); - add_kw("quote", kw_quote, F_EXPR); - return 0; -} -fn start() -{ - var s, f; - heap_init(); - zc_init(); - blob_load(GORG[5]); - zc_tables(); - here = 0; hdelta = 0; hlimit = HEAP_LO; ptop = 0; - ubase = nsym; vislo = 0; - src_push(0, 0, 0, 0); - next(); - unit_top(); - unit_end(ubase); - if (here > ptop) ptop = here; - s = lookup(s_main); - if (s >= 0 && skind[s] == K_FN && (sflag[s] & F_DEF)) { f = sval[s]; f(); } - halt(); - return 0; -} |
