From 44ffd261bb36a4b28f0086c6db6736385b61aab6 Mon Sep 17 00:00:00 2001 From: rajames Date: Sun, 4 Oct 2026 10:41:50 -0400 Subject: [PATCH] feat(v4.0.0): the text assembler takes &NAME and CONST+N &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 --- v4/include/v4/text.h | 4 ++++ v4/src/text.c | 26 +++++++++++++++++++++++++- v4/tests/test_text.c | 15 +++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/v4/include/v4/text.h b/v4/include/v4/text.h index da97aa3b..02213392 100644 --- a/v4/include/v4/text.h +++ b/v4/include/v4/text.h @@ -26,6 +26,10 @@ * 123 -5 $FF a literal: `@p` and its value. Decimal, or hex after $. * It must fit a cell, as a signed or an unsigned number. * CONST a constant given by v4_text_constant: a literal. + * CONST+N the constant plus the number N: one literal. (Only when + * the whole token is not itself a name.) + * &NAME the address of the word NAME, which must already be + * defined: a literal. * ; + dup a! ... an opcode. `@p` cannot be written; write the number. * jump X call X if X -if X next X * a branch to X: a label of this word if it has one of diff --git a/v4/src/text.c b/v4/src/text.c index 3179a06d..6da0ffdf 100644 --- a/v4/src/text.c +++ b/v4/src/text.c @@ -300,7 +300,7 @@ static void end_word(v4_text *t) 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)) { + 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; } @@ -427,6 +427,14 @@ static void one_token(v4_text *t, reader *r, const char *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; } @@ -444,6 +452,22 @@ static void one_token(v4_text *t, reader *r, const char *tok) 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 */ } diff --git a/v4/tests/test_text.c b/v4/tests/test_text.c index 94b32359..b48fc548 100644 --- a/v4/tests/test_text.c +++ b/v4/tests/test_text.c @@ -409,6 +409,21 @@ int main(void) CHECK(ok(": 2DUP over over ; : 1+ 1 + ; : T ( a b -- 2a+2b+1 ) 2DUP + + + 1+ ;") && result("T", 2, 2, 3) == 11, "names that begin with digits"); + /* CONST+N: a constant and an offset as one literal */ + CHECK(ok(": T VAR+3 ;") && result("T", 0, 0, 0) == VARS + 3, "CONST+N is the constant plus N"); + CHECK(ok(": T VAR+0 VAR+10 + ;") && result("T", 0, 0, 0) == 2 * VARS + 10, "any N"); + CHECK(nb.mem[ORIGIN + 1] == VARS && nb.mem[ORIGIN + 2] == VARS + 10, "and it is a single literal"); + CHECK(ok(": VAR+1 77 ; : T VAR+1 ;") && result("T", 0, 0, 0) == 77, "a word of that very name comes first"); + CHECK(refused(": T VAR+ ;", "never defined", 1) && refused(": T VAR+x ;", "never defined", 1) && refused(": T NOPE+1 ;", "never defined", 1), + "what is not a constant and a number is just an unknown name"); + + /* &NAME: a word's address as a literal */ + CHECK(ok(": A 5 ; : T &A ;") && result("T", 0, 0, 0) == v4_text_word(&tx, "A"), "&NAME is the word's address"); + CHECK(ok(": A 5 ; : T &A push ;") && result("T", 0, 0, 0) == 5, "which can be executed"); + CHECK(refused(": T &A ; : A 5 ;", "already defined", 1), "the word must be defined first"); + CHECK(refused(": T &VAR ;", "already defined", 1), "and be a word"); + CHECK(refused(": &X 1 ;", "cannot be used as a name", 1), "a name cannot begin with &"); + /* headers: a dictionary entry in front of a word (capsule/dict.v4) */ CHECK(ok("header DUP : (DUP) dup + ;"), "a header assembles: %s", v4_text_error(&tx)); {