/* text.c -- assemble definitions written as text. See text.h. */ #include "v4/text.h" #include #include #include #include enum { KIND_WORD = 1, KIND_CONST, KIND_MACRO }; static const char *const mnemonic[V4_OPCODE_COUNT] = { ";", "ex", "jump", "call", "unext", "next", "if", "-if", "@p", "@+", "@b", "@", "!p", "!+", "!b", "!", "+*", "2*", "2/", "inv", "+", "and", "xor", "drop", "dup", "pop", "over", "a", "nop", "push", "b!", "a!" }; /* Where tokens come from: the source text, or a macro's text on top of it. */ typedef struct { const char *p[V4_TEXT_MAX_DEPTH + 1u]; unsigned depth; /* 0: nothing; 1: the source; more: in macros */ } reader; static void fail(v4_text *t, const char *what, const char *token) { if (t->failed) return; t->failed = 1; if (token) snprintf(t->message, sizeof t->message, "line %u: %s: %s", t->line, what, token); else snprintf(t->message, sizeof t->message, "line %u: %s", t->line, what); } static unsigned intern(v4_text *t, const char *s) { size_t len = strlen(s) + 1u; unsigned at = t->pool_used; if (len > V4_TEXT_POOL - t->pool_used) { fail(t, "out of name space", s); return 0; } memcpy(t->pool + at, s, len); t->pool_used += (unsigned)len; return at; } static int opcode_of(const char *s) { for (int i = 0; i < (int)V4_OPCODE_COUNT; i++) if (strcmp(s, mnemonic[i]) == 0) return i; return -1; } static const v4_text_symbol *find(const v4_text *t, const char *s) { for (unsigned i = t->nsym; i-- > 0; ) if (strcmp(t->pool + t->sym[i].name, s) == 0) return &t->sym[i]; return NULL; } /* The entry flags a `header` may be given (capsule/dict.v4). */ static v4_cell flag_of(const char *s) { if (strcmp(s, "immediate") == 0) return 1; if (strcmp(s, "data") == 0) return 4; if (strcmp(s, "inline") == 0) return 8; if (strcmp(s, "compile-only") == 0) return 16; return 0; } static int is_reserved(const char *s) { return strcmp(s, ":") == 0 || strcmp(s, "macro") == 0 || strcmp(s, "endmacro") == 0 || strcmp(s, "\\") == 0 || strcmp(s, "(") == 0 || strcmp(s, "FOR") == 0 || strcmp(s, "NEXT") == 0 || strcmp(s, "UNEXT") == 0 || strcmp(s, "header") == 0 || flag_of(s) != 0; } static int is_label_def(const char *s) { size_t len = strlen(s); return len > 1 && s[len - 1] == ':'; } /* A whole token that is a number: decimal with an optional sign, or $hex. * It must fit a cell, read as signed or as unsigned. */ static int number_of(const char *s, v4_cell *out, int *too_big) { const char *digits = s; char *end; int neg = 0, base = 10; unsigned long long u; *too_big = 0; if (*digits == '$') { base = 16; digits++; } else if (*digits == '-') { neg = 1; digits++; } if (*digits == 0) return 0; for (const char *c = digits; *c; c++) { int ok = (*c >= '0' && *c <= '9') || (base == 16 && ((*c >= 'a' && *c <= 'f') || (*c >= 'A' && *c <= 'F'))); if (!ok) return 0; } errno = 0; u = strtoull(digits, &end, base); if (*end) return 0; if (errno == ERANGE || (neg ? u > (unsigned long long)V4_MSB : u > (unsigned long long)(v4_ucell)~(v4_ucell)0)) { *too_big = 1; return 1; } *out = neg ? (v4_cell)((v4_ucell)0 - (v4_ucell)u) : (v4_cell)(v4_ucell)u; return 1; } /* The next token, or 0 at the end. Comments are skipped here, and lines * counted, in the source only: a macro's text holds neither. */ static int next_token(v4_text *t, reader *r, char *tok) { for (;;) { const char *p; unsigned len = 0; if (r->depth == 0) return 0; p = r->p[r->depth - 1u]; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { if (*p == '\n' && r->depth == 1) t->line++; p++; } if (*p == 0) { r->depth--; continue; } while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') { if (len < V4_TEXT_MAX_TOKEN) tok[len] = *p; len++; p++; } if (len > V4_TEXT_MAX_TOKEN) { tok[V4_TEXT_MAX_TOKEN] = 0; fail(t, "name too long", tok); return 0; } tok[len] = 0; if (strcmp(tok, "\\") == 0) { while (*p && *p != '\n') p++; r->p[r->depth - 1u] = p; continue; } if (strcmp(tok, "(") == 0) { while (*p && *p != ')') { if (*p == '\n' && r->depth == 1) t->line++; p++; } if (*p == 0) { fail(t, "comment not closed", NULL); return 0; } r->p[r->depth - 1u] = p + 1; continue; } r->p[r->depth - 1u] = p; return 1; } } static void end_word(v4_text *t); static void check_asm(v4_text *t, const char *tok); /* The next run of non-blank characters of the source, whatever they are: * a header's name may be ( or \ or any opcode. */ static int raw_token(v4_text *t, reader *r, char *tok) { const char *p; unsigned len = 0; if (r->depth != 1) return 0; p = r->p[0]; while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { if (*p == '\n') t->line++; p++; } while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') { if (len < V4_TEXT_MAX_TOKEN) tok[len] = *p; len++; p++; } r->p[0] = p; if (len == 0 || len > V4_TEXT_MAX_TOKEN) return 0; tok[len] = 0; return 1; } /* One cell appended at the next word address; nothing is being built. */ static void put_cell(v4_text *t, v4_cell value) { v4_asm *as = &t->as; if (as->here < 0 || (v4_ucell)as->here >= V4_NODE_WORDS) { as->error = 1; return; } v4_node_store(as->n, as->here, value); as->here++; } /* `header NAME`: a dictionary entry for NAME, as capsule/dict.v4 lays one * out, in front of the word the next `:` starts: * the name: its count byte, then its characters, four bytes to a cell, * zero-padded to a whole cell * flags, the name's word address, the link to the entry before * and the code follows. */ static void header(v4_text *t, reader *r) { char name[V4_TEXT_MAX_TOKEN + 1u]; unsigned len, cells, i; v4_cell nfa; if (r->depth != 1) { fail(t, "not allowed in a macro", "header"); return; } if (t->header_pending) { fail(t, "header must be followed by :", NULL); return; } if (!raw_token(t, r, name)) { fail(t, "header without a name, or one too long", NULL); return; } len = (unsigned)strlen(name); if (len > 31) { fail(t, "a header's name is at most 31 characters", name); return; } end_word(t); nfa = v4_asm_label(&t->as); cells = (len + 4u) / 4u; for (i = 0; i < cells; i++) { v4_ucell w = 0; for (unsigned b = 0; b < 4; b++) { unsigned at = 4u * i + b; /* byte 0 is the count */ unsigned ch = at == 0 ? len : (at <= len ? (unsigned char)name[at - 1u] : 0u); w |= (v4_ucell)ch << (8u * b); } put_cell(t, (v4_cell)w); } put_cell(t, 0); put_cell(t, nfa); put_cell(t, t->latest); check_asm(t, name); if (t->failed) return; t->latest = t->as.here; t->header_pending = 1; } /* A branch may go in the next slot only if that slot can reach every word of * the node; otherwise it starts a new word. */ static void place_branch(v4_text *t) { v4_asm *as = &t->as; if (as->slot == 0) return; if (!v4_iword_branch_legal(as->slot) || (v4_ucell)v4_iword_slot_mask(as->slot) < (v4_ucell)(V4_NODE_WORDS - 1u)) (void)v4_asm_label(as); } static void check_asm(v4_text *t, const char *tok) { if (t->as.error) fail(t, "does not fit: memory is full, or a branch cannot reach", tok); } static void branch_to(v4_text *t, unsigned op, v4_cell target, const char *tok) { place_branch(t); v4_asm_branch(&t->as, op, target); check_asm(t, tok); } static void branch_later(v4_text *t, unsigned op, const char *name, int local) { v4_text_ref *ref; if (t->nref == V4_TEXT_MAX_REFS) { fail(t, "too many unresolved branches", name); return; } place_branch(t); ref = &t->ref[t->nref]; ref->name = intern(t, name); ref->ref = v4_asm_branch_fwd(&t->as, op); ref->line = t->line; ref->local = local; check_asm(t, name); if (!t->failed) t->nref++; } /* A branch to `name`. Written as a bare name it is a call to a word. After * a branch mnemonic it is a label of this word if the word has one, before * or after the branch, and only otherwise a word -- so it cannot be settled * until the word ends, even when a word of that name already exists. */ static void branch(v4_text *t, unsigned op, const char *name, int may_be_label) { const v4_text_symbol *s; if (may_be_label) for (unsigned i = 0; i < t->nlabel; i++) if (strcmp(t->pool + t->label[i].name, name) == 0) { branch_to(t, op, t->label[i].addr, name); return; } s = find(t, name); if (s && s->kind == KIND_WORD && !may_be_label) { branch_to(t, op, s->value, name); return; } if ((s && s->kind != KIND_WORD) || opcode_of(name) >= 0 || is_reserved(name) || is_label_def(name)) { fail(t, "not something to branch to", name); return; } { v4_cell v; int big; if (number_of(name, &v, &big)) { fail(t, "not something to branch to", name); return; } } branch_later(t, op, name, may_be_label); } static void resolve(v4_text *t, unsigned i, v4_cell target) { v4_asm_resolve(&t->as, t->ref[i].ref, target); if (t->as.error) { unsigned line = t->line; t->line = t->ref[i].line; fail(t, "branch cannot reach", t->pool + t->ref[i].name); t->line = line; } t->ref[i] = t->ref[--t->nref]; } /* The word being assembled ends: its labels go, and what was not a label of * it must be a word. */ static void end_word(v4_text *t) { if (t->nfor) { fail(t, "FOR without NEXT or UNEXT", NULL); t->nfor = 0; } for (unsigned i = 0; i < t->nref && !t->failed; ) { const v4_text_symbol *s; if (!t->ref[i].local) { i++; continue; } t->ref[i].local = 0; s = find(t, t->pool + t->ref[i].name); if (s && s->kind == KIND_WORD) resolve(t, i, s->value); /* moves another into place i */ else i++; } t->nlabel = 0; } static void define(v4_text *t, const char *name, unsigned char kind, v4_cell value) { v4_cell v; int big; if (opcode_of(name) >= 0 || is_reserved(name) || is_label_def(name) || number_of(name, &v, &big) || name[0] == '&') { fail(t, "cannot be used as a name", name); return; } if (find(t, name)) { fail(t, "already defined", name); return; } if (t->nsym == V4_TEXT_MAX_SYMBOLS) { fail(t, "too many names", name); return; } t->sym[t->nsym].name = intern(t, name); t->sym[t->nsym].kind = kind; t->sym[t->nsym].value = value; if (!t->failed) t->nsym++; } static void define_macro(v4_text *t, reader *r) { char name[V4_TEXT_MAX_TOKEN + 1u], tok[V4_TEXT_MAX_TOKEN + 1u]; unsigned start, line = t->line; if (r->depth != 1) { fail(t, "macro inside a macro", NULL); return; } if (!next_token(t, r, name)) { fail(t, "macro without a name", NULL); return; } start = t->pool_used; for (;;) { size_t len; if (!next_token(t, r, tok)) { if (!t->failed) { t->line = line; fail(t, "macro without endmacro", name); } return; } if (strcmp(tok, "endmacro") == 0) break; if (strcmp(tok, ":") == 0 || strcmp(tok, "macro") == 0 || is_label_def(tok)) { fail(t, "not allowed in a macro", tok); return; } len = strlen(tok); if (len + 2u > V4_TEXT_POOL - t->pool_used) { fail(t, "out of name space", name); return; } memcpy(t->pool + t->pool_used, tok, len); t->pool_used += (unsigned)len; t->pool[t->pool_used++] = ' '; } if (t->pool_used == V4_TEXT_POOL) { fail(t, "out of name space", name); return; } t->pool[t->pool_used++] = 0; define(t, name, KIND_MACRO, (v4_cell)start); } static void one_token(v4_text *t, reader *r, const char *tok) { const v4_text_symbol *s; v4_asm *as = &t->as; v4_cell v; int op, big; if (strcmp(tok, "header") == 0) { header(t, r); return; } if (t->header_pending) { v4_cell f = flag_of(tok); if (f) { as->n->mem[t->latest - 3] |= f; return; } if (strcmp(tok, ":") != 0) { fail(t, "header must be followed by :", tok); return; } } else if (flag_of(tok)) { fail(t, "a flag with no header before it", tok); return; } if (strcmp(tok, ":") == 0) { char name[V4_TEXT_MAX_TOKEN + 1u]; if (r->depth != 1) { fail(t, "not allowed in a macro", tok); return; } if (!next_token(t, r, name)) { fail(t, ": without a name", NULL); return; } end_word(t); define(t, name, KIND_WORD, v4_asm_label(as)); t->header_pending = 0; return; } if (strcmp(tok, "macro") == 0) { define_macro(t, r); return; } if (strcmp(tok, "endmacro") == 0) { fail(t, "endmacro without macro", NULL); return; } if (is_label_def(tok)) { char name[V4_TEXT_MAX_TOKEN + 1u]; v4_cell addr; size_t len = strlen(tok) - 1u; memcpy(name, tok, len); name[len] = 0; for (unsigned i = 0; i < t->nlabel; i++) if (strcmp(t->pool + t->label[i].name, name) == 0) { fail(t, "label already defined in this word", name); return; } if (t->nlabel == V4_TEXT_MAX_LABELS) { fail(t, "too many labels in one word", name); return; } addr = v4_asm_label(as); t->label[t->nlabel].name = intern(t, name); t->label[t->nlabel].addr = addr; t->nlabel++; for (unsigned i = 0; i < t->nref && !t->failed; ) if (t->ref[i].local && strcmp(t->pool + t->ref[i].name, name) == 0) resolve(t, i, addr); else i++; return; } if (strcmp(tok, "FOR") == 0) { if (t->nfor == V4_TEXT_MAX_DEPTH) { fail(t, "FOR nested too deep", NULL); return; } v4_asm_op(as, V4_OP_PUSH); t->for_addr[t->nfor++] = v4_asm_label(as); check_asm(t, tok); return; } if (strcmp(tok, "NEXT") == 0) { if (t->nfor == 0) { fail(t, "NEXT without FOR", NULL); return; } branch_to(t, V4_OP_NEXT, t->for_addr[--t->nfor], tok); return; } if (strcmp(tok, "UNEXT") == 0) { v4_cell body; if (t->nfor == 0) { fail(t, "UNEXT without FOR", NULL); return; } body = t->for_addr[--t->nfor]; /* unext repeats the word it is in, so it must still be the word the * body started, with a slot left, and that word must own no literal */ if (as->here != body || as->slot == 0 || as->slot >= V4_SLOT_COUNT || as->nlit != 0) { fail(t, "FOR ... UNEXT must fit one instruction word and hold no literal", NULL); return; } v4_asm_op(as, V4_OP_UNEXT); check_asm(t, tok); return; } op = opcode_of(tok); if (op >= 0) { if (op == V4_OP_FETCH_P) { fail(t, "write the number, not @p", NULL); return; } if (v4_op_is_branch((unsigned)op)) { char target[V4_TEXT_MAX_TOKEN + 1u]; if (!next_token(t, r, target)) { fail(t, "branch without a target", tok); return; } branch(t, (unsigned)op, target, 1); return; } v4_asm_op(as, (unsigned)op); check_asm(t, tok); return; } if (tok[0] == '&' && tok[1] != 0) { /* the address of a word, as a literal */ s = find(t, tok + 1); if (!s || s->kind != KIND_WORD) { fail(t, "& needs a word that is already defined", tok + 1); return; } v4_asm_lit(as, s->value); check_asm(t, tok); return; } s = find(t, tok); if (s && s->kind == KIND_MACRO) { if (r->depth == V4_TEXT_MAX_DEPTH + 1u) { fail(t, "macros nested too deep", tok); return; } r->p[r->depth++] = t->pool + (unsigned)s->value; return; } if (number_of(tok, &v, &big)) { if (big) { fail(t, "number does not fit a cell", tok); return; } v4_asm_lit(as, v); check_asm(t, tok); return; } if (s && s->kind == KIND_CONST) { v4_asm_lit(as, s->value); check_asm(t, tok); return; } if (!s) { /* CONST+N: a constant and an offset, one literal */ const char *plus = strrchr(tok, '+'); if (plus && plus != tok && plus[1] != 0) { char left[V4_TEXT_MAX_TOKEN + 1u]; const v4_text_symbol *c; size_t len = (size_t)(plus - tok); memcpy(left, tok, len); left[len] = 0; c = find(t, left); if (c && c->kind == KIND_CONST && number_of(plus + 1, &v, &big) && !big) { v4_asm_lit(as, (v4_cell)((v4_ucell)c->value + (v4_ucell)v)); check_asm(t, tok); return; } } } branch(t, V4_OP_CALL, tok, 0); /* a word, defined already or later */ } void v4_text_begin(v4_text *t, v4_node *n, v4_cell origin) { v4_asm_begin(&t->as, n, origin); t->nsym = 0; t->nlabel = 0; t->nref = 0; t->nfor = 0; t->pool_used = 0; t->line = 0; t->failed = 0; t->message[0] = 0; t->latest = 0; t->header_pending = 0; } void v4_text_constant(v4_text *t, const char *name, v4_cell value) { if (t->failed) return; if (strlen(name) > V4_TEXT_MAX_TOKEN) { fail(t, "name too long", name); return; } define(t, name, KIND_CONST, value); } int v4_text_assemble(v4_text *t, const char *source) { char tok[V4_TEXT_MAX_TOKEN + 1u]; reader r; if (t->failed) return 0; r.p[0] = source; r.depth = 1; t->line = 1; while (!t->failed && next_token(t, &r, tok)) one_token(t, &r, tok); if (!t->failed && t->header_pending) fail(t, "header must be followed by :", NULL); if (!t->failed) end_word(t); return !t->failed; } int v4_text_assemble_file(v4_text *t, const char *path) { const char *base = strrchr(path, '/'); char *text = NULL, was[sizeof t->message]; long size = 0; FILE *f; if (t->failed) return 0; base = base ? base + 1 : path; f = fopen(path, "rb"); if (f && fseek(f, 0, SEEK_END) == 0 && (size = ftell(f)) >= 0 && fseek(f, 0, SEEK_SET) == 0) text = malloc((size_t)size + 1u); if (!text || fread(text, 1, (size_t)size, f) != (size_t)size) { if (f) fclose(f); free(text); t->line = 0; fail(t, "cannot read", path); return 0; } fclose(f); text[size] = 0; if (!v4_text_assemble(t, text)) { memcpy(was, t->message, sizeof was); snprintf(t->message, sizeof t->message, "%.40s, %.170s", base, was); } free(text); return !t->failed; } int v4_text_finish(v4_text *t) { if (t->failed) return 0; end_word(t); while (t->nref && !t->failed) { const v4_text_symbol *s = find(t, t->pool + t->ref[0].name); if (!s || s->kind != KIND_WORD) { t->line = t->ref[0].line; fail(t, "never defined", t->pool + t->ref[0].name); break; } resolve(t, 0, s->value); } if (!t->failed && !v4_asm_ok(&t->as)) fail(t, "does not fit: memory is full", NULL); return !t->failed; } v4_cell v4_text_word(const v4_text *t, const char *name) { const v4_text_symbol *s = find(t, name); return s && s->kind == KIND_WORD ? s->value : (v4_cell)-1; } v4_cell v4_text_latest(const v4_text *t) { return t->latest; } v4_cell v4_text_here(v4_text *t) { return v4_asm_label(&t->as); } const char *v4_text_error(const v4_text *t) { return t->failed ? t->message : ""; }