aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 09:48:30 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 09:48:30 +0000
commit4478d6d926e0d2ccb5c267f4b9d6648fa6bcc43a (patch)
tree8b28bb1e14eab791606cb7ec996a3ae8162fab1d
parenttodo: tick the parity runner; zc0 follow-ups it found (diff)
parentzc0: byte string types, u/m/d as names, enhancements in asm (diff)
Merge branch 'worktree-agent-a0734822346ae18db' into ccr-0054c02e-dfdvnd
-rw-r--r--README.md2
-rw-r--r--src/tools/zc0.c175
-rw-r--r--tests/programs/bareio.zc2
-rw-r--r--tests/programs/zc0bstr.zc96
-rw-r--r--tests/programs/zc0umd.zc58
-rwxr-xr-xtests/test.sh4
6 files changed, 285 insertions, 52 deletions
diff --git a/README.md b/README.md
index 44ee129..eed00d6 100644
--- a/README.md
+++ b/README.md
@@ -516,7 +516,7 @@ tables: `run.add_binop("**", 11, "ipow c")` adds or replaces a binary operator w
## the bootstrap
-`src/tools/zc0.c` compiles the subset of zc used by `src/compiler/compiler.zc` (without the analyzer, types, quote, parse words, or block expressions, but with `enhance` and top-level `embed` declarations) into the first genome, `build/s0/genome.bin`. a core built around that image compiles `src/compiler/compiler.zc` again in the VM using the full compiler, analyzer, and native division code, and `mkgenome` accepts the result only if it is an image (a compiler that rejects its own source prints diagnostics instead, which `mkgenome` shows). a core built around the second image compiles the source a third time; the two self-compiled images must be identical, and the first becomes `build/core/genome.bin`. the test suite checks this fixpoint on the interpreter, microcore, jit and llvm backends when available. in the zc0 subset `u`, `m` and `d` are recast tags, not names. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements.
+`src/tools/zc0.c` compiles the subset of zc used by `src/compiler/compiler.zc` (without the analyzer, types, quote, parse words, or block expressions, but with `enhance` and top-level `embed` declarations) into the first genome, `build/s0/genome.bin`. a core built around that image compiles `src/compiler/compiler.zc` again in the VM using the full compiler, analyzer, and native division code, and `mkgenome` accepts the result only if it is an image (a compiler that rejects its own source prints diagnostics instead, which `mkgenome` shows). a core built around the second image compiles the source a third time; the two self-compiled images must be identical, and the first becomes `build/core/genome.bin`. the test suite checks this fixpoint on the interpreter, microcore, jit and llvm backends when available. zc0 reads `u`, `m` and `d` as ordinary names too: `u*x` with the star touching the letter is a recast (no code), `u * x` is a product, and `.u`, `.m` and `.d` tags are read and ignored. it packs `b"..."` byte strings like the compiler and tracks `bstr` locals, globals, parameters and returns, and `embed` declarations (arrays only at the top level). an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements.
## the instruction set
diff --git a/src/tools/zc0.c b/src/tools/zc0.c
index b0ab29f..9faf883 100644
--- a/src/tools/zc0.c
+++ b/src/tools/zc0.c
@@ -4,8 +4,10 @@
the same image layout the resident compiler's image mode produces. the cores
embed that file with #embed (src/core/genome.h). 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. */
+ (.m and .d on parameters, .u on a return) are parsed and recorded in
+ signatures but not checked. u, m and d are ordinary names, as in the
+ resident compiler: a recast is the letter with the star touching it, u*x,
+ and it makes no code. */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@@ -176,7 +178,10 @@ static void next(void)
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();
+ while (src[pos] && src[pos] != '"') {
+ if (i >= 4095) { die("a byte string literal is at most 4095 bytes; embed larger data"); break; }
+ 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"))) {
@@ -187,7 +192,14 @@ static void next(void)
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; }
+ if (src[pos] == '"') {
+ pos++; i = 0;
+ while (src[pos] && src[pos] != '"') {
+ if (i >= 4095) { die("a string literal is at most 4095 bytes"); break; }
+ 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; }
@@ -200,9 +212,16 @@ static int is(const char *s) { return (tk == T_OP || tk == T_ID) && !strcmp(ts,
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; }
+/* u, m and d are ordinary names. u*x, m*x and d*x are recasts when the star touches
+ the letter, which the raw character after the name tells: u * x is a product, and
+ so is u*=x. the current token is the name, so pos is just past it. */
+static int isrecast(void)
+{
+ return tk == T_ID && ts[1] == 0 && (ts[0] == 'u' || ts[0] == 'm' || ts[0] == 'd') && src[pos] == '*' && src[pos + 1] != '=';
+}
/* ---- symbols ---- */
-typedef struct { char name[64]; int kind, sig, defd, str; int64_t val; } Sym; /* str: a global holding a byte string */
+typedef struct { char name[64]; int kind, sig, defd, str; int64_t val; } Sym; /* str: a global holding a byte string, or a function returning one */
static Sym sy[16384]; static int nsy;
typedef struct { int at, sym; } Fix;
static Fix fx[65536]; static int nfx;
@@ -237,7 +256,7 @@ static void fnref(const char *n) /* push a function address, fixing it up later
}
/* ---- locals ---- */
-typedef struct { char name[64]; int slot, arr, flt, str; } Loc;
+typedef struct { char name[64]; int slot, arr, flt, str, dstr; } Loc; /* dstr: declared bstr, so always a byte string */
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); } }
@@ -349,25 +368,32 @@ static int64_t cexpr(void)
for a growable handle or anything scalar. */
static int ptype0(void);
static int ptype(void);
+static int ptagname(void);
+static int ptstr; /* the type just read is bstr itself, with no stars, brackets or parameters after it */
static int ptype1(void)
{
- int n, e;
+ int n, e, isb;
+ ptstr = 0;
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; }
+ isb = !strcmp(ts, "bstr");
ident();
if (accept("<")) { /* generic arguments: vec<node*> */
+ isb = 0;
do { ptype(); } while (accept(","));
if (is(">>")) ts[1] = 0; /* >> closes two lists: leave the second > */
else expect(">");
}
+ ptstr = isb;
return 1;
}
static int ptype0(void)
{
int n = ptype1();
while (is("*") || is("[")) {
+ ptstr = 0;
if (accept("*")) n = 1;
else {
int count = 0, cap = 8, *dims = malloc(cap * sizeof(int));
@@ -390,19 +416,27 @@ static int ptype(void)
{
int n = ptype0();
while (accept("(")) {
- if (!accept(")")) { do { ptype(); if (accept(".")) ident(); } while (accept(",")); expect(")"); }
- n = 1;
+ if (!accept(")")) { do { ptype(); if (accept(".")) ptagname(); } while (accept(",")); expect(")"); }
+ n = 1; ptstr = 0;
}
return n;
}
+/* the name of an ownership tag, after its dot: 1 for m, 2 for d, 3 for u. zc0 does
+ nothing with them but record .m and .d on parameters and .u on a return. */
+static int ptagname(void)
+{
+ int t = 0;
+ if (is("m")) t = 1; else if (is("d")) t = 2; else if (is("u")) t = 3; else die("a tag is .u, .m or .d");
+ next();
+ return t;
+}
/* 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();
+ ptag = ptagname();
}
/* 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
@@ -423,7 +457,7 @@ static int notatype(void)
static int optype(void)
{
Lex L; int n;
- if (notatype() || (tk != T_ID && !is("["))) return -1;
+ if (notatype() || isrecast() || (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);
@@ -451,20 +485,26 @@ static void strlit(void) /* inline word string: jump over it, push its address *
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)
+/* a byte string's words: the length, then the bytes packed 8 per word, little-endian,
+ and a 0 after the last byte when the length is a multiple of 8, as bstrdata does in
+ compiler.zc. the result is the address of the length word, which is the string. */
+static int bstrwords(const unsigned char *b, long n)
{
- int j = here, a, i = 0, k, n = (int)strlen(ts);
- const char *b = ts;
- uint64_t w;
- lit(0); op(O_JMP); a = here;
+ int a = here; long i = 0; int k; uint64_t w;
emit(n);
while (i < n) {
w = 0; k = 0;
- while (k < 8 && i < n) { w |= (uint64_t)(unsigned char)b[i] << (k * 8); k++; i++; }
+ while (k < 8 && i < n) { w |= (uint64_t)b[i] << (k * 8); k++; i++; }
emit((int64_t)w);
}
if ((n & 7) == 0) emit(0);
+ return a;
+}
+/* b"...": the words inline, jumped over, then the address pushed */
+static void bstrlit(void)
+{
+ int j = here, a;
+ lit(0); op(O_JMP); a = bstrwords((const unsigned char *)ts, (long)strlen(ts));
setlit(j, here); lit(a); next();
}
static int returns_bstr(const char *n)
@@ -503,7 +543,7 @@ static void primary(void)
else if (!strcmp(n, "len")) fnref("len_s");
else fnref(n);
op(O_CALL);
- xs = returns_bstr(n); xf = 0;
+ xs = returns_bstr(n) || (s && s->kind == K_FN && s->str); xf = 0;
return;
}
fnref(n);
@@ -512,7 +552,7 @@ static void incdec(int post, int d)
{
if (!lv) die("++/-- needs an lvalue");
lv = 0;
- if (lvi >= 0) lo[lvi].str = 0;
+ if (lvi >= 0) lo[lvi].str = lo[lvi].dstr;
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); }
@@ -552,7 +592,7 @@ static void unary(void)
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 if (isrecast()) { next(); expect("*"); unary(); rval(); lvi = -1; } /* u*x: a recast, no code, and not a place to store */
else postfix();
}
static void bin(int minp)
@@ -586,7 +626,7 @@ static void expr(void)
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; }
+ if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = xs || lo[dst].dstr; }
} 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();
@@ -594,7 +634,7 @@ static void expr(void)
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; }
+ if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = lo[dst].dstr; }
}
}
@@ -616,6 +656,7 @@ static void asmblock(void)
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_ENH) { op(22 + (int)s->val); next(); continue; } /* an enhancement is its opcode */
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 */
@@ -631,6 +672,7 @@ static void asmblock(void)
static int brk[256][64], nbrk[256], cont[256], nloop;
static int epi[1024], nepi;
static void stmt(void);
+static void embeddecl(int local);
static void block(void)
{
int save = nlo;
@@ -652,7 +694,14 @@ static void stmt(void)
if (accept(";")) return;
if (is("{")) { block(); return; }
{
- int nw = optype(), n;
+ int nw = optype(), n, tstr = 0;
+ if (nw >= 0) tstr = ptstr;
+ else if (is(".")) { next(); ptagname(); nw = 1; } /* a bare tag: .m p = new(3); */
+ else if (is("embed")) { /* embed "file" as [type] name; makes a local */
+ Lex L; lexsave(&L); next();
+ if (tk == T_STR) { embeddecl(1); return; }
+ lexrest(&L);
+ }
if (nw >= 0 || accept("let") || accept("var")) {
if (nw < 0) nw = 1;
do {
@@ -662,8 +711,8 @@ static void stmt(void)
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; }
+ l->flt = 0; l->dstr = tstr && !l->arr; l->str = l->dstr;
+ if (accept("=")) { expr(); rval(); laddr(l->slot); op(O_ST); l->flt = xf; l->str = xs || l->dstr; }
} while (accept(","));
expect(";"); return;
}
@@ -684,7 +733,7 @@ static void stmt(void)
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); }
+ if (!is(";")) { if (is("let") || is("var") || tk == T_ID || is("[") || is(".")) { stmt(); goto cond; } expr(); rval(); op(O_DROP); }
expect(";");
cond: c = here;
if (!is(";")) { expr(); rval(); ftest(); } else { lit(1); xf = 0; }
@@ -708,18 +757,18 @@ static void stmt(void)
}
/* ---- top level ---- */
-static void fndef(int ret)
+static void fndef(int ret, int rstr) /* rstr: the return type is bstr */
{
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 pn[16][64]; int pstr[16];
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 */
+ t = 0; ptag = 0; pstr[np & 15] = 0;
+ if (optype() >= 0) { t = ptag == 1 ? 1 : ptag == 2 ? 2 : 0; pstr[np & 15] = ptstr; } /* 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();
@@ -729,7 +778,7 @@ static void fndef(int ret)
} while (accept(","));
expect(")");
}
- s->sig = ret + 2 * np + 64 * tags;
+ s->sig = ret + 2 * np + 64 * tags; s->str = rstr;
if (accept(";")) return; /* prototype */
if (s->defd) die("function defined twice");
s->val = here; s->defd = 1;
@@ -745,7 +794,7 @@ static void fndef(int ret)
}
#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++; }
+ for (i = 0; i < np; i++) { strcpy(lo[nlo].name, pn[i]); lo[nlo].arr = 0; lo[nlo].flt = 0; lo[nlo].dstr = lo[nlo].str = pstr[i & 15]; 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); }
@@ -798,6 +847,9 @@ static void enhdef(void)
}
/* embed "path" as [type] name; at the top level: a global holding the file's bytes,
read when the image is built. the path is relative to the working directory.
+ in a function it declares a local: the bytes of a bstr or char* sit in the code,
+ jumped over, and the local starts as their address; a number is stored in the
+ local. an array takes the top level.
bstr (the default) packs the bytes; char* keeps one character to a word; a
number takes them little-endian; T[] and T[n] have one element per file chunk
of T's size. the checks are the resident compiler's (kw_embed). zc0 drops
@@ -813,9 +865,14 @@ static int embed_width(const char *t, int star)
if (!strcmp(t, "int") || !strcmp(t, "word") || !strcmp(t, "bool") || !strcmp(t, "float") || !strcmp(t, "f64")) return 8;
return 0;
}
-static void embeddecl(void)
+static Loc *newloc(const char *n)
{
- char path[1024], ty[64] = "", *n; int star = 0, arr = -2, w, i, a; long len; unsigned char *buf; FILE *f;
+ Loc *l = &lo[nlo++]; strcpy(l->name, n); l->slot = nslot++; l->arr = l->flt = l->str = l->dstr = 0;
+ return l;
+}
+static void embeddecl(int local)
+{
+ char path[1024], ty[64] = "", *n; int star = 0, arr = -2, w, i, a, j; long len; unsigned char *buf; FILE *f; Loc *l;
if (tk != T_STR) die("embed takes a path in quotes: embed \"file\" as name;");
snprintf(path, sizeof path, "%s", ts); next();
expect("as");
@@ -835,13 +892,23 @@ static void embeddecl(void)
if (!buf || fread(buf, 1, (size_t)len, f) != (size_t)len) die("can't read the file named by embed");
fclose(f);
if (arr == -2 && (!ty[0] || (!strcmp(ty, "bstr") && !star))) {
- a = here; add(n, K_GVAR, a)->str = 1; emit(0); img[a - GORG] = here; emit(len);
- for (i = 0; i < len; i += 8) { uint64_t u = 0; for (w = 0; w < 8 && i + w < len; w++) u |= (uint64_t)buf[i + w] << (w * 8); emit((int64_t)u); }
- if (!(len & 7)) emit(0);
+ if (local) {
+ j = here; lit(0); op(O_JMP); a = bstrwords(buf, len); setlit(j, here);
+ l = newloc(n); l->str = l->dstr = 1; lit(a); laddr(l->slot); op(O_ST);
+ } else {
+ a = here; add(n, K_GVAR, a)->str = 1; emit(0); w = bstrwords(buf, len); img[a - GORG] = w;
+ }
} else if (arr == -2 && !strcmp(ty, "char") && star == 1) {
- a = here; add(n, K_GVAR, a); emit(0); img[a - GORG] = here;
- for (i = 0; i < len; i++) emit(buf[i]);
- emit(0);
+ if (local) {
+ j = here; lit(0); op(O_JMP); a = here;
+ for (i = 0; i < len; i++) emit(buf[i]);
+ emit(0); setlit(j, here);
+ l = newloc(n); lit(a); laddr(l->slot); op(O_ST);
+ } else {
+ a = here; add(n, K_GVAR, a); emit(0); img[a - GORG] = here;
+ for (i = 0; i < len; i++) emit(buf[i]);
+ emit(0);
+ }
} else if (arr == -2) {
uint64_t u = 0; w = embed_width(ty, star);
if (!w) die("embed declares a bstr, a char*, a number or an array of numbers");
@@ -851,9 +918,15 @@ static void embeddecl(void)
for (i = 0; i < len; i++) u |= (uint64_t)buf[i] << (i * 8);
if (ty[0] == 'i' && w < 8 && (u >> (w * 8 - 1) & 1)) u |= ~(uint64_t)0 << (w * 8);
if (!strcmp(ty, "f32")) u = fop_s2f(u);
- a = here; add(n, K_GVAR, a); emit((int64_t)u);
+ if (local) {
+ l = newloc(n); l->flt = !strcmp(ty, "float") || !strcmp(ty, "f64") || !strcmp(ty, "f32");
+ lit((int64_t)u); laddr(l->slot); op(O_ST);
+ } else {
+ a = here; add(n, K_GVAR, a); emit((int64_t)u);
+ }
} else {
int count; w = embed_width(ty, star);
+ if (local) die("zc0 embeds an array at the top level, not in a function");
if (!w) die("embed declares a bstr, a char*, a number or an array of numbers");
if (len % w) die("the embedded file's size is not a whole number of elements");
count = (int)(len / w);
@@ -880,18 +953,19 @@ static void top1(void)
expect(";"); return;
}
if (accept("include")) { do { ident(); } while (accept(",")); expect(";"); return; }
- if (accept("embed")) { embeddecl(); return; }
+ if (accept("embed")) { embeddecl(0); 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("fn")) { fndef(0, 0); return; }
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;
+ int nw, typed, k, a, rt, gstr = 0; Sym *gs;
+ ptag = 0; nw = optype(); typed = nw >= 0; if (typed) gstr = ptstr; else ptag = 0;
+ if (!typed && is(".")) { next(); ptag = ptagname(); typed = 1; nw = 1; } /* .u name(...), or a bare tag on a global */
+ rt = ptag == 3;
if (typed || accept("let") || accept("var")) {
- if (isfn()) { fndef(rt); return; }
+ if (isfn()) { if (ptag && !rt) die("the only return tag is .u"); fndef(rt, gstr); return; }
do {
char *n = ident();
if (dsn && !dnm[0]) strcpy(dnm, n);
@@ -901,9 +975,10 @@ static void top1(void)
if (k > 1) {
a = here; while (k--) emit(0); add(n, K_GARR, a);
} else {
- a = here; add(n, K_GVAR, a); emit(0);
+ a = here; gs = add(n, K_GVAR, a); gs->str = gstr; 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 if (tk == T_BSTR) { int d = bstrwords((const unsigned char *)ts, (long)strlen(ts)); img[a - GORG] = d; gs->str = 1; next(); }
else img[a - GORG] = cexpr();
}
}
diff --git a/tests/programs/bareio.zc b/tests/programs/bareio.zc
index a06ef38..21d2b40 100644
--- a/tests/programs/bareio.zc
+++ b/tests/programs/bareio.zc
@@ -2,7 +2,7 @@ namespace;
include rt;
// bare-metal syscalls: clock, a RAM file, random, and the machine size.
-// zc0 subset, so a run-only image can carry it. names stay off u/m/d.
+// zc0 subset, so a run-only image can carry it.
fn main() {
let buf[16], n, t0, t1, i, fd, r, board[8];
t0 = rt.syscall(6, 0, 0, 0);
diff --git a/tests/programs/zc0bstr.zc b/tests/programs/zc0bstr.zc
new file mode 100644
index 0000000..bb4ffbc
--- /dev/null
+++ b/tests/programs/zc0bstr.zc
@@ -0,0 +1,96 @@
+namespace;
+include rt;
+
+// packed byte strings in the zc0 subset: the same program runs on the resident
+// compiler and, built into a run-only image, on stage 0 (src/tools/zc0.c). it
+// covers every b"..." escape, the packed layout, bstr locals, globals,
+// parameters and returns, + and the six comparisons, indexing, slicing, len
+// and embed. run from the repository root: the embeds read tests/data/embed.
+
+bstr greeting = b"hello";
+bstr shout;
+bstr plain = b"plain";
+embed "tests/data/embed/text.txt" as bstr text;
+embed "tests/data/embed/nul.bin" as bstr nul;
+embed "tests/data/embed/text.txt" as untyped;
+
+bstr tag(bstr s) { return b"<" + s + b">"; }
+bstr twice(bstr s);
+bstr twice(bstr s) { return s + s; }
+bstr pick(int n) {
+ bstr r;
+ if (n) r = b"yes"; else r = b"no";
+ return r;
+}
+int rank(bstr a, bstr b) { return (a < b) * 8 + (a <= b) * 4 + (a == b) * 2 + (a > b); }
+int count(bstr s, int c) {
+ int n = 0, i = 0;
+ while (i < len(s)) { if (s[i] == c) n++; i++; }
+ return n;
+}
+int num(int n) { rt.put_n(n); rt.put_c(32); return 0; }
+int nl() { rt.put_c(10); return 0; }
+int bytes(bstr s) {
+ int i = 0;
+ num(len(s));
+ while (i < len(s)) { num(s[i]); i++; }
+ nl();
+ return 0;
+}
+int words(bstr s) {
+ word* w = rt.bstr_storage(s);
+ int n = 1 + (w[0] >> 3) + 1, i = 0;
+ while (i < n) { num(w[i]); i++; }
+ nl();
+ return 0;
+}
+
+fn main() {
+ bstr a = b"abc", b, c;
+ bstr e = b"";
+ let u = b"x";
+ // every escape the lexer knows: n t r 0 e, and any other character stands for itself
+ bytes(b"\n\t\r\e\\\"\'\q\x41");
+ bytes(b"ab\0cd");
+ bytes(b"\0");
+ bytes(b"café");
+ bytes(b"caf\303\251");
+ bytes(b"quote \" inside, 'single', back\\slash");
+ // the layout: a length word, bytes packed 8 to a word, a 0 word after a full last word
+ words(e);
+ words(b"a");
+ words(b"abcdefg");
+ words(b"abcdefgh");
+ words(b"abcdefghi");
+ words(b"0123456789abcdef");
+ words(b"0123456789abcdefg");
+ words(text);
+ words(nul);
+ // declarations, assignment later, a return value, concatenation
+ b = tag(a);
+ c = twice(b) + e + u;
+ rt.put_s(a); rt.put_c(32); rt.put_s(b); rt.put_c(32); rt.put_s(c); nl();
+ shout = twice(greeting) + b"!";
+ rt.put_s(greeting); rt.put_c(32); rt.put_s(shout); rt.put_c(32); rt.put_s(plain); nl();
+ rt.put_s(tag(twice(tag(b"z")))); nl();
+ rt.put_s(pick(1) + b"/" + pick(0)); nl();
+ // comparisons: below, equal, above, a prefix, the empty string, a 9 byte string
+ num(rank(b"abc", b"abd")); num(rank(b"abc", b"abc")); num(rank(b"abd", b"abc"));
+ num(rank(b"ab", b"abc")); num(rank(b"", b"")); num(rank(b"", b"a"));
+ num(rank(b"abcdefghi", b"abcdefghi")); num(rank(b"abcdefgh", b"abcdefghi")); nl();
+ num(a == b"abc"); num(a != b"abc"); num(a == b); num(a != b);
+ num(tag(a) == b"<abc>"); num(shout > greeting); num(greeting >= shout); num(plain <= plain); nl();
+ // indexing, slicing, len
+ num(a[0]); num(a[2]); num(a[3]); num(len(a)); num(len(e)); num(len(c)); num(len(b"123456789")); nl();
+ rt.put_s(c[1:4]); rt.put_c(32); rt.put_s(a[1:2]); rt.put_c(32); rt.put_s(a[0:0]); rt.put_c('|');
+ rt.put_s(a[2:99]); rt.put_c('|'); rt.put_s(a[3:1]); rt.put_c('|'); rt.put_s((a + a)[2:4]); nl();
+ num(count(c, '<')); num(count(shout, 'l')); num(count(b"mississippi", 's')); num(count(e, 'x')); nl();
+ num(b"xyz"[1]); num(len(b"xyz")); num(len(a + a + a)); num(len(tag(e))); nl();
+ // embed: a global of the file's bytes, with or without the type, and a local
+ rt.put_s(text); num(len(text)); num(text[0]); num(text == untyped); num(len(nul)); num(nul[2]); nl();
+ embed "tests/data/embed/text.txt" as bstr here;
+ embed "tests/data/embed/answer.bin" as int answer;
+ embed "tests/data/embed/text.txt" as char* chars;
+ rt.put_s(here + b"|" + untyped); num(len(here)); num(here == text); num(answer); num(chars[6]); nl();
+ return 0;
+}
diff --git a/tests/programs/zc0umd.zc b/tests/programs/zc0umd.zc
new file mode 100644
index 0000000..9693111
--- /dev/null
+++ b/tests/programs/zc0umd.zc
@@ -0,0 +1,58 @@
+namespace;
+include rt;
+
+// u, m and d are ordinary names, and a tag is written .u, .m or .d after a type.
+// u*x, m*x and d*x with no space before the star is a recast, and u * x with a
+// space is a product. the same program runs on the resident compiler and, built
+// into a run-only image, on stage 0 (src/tools/zc0.c), which reads the tags and
+// recasts and does nothing with them.
+
+int u = 2, m = 3; // globals
+int[4] d4;
+int d(int x) { return x + 1; } // a function
+
+int mul3(int u, int m, int d) { return u * m * d; } // parameters
+int mixed(int.d u, int m, int d) { return u * m - d; }
+int tagged(int(int) f, int.m x) { return f(x); }
+
+// owned objects through locals named for their tags
+word*.u box(int v) { word* m = rt.new(2); m[0] = v; m[1] = v * 2; return u*m; }
+word*.u pair(int u, int m) {
+ word* d = rt.new(2);
+ d[0] = u; d[1] = m;
+ return u*d;
+}
+.u single(int v) { let u = rt.new(1); u[0] = v; return u*u; }
+int sum(word*.m m) { return m[0] + m[1]; }
+int eat(word*.d d) { return d[0] + d[1]; }
+int hold(.m u, .d d) { return u[0] * 100 + d[0]; }
+int dig(word*.m u) { int m = u[0], d = u[1]; return m * 10 + d; }
+
+fn main() {
+ int a = 4, b = 5;
+ int.m n = 9;
+ word*.m p = box(7);
+ .m q = single(6);
+ word* r = pair(8, 9);
+ let c = box(10), e = pair(1, 2);
+ rt.put_n(u * m); rt.put_c(32); // two globals multiplied
+ rt.put_n(u*=m); rt.put_c(32); // u *= m is not a recast either
+ rt.put_n(d(u) * d(m)); rt.put_c(32);
+ rt.put_n(mul3(2, 3, 4)); rt.put_c(32);
+ rt.put_n(mixed(7, 6, 5)); rt.put_c(32);
+ rt.put_n(tagged(d, n)); rt.put_c(10);
+ {
+ int u = 6, m = 7, d = 8; // locals shadow the globals
+ rt.put_n(u * m); rt.put_c(32);
+ rt.put_n(u * d - m); rt.put_c(32);
+ rt.put_n(m * u * d); rt.put_c(32);
+ rt.put_n(a * u + m * b); rt.put_c(10);
+ }
+ rt.put_n(sum(p)); rt.put_c(32);
+ rt.put_n(q[0]); rt.put_c(32);
+ rt.put_n(r[0] * r[1]); rt.put_c(32);
+ rt.put_n(dig(c)); rt.put_c(32);
+ rt.put_n(hold(e, single(5))); rt.put_c(32);
+ rt.put_n(eat(d*p)); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/test.sh b/tests/test.sh
index 709d09c..223c9aa 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -102,6 +102,7 @@ run(){ # $1 = vm command, $2 = label
chk "$($vm < tests/programs/basics.zc | tr '\n' '/')" "285 610 42 512/hey 65 -3 -2 1024 -8/12456/757/9 100-101/5/ok/" "$l language basics"
chk "$($vm < tests/programs/umd.zc)" "100 6 0 15 99 5 5" "$l u m d lifetimes"
chk "$($vm < tests/programs/names.zc)" "12 1 8" "$l u, m and d are ordinary names"
+ chk "$($vm < tests/programs/zc0umd.zc | tr '\n' '/')" "6 6 28 24 37 10/42 41 336 59/21 6 72 120 105 21/" "$l u, m and d as names, products, recasts and tags (the zc0 subset)"
chk "$($vm < tests/programs/gc.zc)" "1000 1 900 1 199900" "$l mark-sweep collector"
chk "$($vm < tests/programs/meta.zc | tr '\n' '/')" "1024 19/unless ok/rrr/15 42/7/42/7/144/" "$l parse words, quote, eval, load, operator tables"
chk "$($vm < tests/programs/misc.zc | tr '\n' '/')" "45/54321/ok/" "$l compile-time eval, patched mnemonics, unchecked"
@@ -358,6 +359,9 @@ s_runonly() {
runonly strings-ex examples/strings.zc
runonly share tests/programs/share.zc
runonly embed tests/programs/embed_top.zc
+ runonly zc0bstr tests/programs/zc0bstr.zc
+ runonly zc0umd tests/programs/zc0umd.zc
+ runonly wiring examples/wiring.zc
d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/ \
&& $CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \
&& chk "$(printf 'abc\n' | $T/zymr | tr '\n' '/')" "$( (cat examples/echo.zc; printf '`abc\n') | $T/zy | tr '\n' '/')" "run echo" \