/* zc0: stage 0 of the bootstrap. a small c compiler for the subset of zc that compiler.zc is written in. it reads zc source on stdin and writes a genome header (vm words placed at GORG) on stdout, in the same image layout the resident compiler's image mode produces. no lifetime analysis, no quote, no parse words, no block expressions: those only exist in compiler.zc. tags (m, d, -> u) and casts (as u, as d) are parsed and recorded in signatures but not checked. */ #include #include #include #include #include #ifdef _WIN32 #include #include #endif #include "memmap.h" #include "fops.h" #ifndef MAXW #define MAXW 0x80000 #endif enum { T_EOF, T_NUM, T_ID, T_STR, T_OP, T_FLT, T_BSTR }; enum { K_FN = 1, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE, K_DOC }; enum { O_Q, O_LD, O_ST, O_PICK, O_DROP, O_SWAP, O_CALL, O_JMP, O_JZ, O_RET, O_IN, O_EMPTY, O_OUT, O_ADD, O_SUB, O_MUL, O_AND, O_OR, O_XOR, O_SHR, O_LT }; static const char *mn = "q@!$_~cjzr,?.+-*&|^><%"; static char module_name[64]; static char *src; static int pos, line = 1, tstart, hdr_start; /* the signature of the function being defined: its header's source text, for the language server (ZC0DOCS) */ static char sgtext[400], sgname[80]; static int tk, glue; static int64_t tv; static char ts[4096]; /* current token */ static int xf, xs, lvi; /* float, byte string; the plain local it came from, or -1 */ static int64_t *img; static int imgcap, here = GORG; static void emit(int64_t w) { if (!img || here - GORG >= imgcap) { int capacity; int64_t *next; if (imgcap > (0x7fffffff - GORG) / 2) { fprintf(stderr, "zc0: image address overflow\n"); exit(1); } capacity = imgcap ? imgcap * 2 : MAXW; if (capacity <= imgcap || capacity > 0x7fffffff - GORG) { fprintf(stderr, "zc0: image address overflow\n"); exit(1); } next = realloc(img, (size_t)capacity * sizeof *img); if (!next) { fprintf(stderr, "zc0: cannot grow image buffer\n"); exit(1); } img = next; imgcap = capacity; } img[here++ - GORG] = w; } static void lit(int64_t v) { if ((v >> 62) != 0 && (v >> 62) != -1) { emit((v >> 32) * 2 + 1); emit(0x100000000LL * 2 + 1); emit(O_MUL * 2); emit((int64_t)((uint64_t)v & 0xffffffffu) * 2 + 1); emit(O_OR * 2); } else emit(v * 2 + 1); } static void op(int k) { emit((int64_t)k * 2); } static void setlit(int at, int64_t v) { img[at - GORG] = v * 2 + 1; } static int pguess, pbad; /* inside a speculative type parse: an error is noted instead of stopping */ static void die(const char *m) { if (pguess) { pbad = 1; return; } fprintf(stderr, "zc0: line %d: %s (at '%s')\n", line, m, ts); exit(1); } /* ---- lexer ---- */ static const char *ops3[] = { "<<=", ">>=", 0 }; static const char *ops2[] = { "==", "!=", "<=", ">=", "<<", ">>", "&&", "||", "++", "--", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "->", 0 }; static int esc(void) { int c = src[pos++]; if (c != '\\') return c; c = src[pos++]; switch (c) { case 'n': return 10; case 't': return 9; case 'r': return 13; case '0': return 0; case 'e': return 27; } return c; } /* doc comments, as the self-hosted compiler reads them with docs on: the // lines directly above a declaration, up to a blank line or any other text */ static char dbuf[2100], dsnap[2100], dnm[64]; static int dn, dlast, dline, dsn, docpending; static void doccomment(void) /* pos is on the first slash */ { int cl = line, k = pos - 1, bol; while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--; bol = k < 0 || src[k] == '\n'; pos += 2; if (src[pos] == ':') { dn = 0; dlast = cl; docpending = 0; while (src[pos] && src[pos] != '\n') pos++; return; } if (!bol) { while (src[pos] && src[pos] != '\n') pos++; return; } if (dn && dlast != cl - 1) dn = 0; if (src[pos] == ' ') pos++; while (src[pos] && src[pos] != '\n') { if (dn < 2040) dbuf[dn++] = src[pos]; pos++; } while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r')) dn--; dbuf[dn++] = '\n'; dlast = cl; docpending = 1; } /* block comments may document a declaration on the same line */ static void docblock(void) { int cl = line, k = pos - 1, bol, start = 1, c; while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--; bol = k < 0 || src[k] == '\n'; if (!bol || (dn && dlast != cl - 1)) dn = 0; pos += 2; if (src[pos] == '*') { pos++; if (src[pos] == '/') { pos++; return; } } while (src[pos]) { if (start) { while (src[pos] == ' ' || src[pos] == '\t') pos++; if (src[pos] == '*') { pos++; if (src[pos] == '/') { pos++; break; } if (src[pos] == ' ') pos++; } start = 0; } c = src[pos++]; if (c == '*' && src[pos] == '/') { pos++; break; } if (dn < 2040) dbuf[dn++] = (char)c; if (c == '\n') { line++; start = 1; } } while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r' || dbuf[dn - 1] == '\n')) dn--; dbuf[dn++] = '\n'; dlast = line; docpending = 1; } static void next(void) { int ws = 0, i; for (;;) { while (src[pos] && isspace((unsigned char)src[pos])) { if (src[pos] == '\n') line++; pos++; ws = 1; } if (src[pos] == '/' && src[pos + 1] == '/') { doccomment(); ws = 1; continue; } if (src[pos] == '/' && src[pos + 1] == '*') { docblock(); ws = 1; continue; } break; } glue = !ws; tstart = pos; if (docpending) { dline = 0; if (dn && (line == dlast || line == dlast + 1)) dline = line; docpending = 0; } if (!src[pos] || src[pos] == '`') { tk = T_EOF; strcpy(ts, ""); return; } if (isdigit((unsigned char)src[pos]) || (src[pos] == '.' && isdigit((unsigned char)src[pos + 1]))) { int p = pos, fl = 0; if (src[pos] == '0' && (src[pos + 1] == 'x' || src[pos + 1] == 'X')) { char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } if (src[p] == '.') { int nch = (unsigned char)src[p + 1]; if (isdigit(nch) || nch == 'e' || nch == 'E' || !(isalpha(nch) || nch == '_')) fl = 1; } else { while (isdigit((unsigned char)src[p])) p++; if (src[p] == '.') { int nch = (unsigned char)src[p + 1]; /* 1.5 and 1. and 1.e2 are floats. 1.field is an integer and a field. */ if (isdigit(nch) || nch == 'e' || nch == 'E' || !(isalpha(nch) || nch == '_')) fl = 1; } } if (!fl && (src[p] == 'e' || src[p] == 'E')) { int q = p + 1; if (src[q] == '+' || src[q] == '-') q++; if (isdigit((unsigned char)src[q])) fl = 1; } if (fl) { char b[80]; int n = 0, frac = 0; while (src[pos] == '.' || isdigit((unsigned char)src[pos])) { if (src[pos] == '.') { if (frac) break; frac = 1; } if (n < 78) b[n++] = src[pos]; pos++; } if (src[pos] == 'e' || src[pos] == 'E') { int q = pos + 1; if (src[q] == '+' || src[q] == '-') q++; if (isdigit((unsigned char)src[q])) { if (n < 78) b[n++] = src[pos++]; if (src[pos] == '+' || src[pos] == '-') { if (n < 78) b[n++] = src[pos++]; } while (isdigit((unsigned char)src[pos]) && n < 78) b[n++] = src[pos++]; } } b[n] = 0; memcpy(ts, b, (size_t)n + 1); tv = (int64_t)fop_parse(b); tk = T_FLT; return; } { char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } } if (isalpha((unsigned char)src[pos]) || src[pos] == '_') { i = 0; while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; ts[i] = 0; if (i == 1 && ts[0] == 'b' && src[pos] == '"') { pos++; i = 0; while (src[pos] && src[pos] != '"' && i < 4095) ts[i++] = (char)esc(); ts[i] = 0; if (src[pos] == '"') pos++; tk = T_BSTR; return; } if (src[pos] == '.' && (!strcmp(ts,"rt") || !strcmp(ts,"run") || !strcmp(ts,"core") || !strcmp(ts,"lsp") || !strcmp(ts,"std") || !strcmp(ts,"icon"))) { ts[i++] = src[pos++]; while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; ts[i] = 0; } tk = T_ID; return; } if (src[pos] == '\'') { pos++; tv = esc(); if (src[pos++] != '\'') die("bad char literal"); tk = T_NUM; strcpy(ts, "'c'"); return; } if (src[pos] == '"') { pos++; i = 0; while (src[pos] && src[pos] != '"') ts[i++] = (char)esc(); ts[i] = 0; pos++; tk = T_STR; return; } tk = T_OP; for (i = 0; ops3[i]; i++) if (!strncmp(src + pos, ops3[i], 3)) { strcpy(ts, ops3[i]); pos += 3; return; } for (i = 0; ops2[i]; i++) if (!strncmp(src + pos, ops2[i], 2)) { strcpy(ts, ops2[i]); pos += 2; return; } ts[0] = src[pos++]; ts[1] = 0; } typedef struct { char *src; int pos, line, tk, glue; int64_t tv; char ts[4096]; int xf, xs; } Lex; static void lexsave(Lex *L) { L->src = src; L->pos = pos; L->line = line; L->tk = tk; L->glue = glue; L->tv = tv; memcpy(L->ts, ts, sizeof ts); L->xf = xf; L->xs = xs; } static void lexrest(Lex *L) { src = L->src; pos = L->pos; line = L->line; tk = L->tk; glue = L->glue; tv = L->tv; memcpy(ts, L->ts, sizeof ts); xf = L->xf; xs = L->xs; } static int is(const char *s) { return (tk == T_OP || tk == T_ID) && !strcmp(ts, s); } static int accept(const char *s) { if (is(s)) { next(); return 1; } return 0; } static void expect(const char *s) { if (!accept(s)) { char b[64]; sprintf(b, "expected '%s'", s); die(b); } } static char *ident(void) { static char b[8][256]; static int k; char *r = b[k++ & 7]; if (tk != T_ID) die("expected a name"); strcpy(r, ts); next(); return r; } /* ---- symbols ---- */ typedef struct { char name[64]; int kind, sig, defd; int64_t val; } Sym; static Sym sy[16384]; static int nsy; typedef struct { int at, sym; } Fix; static Fix fx[65536]; static int nfx; static Sym *find_raw(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; } static void qualify(char *dst, size_t size, const char *name) { if (!module_name[0] || strchr(name, '.')) snprintf(dst, size, "%s", name); else if (!strncmp(name, "doc ", 4) || !strncmp(name, "sig ", 4)) snprintf(dst, size, "%.4s%s.%s", name, module_name, name + 4); else snprintf(dst, size, "%s.%s", module_name, name); } static Sym *find(const char *name) { char full[256]; Sym *s; qualify(full, sizeof full, name); s = find_raw(full); if (s) return s; s = find_raw(name); if (s || strchr(name, '.')) return s; const char *modules[] = { "rt", "run", "core", "lsp", 0 }; for (int i = 0; modules[i]; i++) { snprintf(full,sizeof full,"%s.%s",modules[i],name); s=find_raw(full); if(s) return s; } return 0; } static Sym *add(const char *n, int kind, int64_t val) { char full[256]; qualify(full, sizeof full, n); n = full; Sym *s = find_raw(n); if (s && s->kind == K_FN && kind == K_FN && !s->defd) { s->val = val; return s; } /* prototype or forward use */ s = &sy[nsy++]; strncpy(s->name, n, 63); s->kind = kind; s->val = val; s->defd = 1; s->sig = 0; return s; } static void fnref(const char *n) /* push a function address, fixing it up later if needed */ { Sym *s = find(n); if (!s) { s = add(n, K_FN, 0); s->defd = 0; } if (s->kind != K_FN) die("not a function"); if (s->defd) { lit(s->val); return; } fx[nfx].at = here; fx[nfx++].sym = (int)(s - sy); lit(0); } /* ---- locals ---- */ typedef struct { char name[64]; int slot, arr, flt, str; } Loc; static Loc lo[1024]; static int nlo, nslot, infn; static Loc *findl(const char *n) { int i; for (i = nlo; i--;) if (!strcmp(lo[i].name, n)) return &lo[i]; return 0; } static void laddr(int k) { lit(ZFP); op(O_LD); if (k) { lit(k); op(O_ADD); } } /* ---- operator tables ---- */ static void seq_isz(void) { lit(0); op(O_PICK); lit(0); op(O_SWAP); op(O_SUB); op(O_OR); lit(63); op(O_SHR); lit(1); op(O_ADD); } /* ±0 is false. a float becomes 0 or 1 before the integer test. */ static void ftest(void) { if (!xf) return; emit(1200); emit(0); op(605); lit(1); op(18); xf = 0; } static void seq_nz(void) { lit(0); op(O_PICK); lit(0); op(O_SWAP); op(O_SUB); op(O_OR); lit(63); op(O_SHR); lit(0); op(O_SWAP); op(O_SUB); } static int binprec(void) { static const char *n[] = { "*", "/", "%", "+", "-", "<<", ">>", "<", ">", "<=", ">=", "==", "!=", "&", "^", "|", "&&", "||", 0 }; static int p[] = { 10, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6, 5, 4, 3, 2, 1 }; int i; if (tk != T_OP) return 0; for (i = 0; n[i]; i++) if (!strcmp(ts, n[i])) return p[i]; return 0; } static int fis(const char *o) { return !strcmp(o, "+") || !strcmp(o, "-") || !strcmp(o, "*") || !strcmp(o, "/") || !strcmp(o, "<") || !strcmp(o, ">") || !strcmp(o, "<=") || !strcmp(o, ">=") || !strcmp(o, "==") || !strcmp(o, "!="); } static void fbinop(const char *o, int lf, int rf) { if (!lf) { op(O_SWAP); op(608); op(O_SWAP); } if (!rf) op(608); if (!strcmp(o, "+")) { op(601); xf = 1; } else if (!strcmp(o, "-")) { op(602); xf = 1; } else if (!strcmp(o, "*")) { op(603); xf = 1; } else if (!strcmp(o, "/")) { op(604); xf = 1; } else if (!strcmp(o, "==")) { op(605); xf = 0; } else if (!strcmp(o, "!=")) { op(605); lit(1); op(18); xf = 0; } else if (!strcmp(o, "<")) { op(606); xf = 0; } else if (!strcmp(o, ">")) { op(O_SWAP); op(606); xf = 0; } else if (!strcmp(o, "<=")) { op(607); xf = 0; } else if (!strcmp(o, ">=")) { op(O_SWAP); op(607); xf = 0; } else xf = 0; } static int sis(const char *o) { return !strcmp(o, "+") || !strcmp(o, "<") || !strcmp(o, ">") || !strcmp(o, "<=") || !strcmp(o, ">=") || !strcmp(o, "==") || !strcmp(o, "!="); } static void sbinop(const char *o) { if (!strcmp(o, "+")) { fnref("cat_s"); op(O_CALL); xs = 1; } else if (!strcmp(o, "==")) { fnref("eq_s"); op(O_CALL); xs = 0; } else if (!strcmp(o, "!=")) { fnref("eq_s"); op(O_CALL); lit(1); op(O_XOR); xs = 0; } else if (!strcmp(o, "<")) { fnref("lt_s"); op(O_CALL); xs = 0; } else if (!strcmp(o, ">")) { op(O_SWAP); fnref("lt_s"); op(O_CALL); xs = 0; } else if (!strcmp(o, "<=")) { fnref("le_s"); op(O_CALL); xs = 0; } else { op(O_SWAP); fnref("le_s"); op(O_CALL); xs = 0; } xf = 0; } static void binop(const char *o) { if (!strcmp(o, "*")) op(O_MUL); else if (!strcmp(o, "/")) { fnref("zdiv"); op(O_CALL); } else if (!strcmp(o, "%")) { fnref("zmod"); op(O_CALL); } else if (!strcmp(o, "+")) op(O_ADD); else if (!strcmp(o, "-")) op(O_SUB); else if (!strcmp(o, "<<")) { fnref("zshl"); op(O_CALL); } else if (!strcmp(o, ">>")) op(O_SHR); else if (!strcmp(o, "<")) op(O_LT); else if (!strcmp(o, ">")) { op(O_SWAP); op(O_LT); } else if (!strcmp(o, "<=")) { op(O_SWAP); op(O_LT); lit(1); op(O_XOR); } else if (!strcmp(o, ">=")) { op(O_LT); lit(1); op(O_XOR); } else if (!strcmp(o, "==")) { op(O_SUB); seq_isz(); } else if (!strcmp(o, "!=")) { op(O_SUB); seq_nz(); } else if (!strcmp(o, "&")) op(O_AND); else if (!strcmp(o, "^")) op(O_XOR); else if (!strcmp(o, "|")) op(O_OR); else die("bad operator"); } /* ---- constant expressions (globals, array sizes, const) ---- */ static int64_t cexpr(void); static int64_t cprim(void) { int64_t v; Sym *s; if (tk == T_NUM) { v = tv; next(); return v; } if (accept("-")) return -cprim(); if (accept("~")) return ~cprim(); if (accept("(")) { v = cexpr(); expect(")"); return v; } if (tk == T_ID && (s = find(ts)) && (s->kind == K_CONST || (s->kind != K_GVAR && s->defd))) { next(); return s->val; } die("constant expected"); return 0; } static int64_t cexpr(void) { int64_t v = cprim(); for (;;) { if (accept("+")) v += cprim(); else if (accept("-")) v -= cprim(); else if (accept("*")) v *= cprim(); else if (accept("|")) v |= cprim(); else if (accept("&")) v &= cprim(); else if (accept("<<")) v <<= cprim(); else if (accept(">>")) v >>= cprim(); else return v; } } /* ---- types ---- zc0 has no types: a type is read and thrown away. all it has to know is the shape, so that T[n] still reserves n words and the rest reserve one. the grammar is the compiler's own (see ptype in compiler.zc): ptype a type, then any number of (params): int(int, int) is a function type ptype0 a type with its stars: int*, vec*, point** ptype1 a base: T[n], T[?], fixed T[?], fn, a name, or a generic instance each returns the words a variable of the type takes: n for a fixed array, 1 for a growable handle or anything scalar. */ static int ptype0(void); static int ptype(void); static int ptype1(void) { int n, e; if (is("[")) { die("array types are postfix: T[N], T[] or T[?]"); return 1; } if (is("fixed")) { next(); return ptype0(); } if (is("fn")) { next(); return 1; } /* fn(int): the parameters follow, in ptype */ if (tk != T_ID) { die("a type name"); return 1; } ident(); if (accept("<")) { /* generic arguments: vec */ do { ptype(); } while (accept(",")); if (is(">>")) ts[1] = 0; /* >> closes two lists: leave the second > */ else expect(">"); } return 1; } static int ptype0(void) { int n = ptype1(); while (is("*") || is("[")) { if (accept("*")) n = 1; else { int count = 0, cap = 8, *dims = malloc(cap * sizeof(int)); if (!dims) die("cannot allocate array dimensions"); while (accept("[")) { int length; if (accept("?")) { expect("]"); length = -2; } else if (accept("]")) length = -1; else { length = (int)cexpr(); expect("]"); } if (count == cap) { cap *= 2; int *more = realloc(dims, cap * sizeof(int)); if (!more) die("cannot grow array dimensions"); dims = more; } dims[count++] = length; } while (count) { int length = dims[--count]; if (length == -2) n = 1; else n = length < 0 ? 0 : n * length; } free(dims); } } return n; } static int ptype(void) { int n = ptype0(); while (accept("(")) { if (!accept(")")) { do { ptype(); if (accept(".")) ident(); } while (accept(",")); expect(")"); } n = 1; } return n; } /* the ownership tag after a type, .u .m or .d: 0 when there is none */ static int ptag; static void ptagopt(void) { ptag = 0; if (!accept(".")) return; if (is("m")) ptag = 1; else if (is("d")) ptag = 2; else if (is("u")) ptag = 3; else die("a tag is .u, .m or .d"); next(); } /* an optional type in front of a name. the words it needs, or -1 when there is no type here at all, which is how an untyped name is told from a type. the parse is taken back when what follows is not a name. */ /* words that open a statement or an expression, so a type never starts with one of them. they are all plain identifiers to the lexer. */ static int notatype(void) { static const char *w[] = { "return", "if", "else", "while", "for", "do", "break", "continue", "let", "var", "fn", "const", "enhance", "struct", "enum", "asm", "switch", "case", "default", "immediate", "type", "include", "as", "in", "new", "sizeof", "typeof", "o3", "unchecked", 0 }; int i; if (tk != T_ID) return 0; for (i = 0; w[i]; i++) if (!strcmp(ts, w[i])) return 1; return 0; } static int optype(void) { Lex L; int n; if (notatype() || (tk != T_ID && !is("["))) return -1; lexsave(&L); pguess = 1; pbad = 0; n = ptype(); ptagopt(); pguess = 0; if (tk == T_ID && !pbad) return n; lexrest(&L); return -1; } /* a name followed by ( : a function definition rather than a variable */ static int isfn(void) { Lex L; int r; lexsave(&L); pguess = 1; r = tk == T_ID; if (r) { ident(); r = is("("); } pguess = 0; lexrest(&L); return r; } /* ---- expressions ---- */ static int lv; /* the stack top is an address not yet loaded */ static void rval(void) { if (lv) { op(O_LD); lv = 0; } } static void expr(void); static void strlit(void) /* inline word string: jump over it, push its address */ { int j = here, a, i, n = (int)strlen(ts); lit(0); op(O_JMP); a = here; for (i = 0; i < n; i++) emit((unsigned char)ts[i]); emit(0); setlit(j, here); lit(a); next(); } /* b"...": length word, then bytes packed 8 per word, little-endian, plus a 0 */ static void bstrlit(void) { int j = here, a, i = 0, k, n = (int)strlen(ts); uint64_t w; lit(0); op(O_JMP); a = here; emit(n); while (i < n) { w = 0; k = 0; while (k < 8 && i < n) { w |= (uint64_t)(unsigned char)ts[i] << (k * 8); k++; i++; } emit((int64_t)w); } if ((n & 7) == 0) emit(0); setlit(j, here); lit(a); next(); } static int returns_bstr(const char *n) { if (!strncmp(n, "rt.", 3)) n += 3; return !strcmp(n, "cat_s") || !strcmp(n, "slice_s") || !strcmp(n, "n_to_s") || !strcmp(n, "new_s"); } static int args(void) { int n = 0; expect("("); if (!accept(")")) { do { expr(); rval(); n++; } while (accept(",")); expect(")"); } return n; } static void asmblock(void); static void primary(void) { char n[64]; Loc *l; Sym *s; lv = 0; lvi = -1; xf = 0; xs = 0; if (tk == T_FLT) { emit(1200); emit(tv); xf = 1; next(); return; } if (tk == T_NUM) { lit(tv); next(); return; } if (tk == T_BSTR) { bstrlit(); xs = 1; return; } if (tk == T_STR) { strlit(); return; } if (accept("(")) { expr(); expect(")"); return; } if (is("asm")) { next(); asmblock(); return; } strcpy(n, ident()); if ((l = findl(n))) { laddr(l->slot); lv = !l->arr; if (!l->arr) { xf = l->flt; xs = l->str; lvi = (int)(l - lo); } return; } s = find(n); if (s && s->kind == K_CONST) { lit(s->val); return; } if (s && s->kind == K_GVAR) { lit(s->val); lv = 1; return; } if (s && s->kind == K_GARR) { lit(s->val); return; } if (is("(")) { int na = args(); if (s && s->kind == K_ENH) { if (na != (s->sig & 15)) die("wrong number of enhancement inputs"); op(22 + (int)s->val); if (((s->sig >> 4) & 15) == 0) lit(0); else if (((s->sig >> 4) & 15) != 1) die("multiple enhancement outputs need asm"); xs = 0; xf = 0; return; } if (!strcmp(n, "put_s") && na == 1 && xs) fnref("put_bs"); else if (!strcmp(n, "len")) fnref("len_s"); else fnref(n); op(O_CALL); xs = returns_bstr(n); xf = 0; return; } fnref(n); } static void incdec(int post, int d) { if (!lv) die("++/-- needs an lvalue"); lv = 0; if (lvi >= 0) lo[lvi].str = 0; xs = 0; if (post) { lit(0); op(O_PICK); op(O_LD); lit(0); op(O_PICK); lit(d); op(O_ADD); lit(2); op(O_PICK); op(O_ST); op(O_SWAP); op(O_DROP); } else { lit(0); op(O_PICK); op(O_LD); lit(d); op(O_ADD); lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_ST); op(O_SWAP); op(O_DROP); } } static void postfix(void) { int n, was; primary(); for (;;) { if (accept("[")) { was = xs; rval(); expr(); rval(); if (was && accept(":")) { expr(); rval(); expect("]"); fnref("slice_s"); op(O_CALL); lv = 0; xs = 1; xf = 0; lvi = -1; } else if (was) { expect("]"); fnref("at_s"); op(O_CALL); lv = 0; xs = 0; xf = 0; lvi = -1; } else { expect("]"); op(O_ADD); lv = 1; xs = 0; xf = 0; lvi = -1; } } else if (is("(")) { rval(); n = args(); lit(n); op(O_PICK); op(O_CALL); op(O_SWAP); op(O_DROP); xf = 0; xs = 0; lvi = -1; } else if (accept("++")) incdec(1, 1); else if (accept("--")) incdec(1, -1); else if (accept("as")) ident(); else return; } } static void unary(void) { if (accept("-")) { unary(); rval(); xs = 0; if (xf) { emit(1200); emit(0); op(O_SWAP); op(602); } else { lit(0); op(O_SWAP); op(O_SUB); } } else if (accept("!")) { unary(); rval(); ftest(); seq_isz(); xf = 0; xs = 0; } else if (accept("~")) { unary(); rval(); lit(-1); op(O_XOR); xf = 0; xs = 0; } else if (accept("*")) { unary(); rval(); lv = 1; xs = 0; } else if (accept("&")) { unary(); if (!lv) die("& needs an lvalue"); lv = 0; xs = 0; } else if (accept("++")) { unary(); incdec(0, 1); } else if (accept("--")) { unary(); incdec(0, -1); } else if (tk == T_ID && (!strcmp(ts, "u") || !strcmp(ts, "m") || !strcmp(ts, "d"))) { next(); expect("*"); unary(); } /* u*x: a recast, no code */ else postfix(); } static void bin(int minp) { int p, a, b, lf, rf, ls, rs; char o[8]; unary(); while ((p = binprec()) && p >= minp) { lf = xf; ls = xs; strcpy(o, ts); next(); rval(); if (!strcmp(o, "&&")) { ftest(); a = here; lit(0); op(O_JZ); bin(p + 1); rval(); ftest(); seq_nz(); b = here; lit(0); op(O_JMP); setlit(a, here); lit(0); setlit(b, here); xf = 0; xs = 0; } else if (!strcmp(o, "||")) { ftest(); a = here; lit(0); op(O_JZ); lit(1); b = here; lit(0); op(O_JMP); setlit(a, here); bin(p + 1); rval(); ftest(); seq_nz(); setlit(b, here); xf = 0; xs = 0; } else { bin(p + 1); rval(); rf = xf; rs = xs; if ((ls || rs) && sis(o)) { if (!(ls && rs)) die("a byte string needs a byte string on the other side"); sbinop(o); } else if ((lf || rf) && fis(o)) { fbinop(o, lf, rf); xs = 0; } else { binop(o); xf = 0; xs = 0; } } lv = 0; lvi = -1; } } static void expr(void) { char o[8]; int dst, lf; bin(1); dst = lvi; if (is("=")) { if (!lv) die("assignment needs an lvalue"); next(); expr(); rval(); op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); op(O_ST); lv = 0; if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = xs; } } else if (tk == T_OP && strlen(ts) >= 2 && ts[strlen(ts) - 1] == '=' && strcmp(ts, "==") && strcmp(ts, "!=") && strcmp(ts, "<=") && strcmp(ts, ">=")) { if (!lv) die("assignment needs an lvalue"); lf = xf; strcpy(o, ts); o[strlen(o) - 1] = 0; next(); lit(0); op(O_PICK); op(O_LD); lv = 0; expr(); rval(); if ((lf || xf) && fis(o)) fbinop(o, lf, xf); else { binop(o); xf = 0; } op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); op(O_ST); lv = 0; if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = 0; } } } /* ---- asm blocks: raw vm words ---- */ static void asmblock(void) { char ln[128][64], rn[256][64]; int la[128], nl = 0, ra[256], nr = 0, i, k; const char *c; Loc *l; Sym *s; expect("{"); while (!accept("}")) { if (tk == T_EOF) die("unterminated asm"); if (tk == T_FLT) { emit(1200); emit(tv); next(); continue; } if (tk == T_NUM) { lit(tv); next(); continue; } if (tk == T_BSTR) { bstrlit(); continue; } if (tk == T_STR) { strlit(); continue; } if (accept("#")) { if (tk != T_NUM) die("# needs a number"); emit(tv); next(); continue; } if (is("-")) { next(); if (tk == T_NUM && glue) { lit(-tv); next(); } else op(O_SUB); continue; } if (accept(":")) { strcpy(ln[nl], ident()); la[nl++] = here; continue; } if (strlen(ts) == 1 && (c = strchr(mn, ts[0]))) { op((int)(c - mn)); next(); continue; } if (tk != T_ID) die("bad asm token"); if ((l = findl(ts))) { laddr(l->slot); next(); continue; } if ((s = find(ts)) && s->kind != K_FN) { lit(s->val); next(); continue; } if (s) { fnref(ts); next(); continue; } strcpy(rn[nr], ts); ra[nr++] = here; lit(0); next(); /* a label of this block */ } for (i = 0; i < nr; i++) { for (k = -1; ++k < nl && strcmp(ln[k], rn[i]);) ; if (k == nl) die("unknown asm label"); setlit(ra[i], la[k]); } } /* ---- statements ---- */ static int brk[256][64], nbrk[256], cont[256], nloop; static int epi[1024], nepi; static void stmt(void); static void block(void) { int save = nlo; expect("{"); while (!accept("}")) { if (tk == T_EOF) die("missing }"); stmt(); } nlo = save; } static void jmp_to(int t) { lit(t); op(O_JMP); } static void loopbody(int contaddr) { nbrk[nloop] = 0; cont[nloop++] = contaddr; stmt(); nloop--; } static void patchbreaks(void) { int i; for (i = 0; i < nbrk[nloop]; i++) setlit(brk[nloop][i], here); } static void stmt(void) { int a, b, c, d; if (accept(";")) return; if (is("{")) { block(); return; } { int nw = optype(), n; if (nw >= 0 || accept("let") || accept("var")) { if (nw < 0) nw = 1; do { Loc *l = &lo[nlo++]; strcpy(l->name, ident()); l->arr = 0; if (nw > 1) { l->slot = nslot; nslot += nw; l->arr = 1; } else { if (accept("[")) { n = cexpr(); expect("]"); nslot += n; l->slot = nslot - n; l->arr = 1; } else l->slot = nslot++; } l->flt = 0; l->str = 0; if (accept("=")) { expr(); rval(); laddr(l->slot); op(O_ST); l->flt = xf; l->str = xs; } } while (accept(",")); expect(";"); return; } } if (accept("unchecked")) { stmt(); return; } if (accept("if")) { expect("("); expr(); rval(); ftest(); expect(")"); a = here; lit(0); op(O_JZ); stmt(); if (accept("else")) { b = here; lit(0); op(O_JMP); setlit(a, here); stmt(); setlit(b, here); } else setlit(a, here); return; } if (accept("while")) { c = here; expect("("); expr(); rval(); ftest(); expect(")"); a = here; lit(0); op(O_JZ); loopbody(c); jmp_to(c); setlit(a, here); patchbreaks(); return; } if (accept("for")) { int save = nlo; expect("("); if (!is(";")) { if (is("let") || is("var") || tk == T_ID || is("[")) { stmt(); goto cond; } expr(); rval(); op(O_DROP); } expect(";"); cond: c = here; if (!is(";")) { expr(); rval(); ftest(); } else { lit(1); xf = 0; } expect(";"); a = here; lit(0); op(O_JZ); b = here; lit(0); op(O_JMP); d = here; if (!is(")")) { expr(); rval(); op(O_DROP); } expect(")"); jmp_to(c); setlit(b, here); loopbody(d); jmp_to(d); setlit(a, here); patchbreaks(); nlo = save; return; } if (accept("break")) { if (!nloop) die("break outside a loop"); brk[nloop - 1][nbrk[nloop - 1]++] = here; lit(0); op(O_JMP); expect(";"); return; } if (accept("continue")) { if (!nloop) die("continue outside a loop"); jmp_to(cont[nloop - 1]); expect(";"); return; } if (accept("return")) { if (!is(";")) { expr(); rval(); } else lit(0); expect(";"); epi[nepi++] = here; lit(0); op(O_JMP); return; } if (is("asm")) { next(); asmblock(); return; } expr(); rval(); op(O_DROP); expect(";"); } /* ---- top level ---- */ static void fndef(int ret) { char *n = ident(); Sym *s; int np = 0, tags = 0, t, frame, i, helper, busy, done, check, node; if (dsn && !dnm[0]) strcpy(dnm, n); char pn[16][64]; char full_name[256]; qualify(full_name, sizeof full_name, n); s = find_raw(full_name); if (!s || s->kind != K_FN) { s = add(n, K_FN, 0); s->defd = 0; } expect("("); if (!accept(")")) { do { t = 0; ptag = 0; if (optype() >= 0) t = ptag == 1 ? 1 : ptag == 2 ? 2 : 0; /* the type is read and dropped */ else if (accept(".")) { /* .m name, .d name */ if (is("m")) t = 1; else if (is("d")) t = 2; else die("a parameter tag is .m or .d"); next(); } if (np < 12) tags += t << (2 * np); strcpy(pn[np++], ident()); } while (accept(",")); expect(")"); } s->sig = ret + 2 * np + 64 * tags; if (accept(";")) return; /* prototype */ if (s->defd) die("function defined twice"); s->val = here; s->defd = 1; #ifdef ZC0DOCS { /* the header is the source from the item's first token to the { that opens the body */ int hl = tstart - hdr_start; sgname[0] = 0; if (hl > 0 && hl < (int)sizeof sgtext - 1) { memcpy(sgtext, src + hdr_start, hl); sgtext[hl] = 0; while (hl && isspace((unsigned char)sgtext[hl - 1])) sgtext[--hl] = 0; strcpy(sgname, n); } } #endif nlo = 0; nslot = 0; nepi = 0; infn = 1; for (i = 0; i < np; i++) { strcpy(lo[nlo].name, pn[i]); lo[nlo].arr = 0; lo[nlo].flt = 0; lo[nlo].str = 0; lo[nlo++].slot = nslot++; } /* retain the caller's pointer until the frame has its final segment. */ helper = !find("frame_busy") || !strcmp(s->name, "rt.frame_grow") || !strcmp(s->name, "rt.frame_leave"); lit(ZFP); op(O_LD); if (!helper) { lit(0); op(O_PICK); } frame = here; lit(0); op(O_SUB); lit(ZFP); op(O_ST); if (!helper) { lit(find("frame_busy")->val); op(O_LD); busy = here; lit(0); op(O_JZ); done = here; lit(0); op(O_JMP); setlit(busy, here); lit(ZFP); op(O_LD); lit(find("frame_floor")->val); op(O_LD); lit(find("frame_guard")->val); op(O_LD); op(O_ADD); op(O_LT); check = here; lit(0); op(O_JZ); lit(ZFP); op(O_LD); lit(1); op(O_PICK); lit(ZFP); op(O_ST); fnref("frame_grow"); op(O_CALL); lit(ZFP); op(O_ST); busy = here; lit(0); op(O_JMP); setlit(check, here); setlit(done, here); op(O_DROP); setlit(busy, here); } for (i = np; i--;) { laddr(i); op(O_ST); } block(); lit(0); for (i = 0; i < nepi; i++) setlit(epi[i], here); lit(ZFP); op(O_LD); lit(nslot); op(O_ADD); lit(ZFP); op(O_ST); if (!helper) { lit(find("frame_node")->val); op(O_LD); node = here; lit(0); op(O_JZ); lit(ZFP); op(O_LD); lit(find("frame_top")->val); op(O_LD); op(O_SUB); check = here; lit(0); op(O_JZ); done = here; lit(0); op(O_JMP); setlit(check, here); fnref("frame_leave"); op(O_CALL); lit(ZFP); op(O_ST); setlit(done, here); setlit(node, here); } op(O_RET); setlit(frame, nslot); infn = 0; } /* enhance name(params) [-> n] micro "recipe"; or ... vm { asm }; emits the install descriptor; the booted genome installs it */ static int nenh; static void enhdef(void) { char *n, pn[8][64]; int np = 0, nout = 1, d, na, pay, len = 0, i, j; Sym *s; if (tk == T_NUM) { nout = tv; next(); } /* enhance 2 name(...) */ n = ident(); expect("("); if (!accept(")")) { do { if (np == 8) die("at most 8 inputs"); strcpy(pn[np++], ident()); } while (accept(",")); expect(")"); } d = here; for (i = 0; i < 7; i++) emit(0); na = here; for (i = 0; i < np; i++) emit(0); for (i = 0; i < np; i++) { img[na + i - GORG] = here; for (j = 0; pn[i][j]; j++) emit((unsigned char)pn[i][j]); emit(0); } if (accept("micro")) { pay = here; while (tk == T_STR) { for (j = 0; ts[j]; j++) emit((unsigned char)ts[j]); next(); } emit(0); } else if (accept("vm")) { pay = here; asmblock(); op(O_RET); len = here - pay; img[d + 1 - GORG] = 1; } else die("expected micro or vm"); img[d - GORG] = nenh; img[d + 2 - GORG] = np; img[d + 3 - GORG] = nout; img[d + 4 - GORG] = pay; img[d + 5 - GORG] = len; img[d + 6 - GORG] = na; s = add(n, K_ENH, nenh++); s->sig = np + nout * 16 + d * 256; expect(";"); } static void top1(void) { if (accept("namespace")) { if (is(";")) module_name[0] = 0; else { snprintf(module_name, sizeof module_name, "%s", ident()); } expect(";"); return; } if (accept("include")) { do { ident(); } while (accept(",")); expect(";"); return; } { if (accept("unchecked")) { top1(); return; } if (accept("fn")) { fndef(0); return; } if (accept(".")) { if (strcmp(ident(), "u")) die("the only return tag is .u"); fndef(1); return; } /* .u name(...) */ if (accept("const")) { optype(); do { char *n = ident(); if (dsn && !dnm[0]) strcpy(dnm, n); expect("="); add(n, K_CONST, cexpr()); } while (accept(",")); expect(";"); return; } if (accept("enhance")) { enhdef(); return; } { int nw, typed, k, a, rt; ptag = 0; nw = optype(); typed = nw >= 0; rt = ptag == 3; if (typed || accept("let") || accept("var")) { if (isfn()) { fndef(rt); return; } do { char *n = ident(); if (dsn && !dnm[0]) strcpy(dnm, n); k = 0; if (typed) k = nw > 1 ? nw : 0; else if (accept("[")) { k = (int)cexpr(); expect("]"); } if (k > 1) { a = here; while (k--) emit(0); add(n, K_GARR, a); } else { a = here; add(n, K_GVAR, a); emit(0); if (accept("=")) { if (tk == T_STR) { int i, l = (int)strlen(ts); img[a - GORG] = here; for (i = 0; i < l; i++) emit((unsigned char)ts[i]); emit(0); next(); } else img[a - GORG] = cexpr(); } } } while (accept(",")); expect(";"); return; } } die("expected fn, var or const"); } } /* section headings organize the source; they do not document a symbol. */ static int doc_section(const char *text, int length) { int start = 0, end, a, b; while (start < length) { end = start; while (end < length && text[end] != '\n') end++; a = start; b = end; while (a < b && isspace((unsigned char)text[a])) a++; while (b > a && isspace((unsigned char)text[b - 1])) b--; if (b - a >= 4 && text[a] == '-' && text[a + 1] == '-' && text[b - 1] == '-' && text[b - 2] == '-') return 1; start = end + 1; } return 0; } /* one item, with the doc comment above it kept under the name it declares */ static void top(void) { while (tk != T_EOF) { dsn = 0; dnm[0] = 0; #ifdef ZC0DOCS sgname[0] = 0; hdr_start = tstart; #endif if (dn && dline == line) { memcpy(dsnap, dbuf, dn); dsn = dn; dline = 0; } top1(); #ifdef ZC0DOCS /* enabled for images that carry editor documentation */ if (dsn && dnm[0] && !doc_section(dsnap, dsn)) { int j = here, a, i = 0, k, c; uint64_t w; char nm[80]; lit(0); op(O_JMP); a = here; emit(dsn); while (i < dsn) { w = 0; k = 0; while (k < 8 && i < dsn) { c = (unsigned char)dsnap[i]; if (c > 255) c = 63; w |= (uint64_t)c << (k * 8); k++; i++; } emit((int64_t)w); } if (!(dsn & 7)) emit(0); setlit(j, here); sprintf(nm, "doc %s", dnm); add(nm, K_DOC, a); } if (sgname[0]) { /* the signature, as the header's own text, under "sig name" */ int j = here, a, i = 0, k, c, sl = (int)strlen(sgtext); uint64_t w; char nm[96]; lit(0); op(O_JMP); a = here; emit(sl); while (i < sl) { w = 0; k = 0; while (k < 8 && i < sl) { c = (unsigned char)sgtext[i]; w |= (uint64_t)c << (k * 8); k++; i++; } emit((int64_t)w); } if (!(sl & 7)) emit(0); setlit(j, here); sprintf(nm, "sig %s", sgname); add(nm, K_DOC, a); } #endif dsn = 0; dnm[0] = 0; } } int main(int argc, char **argv) { if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) { fputs("usage: zc0 [--help|-h] < source.zc > genome.h\n" "compiles bootstrap source from stdin into a c genome header on stdout.\n" "source must define start(); memory layout is selected when zc0 is built.\n" "options:\n --help, -h show this help and exit\n", stdout); return 0; } if (argc != 1) { fputs("zc0: unexpected arguments; use --help for usage\n", stderr); return 2; } #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); #endif char *buf = malloc(65536); int capacity = 65536, n = 0, i, j; Sym *st; if (!buf) { fprintf(stderr, "zc0: cannot allocate source buffer\n"); return 1; } for (;;) { size_t got; if (n + 1 == capacity) { char *next; if (capacity > 0x7fffffff / 2) { fprintf(stderr, "zc0: source is too large\n"); return 1; } capacity *= 2; next = realloc(buf, (size_t)capacity); if (!next) { fprintf(stderr, "zc0: cannot grow source buffer\n"); return 1; } buf = next; } got = fread(buf + n, 1, (size_t)(capacity - n - 1), stdin); n += (int)got; if (!got) { if (ferror(stdin)) { fprintf(stderr, "zc0: source read failed\n"); return 1; } break; } } buf[n] = 0; src = buf; next(); /* image header: set the frame pointer, jump to start, then the symbol blob pointer and image end */ lit(FTOP); lit(ZFP); op(O_ST); fnref(strstr(src, "namespace run;") ? "run.start" : "start"); op(O_JMP); emit(0); emit(0); top(); for (i = 0; i < nfx; i++) { Sym *s = &sy[fx[i].sym]; if (!s->defd) { fprintf(stderr, "zc0: undefined function %s\n", s->name); return 1; } setlit(fx[i].at, s->val); } if (!(st = find("start")) || !st->defd) { fprintf(stderr, "zc0: no start\n"); return 1; } /* symbol blob: kind, value, sig, name chars, 0 ... then a 0 kind */ img[5] = here; for (i = 0; i < nsy; i++) { emit(sy[i].kind); emit(sy[i].val); emit(sy[i].sig); for (j = 0; sy[i].name[j]; j++) emit((unsigned char)sy[i].name[j]); emit(0); } emit(0); img[6] = here; printf("#define GORG %d\n#define GLEN %d\nstatic const int64_t genome[] = {", GORG, here - GORG); for (i = 0; i < here - GORG; i++) printf("%s%lld", !i ? "\n" : i % 12 ? "," : ",\n", (long long)img[i]); printf("\n};\n"); return 0; }