Skip to content

forth.s: CREATE alone does not produce a usable variable; need VARIABLE/CONSTANT or [']/LITERAL to build them #6

Description

@softwarewrighter

Problem

In the bare forth.s REPL, CREATE name N , does not yield a callable variable. After the CREATE, executing name pushes nothing, so the standard CREATE name 0 , pattern for declaring a single-cell variable does not work.

This came up while wiring up Tuplet's Forth emitter (sw-vibe-coding/tuplet). Our lowering doc (docs/lowering.md) leans on the conventional pattern below to allocate tuple/scalar storage:

CREATE coord2-x 0 ,
CREATE coord2-y 0 ,
: coord2!  coord2-y ! coord2-x ! ;
: coord2@  coord2-x @ coord2-y @ ;

Generating that produces the failure pasted below: every line up to the body returns ok, then the call site emits ? because the data-area words aren't callable.

Concrete repro

Tested on sw-embed/sw-cor24-forth HEAD d349a54 with cor24-run:

$ cor24-run --run forth.s -u $'CREATE foo 42 ,\nfoo .S\nfoo @ .\n' --speed 0 -n 50000000 2>&1 | awk '/^UART output:/{f=1} f'
UART output:  ok
<0>  ok
? 42 ? 

Executed 50000000 instructions

Observations:

  • Line 1: CREATE foo 42 , ok. HERE advances by 10 bytes (3 link + 1 flags_len + 3 name + 3 comma'd cell), so the header and data cell are present.
  • Line 2: foo .S<0> — the stack is empty. foo pushed nothing.
  • Line 3: foo @ .? (unknown-word path) followed by stray output and another ?.

A self-contained repro lives at scripts/repro-cor24-forth-create-variable.sh in the Tuplet repo:
https://github.com/sw-vibe-coding/tuplet/blob/main/scripts/repro-cor24-forth-create-variable.sh

Root cause

The kernel comment on entry_create (forth.s ~line 1218) is explicit:

; CREATE ( -- ) : Read name, build dictionary header at HERE
; Reads next word from UART input, builds link+flags+name at HERE.
; Updates LATEST. Does NOT write CFA — caller does that.

So CREATE is intentionally a header-only primitive used internally by : (which then writes its own CFA). The public REPL CREATE therefore leaves the new word with no runtime — calling it dispatches to whatever bytes happen to land at the post-name address. There is no DOVAR-style runtime attached.

Issue #3 already tracks DOES> / CONSTANT / VARIABLE for the higher-level kernels (forth-in-forth, forth-on-forthish) and explicitly excludes forth.s. This issue is therefore filing the gap that was carved out: REPL users of forth.s have no way to declare a usable variable cell.

Why the natural prelude workaround doesn't reach

Defining VARIABLE or CONSTANT from the REPL would normally look like:

: VARIABLE  CREATE 0 , DOES> ;       \ needs DOES>
: CONSTANT  CREATE , DOES> @ ;       \ needs DOES>

or, dovar-free:

: x! <addr> ! ;
: x@ <addr> @ ;

— but compiling a literal address into the body needs LIT to be reachable from compile mode (i.e. an immediate LITERAL or [']), and forth.s exposes neither. WORDS lists LIT only as the runtime primitive used by the inner interpreter; there is no compile-time way to splice a number into a colon body.

So the gap is closed: from forth.s alone you can't allocate a callable variable cell. (You can build the dictionary entry; you can't make calling it do anything useful.)

Asks (any one of these unblocks Tuplet)

In rough order of preference / smallest-change-first:

  1. Expose LITERAL (immediate; compiles LIT n) or ['] (immediate; compiles LIT xt-of-next-word). Either lets VARIABLE / CONSTANT be written as ordinary colon definitions in user space without changing CREATE.
  2. Add VARIABLE name and CONSTANT name primitives directly to forth.s. Same number of new entry points (one each) but no immediate-word complexity.
  3. Make CREATE attach a default DOVAR-like CFA so name after CREATE name pushes the data-field address. This is the most ANS-faithful fix and unblocks CREATE name 0 , directly.
  4. Add DOES> so user code can splice runtime behavior. This is more invasive (issue More Forth words: +LOOP/J/LEAVE, DOES>, RECURSE, PICK/ROLL/?DUP/MIN/MAX/<=/>=/<> #3 lists it for the higher-level kernels) — listed last for completeness.

If forth.s is intentionally minimal and any of these is out of scope, a doc update saying so explicitly (and pointing prelude-builders at forth-in-forth / forth-on-forthish) would also unblock us by redirecting Tuplet's emitter target.

Tuplet-side workaround

Until this is resolved, the Tuplet Forth emitter (tuplet-forth-emit saga, step 002) can produce deterministic Forth text but cannot run it end-to-end. The runner script is wired up so that re-enabling the runnable demo is a one-line change once any of the above lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions