&NAME is the address of a word as a literal; CONST+N is a loader constant plus an offset as one literal, so that a capsule file can address the cells of its scratch area without adding at run time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
577 lines
20 KiB
C
577 lines
20 KiB
C
/* text.c -- assemble definitions written as text. See text.h. */
|
|
#include "v4/text.h"
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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 : "";
|
|
}
|