4 Commits
Author SHA1 Message Date
rajamesandClaude Opus 5.5 dac7ffac92 feat(v4.0.0): the defining and compiling words
The fourth layer of the compiler capsule.  compile.v4: INTERPRET's
loop, STATE [ ] : ; EXIT IMMEDIATE LITERAL COMPILE [COMPILE] ' EXECUTE,
CREATE VARIABLE CONSTANT DOES>, and IF ELSE THEN BEGIN UNTIL AGAIN
WHILE REPEAT DO ?DO LOOP +LOOP on a control-flow stack in memory.
forth.v4: the first words of the vocabulary, the in-line ones.

FORTH source now goes in and running code comes out.  49 programmes
were run through the v3 binary and through this; every result agrees,
bar COMPILE, which v3 cannot run.  The code laid down for each control
structure is word for word the expansion DECOMPOSITION.md gives.

A line may have six values on the stack while it is interpreted, and
words called from the interpreter may nest eight deep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:42:23 -04:00
rajamesandClaude Opus 5.5 bbd1b4047f refactor(v4.0.0): the capsule's inner words keep their state in memory
WORD, the dictionary words and the code generator run underneath
whatever the user has on the stacks, which are ten and nine deep (D-2).
Measured, they used five to seven data cells of their own, leaving a
line about three.  Each now keeps what it works on in its file's
scratch cells and has at most three cells on the data stack; , calls
nothing; and the longest chains of calls are shorter.

The public words of core.v4, input.v4 and dict.v4 get dictionary
headers.  NUMBER is split so the interpreter can have a flag instead of
NODE-ERROR.  The host-node tests share one memory map, host_map.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:41:50 -04:00
rajamesandClaude Opus 5.5 44ffd261bb 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 <noreply@anthropic.com>
2026-10-04 10:41:50 -04:00
rajamesandClaude Opus 5.5 26a6a16537 feat(v4.0.0): the text assembler lays down dictionary headers
'header NAME [flags]' in front of a word gives it a dictionary entry in
the layout capsule/dict.v4 uses, linked to the header before it, so the
words of a capsule are in the dictionary as soon as it is assembled.
The name is taken as it stands (it may be ( or + or ;) and the
assembler's own name for the code may differ.

Tested in the assembler's own test, and by FIND and the field words
running over assembler-made entries beside ones (HEADER) made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 09:58:26 -04:00
16 changed files with 1426 additions and 255 deletions
+36 -9
View File
@@ -572,8 +572,8 @@ width and print a flood of spaces.
| `BL` | IN | `32` — executed on the golden model (2026-10-04). |
| `EXPECT` `QUERY` | CC | Built on `KEY` (DEV); source in `v4/capsule/input.v4`. FORTH-79 (ruled 2026-10-04). `EXPECT ( baddr n -- )` stores characters from `baddr` upward until a new-line (taken, not stored) or until `n` have been received, then a zero; no action for `n <= 0`; a line longer than `n` leaves the rest to be read next. It also sets `SPAN`, which is not FORTH-79 but is v3's. `QUERY` is `TIB 80 EXPECT 0 >IN !`. v3's `EXPECT` was C's `fgets` and took `n-1` characters, and its `QUERY` took 1024. Executed on the golden model's host node (2026-10-04) against C for every size and line length. `EXPECT` leaves its caller 6 data cells and 4 return entries. |
| `SPAN` `TIB` `>IN` `SOURCE` | CC | Interpreter state on the host node. `TIB` is the buffer's byte address and `>IN` and `SPAN` their variables' word addresses, all in line; `SOURCE ( -- baddr u )` is `TIB` and `SPAN @`, a negative `SPAN` read as 0. Executed on the golden model's host node (2026-10-04). |
| `WORD` `ENCLOSE` | CC | Parser; source in `v4/capsule/input.v4`. `WORD ( c -- baddr )` is FORTH-79 (ruled 2026-10-04): characters are taken from `TIB` until the delimiter `c` or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (`c`, or a zero if the text ran out) is stored after them, uncounted; `>IN` is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's `WORD` skipped every delimiter after the word, always stored a zero, and cut at 62. `ENCLOSE ( baddr c -- baddr n1 n2 n3 )`, not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and `ENCLOSE` against values recorded from the v3 binary. `WORD` leaves its caller 4 data cells and 3 return entries. |
| `NUMBER` `CONVERT` | CC | FORTH-79, not v3 (ruled 2026-10-04); source in `v4/capsule/input.v4`. `CONVERT ( d1 baddr1 -- d2 baddr2 )` takes the characters from `baddr1 + 1` on as digits in the current `BASE`, in either case, accumulating each into the double after multiplying it by `BASE`, and stops at the first that is not one; the double is `( lo hi )`. v3's was base 10 only, started at `baddr1`, and took the double low cell on top. `NUMBER ( baddr -- d )` is the counted string as a signed double in the current `BASE`, with an optional leading minus; anything else gives 0 and sets `NODE-ERROR`. v3's returned `( n flag )` in base 10. Executed on the golden model's host node (2026-10-04) against C in eight bases, including a digit that carries out of the low cell. `NUMBER` leaves its caller 3 data cells and 3 return entries. |
| `WORD` `ENCLOSE` | CC | Parser; source in `v4/capsule/input.v4`. `WORD ( c -- baddr )` is FORTH-79 (ruled 2026-10-04): characters are taken from `TIB` until the delimiter `c` or the end of the text, leading delimiters ignored, and stored as a counted string; the delimiter met (`c`, or a zero if the text ran out) is stored after them, uncounted; `>IN` is left just past it; with nothing left the count is 0. The count is a byte, so a word over 255 characters is cut to 255; the buffer is 257 bytes. v3's `WORD` skipped every delimiter after the word, always stored a zero, and cut at 62. `ENCLOSE ( baddr c -- baddr n1 n2 n3 )`, not a FORTH-79 word, works on a zero-terminated string as v3's. Executed on the golden model's host node (2026-10-04) against C, and `ENCLOSE` against values recorded from the v3 binary. `WORD` keeps what it works on in memory and has at most three cells of its own on the data stack: it leaves its caller 7 data cells and 6 return entries. |
| `NUMBER` `CONVERT` | CC | FORTH-79, not v3 (ruled 2026-10-04); source in `v4/capsule/input.v4`. `CONVERT ( d1 baddr1 -- d2 baddr2 )` takes the characters from `baddr1 + 1` on as digits in the current `BASE`, in either case, accumulating each into the double after multiplying it by `BASE`, and stops at the first that is not one; the double is `( lo hi )`. v3's was base 10 only, started at `baddr1`, and took the double low cell on top. `NUMBER ( baddr -- d )` is the counted string as a signed double in the current `BASE`, with an optional leading minus; anything else gives 0 and sets `NODE-ERROR`. v3's returned `( n flag )` in base 10. Executed on the golden model's host node (2026-10-04) against C in eight bases, including a digit that carries out of the low cell. `NUMBER` leaves its caller 3 data cells and 2 return entries; the interpreter does not use it (it has a single-cell conversion of its own that needs far less of the stack). |
| `S"` `(s")` `[']` | CC | Compiler words. |
| `LITERAL` `[LITERAL]` (placeholders) | RET | The working `LITERAL` is in §5.17. |
@@ -674,18 +674,18 @@ The storage service belongs to the Artemis role, now a device node.
| Word | Fate | Notes |
| --- | --- | --- |
| `HERE` `ALIGN` `ALLOT` `,` `C,` `2,` `PAD` `LATEST` | CC | The dictionary lives on the host node; source in `v4/capsule/dict.v4`. The dictionary pointer `DP` is a byte address, so `C,` packs four characters to a cell; `HERE` is the next whole cell (a word address); `,` and `ALLOT` align first. An address unit is a cell (D-1), so `ALLOT` counts cells where v3's counted bytes; a negative count gives space back. `2,` stores the low cell first, as v3. Going outside the dictionary space stores nothing, moves nothing and sets `NODE-ERROR`. `PAD` is a fixed scratch area, as in v3, given as a byte address. `LATEST` is the xt of the newest entry, 0 if there is none (v3's pushes `HERE`, which looks like a bug; reported, not changed). Executed on the golden model's host node (2026-10-04). `,` and `ALLOT` leave their caller 6 data cells and 5 return entries. |
| `HERE` `ALIGN` `ALLOT` `,` `C,` `2,` `PAD` `LATEST` | CC | The dictionary lives on the host node; source in `v4/capsule/dict.v4`. The dictionary pointer `DP` is a byte address, so `C,` packs four characters to a cell; `HERE` is the next whole cell (a word address); `,` and `ALLOT` align first. An address unit is a cell (D-1), so `ALLOT` counts cells where v3's counted bytes; a negative count gives space back. `2,` stores the low cell first, as v3. Going outside the dictionary space stores nothing, moves nothing and sets `NODE-ERROR`. `PAD` is a fixed scratch area, as in v3, given as a byte address. `LATEST` is the xt of the newest entry, 0 if there is none (v3's pushes `HERE`, which looks like a bug; reported, not changed). Executed on the golden model's host node (2026-10-04). `,` calls nothing and leaves its caller 7 data cells and 8 return entries; `ALLOT` leaves 7 and 7. |
| `SP@` `SP!` | RET | No visible stack pointer (D-2). |
### 5.13 Dictionary manipulation
| Word | Fate |
| --- | --- |
| `FIND` | CC | `( -- xt \| 0 )`, FORTH-79 and v3 alike: the compilation address of the next word in the input stream, or 0 if it is not in the dictionary. `32 WORD` then a search from the newest entry back, skipping hidden ones; names are case sensitive, as v3, and 31 characters are significant (FORTH-79). Executed on the golden model's host node (2026-10-04) against a list kept in C, 300 entries. Leaves its caller 4 data cells and 2 return entries. |
| `'` | CC | `( -- addr )`, FORTH-79: the parameter field address of the next word in the input stream; not found is an error (0, and `NODE-ERROR` set). v3's `'` returned what `FIND` returns. For a word that is code the two are the same address; for a data word the parameter field is the cell after its code, which is what FORTH-79's `n ' NAME !` needs. Its compiling behaviour comes with the compiler words. Executed on the golden model's host node (2026-10-04). |
| `FIND` | CC | `( -- xt \| 0 )`, FORTH-79 and v3 alike: the compilation address of the next word in the input stream, or 0 if it is not in the dictionary. `32 WORD` then a search from the newest entry back, skipping hidden ones; names are case sensitive, as v3, and 31 characters are significant (FORTH-79). Executed on the golden model's host node (2026-10-04) against a list kept in C, 300 entries. Leaves its caller 7 data cells and 5 return entries. |
| `'` | CC | `( -- addr )`, FORTH-79: the parameter field address of the next word in the input stream; not found is an error (0, and `NODE-ERROR` set). v3's `'` returned what `FIND` returns. For a word that is code the two are the same address; for a data word the parameter field is the cell after its code, which is what FORTH-79's `n ' NAME !` needs. It is immediate: inside a definition the address is compiled as a literal (FORTH-79), where v3's `'` parsed when the definition ran. Executed on the golden model's host node (2026-10-04). |
| `>LINK` `LFA` `LINK>` `>NAME` `NFA` `NAME>` `CFA` `PFA` `>BODY` `TRAVERSE` | CC | The fields of an entry, all reached from its xt, as in v3 where the xt was the entry: `>LINK`/`LFA` the link's address, `LINK>` the older entry's xt, `>NAME`/`NFA` the byte address of the counted name, `NAME>` back to the xt, `CFA` the xt itself, `PFA`/`>BODY` the parameter field, `TRAVERSE` from a name's count byte to past its last character (for `n > 0`; otherwise unchanged, as v3). Executed on the golden model's host node (2026-10-04). |
| `SMUDGE` `HIDDEN` | CC | `SMUDGE` toggles the hidden flag of the newest entry; `HIDDEN` sets it. v3 refuses both outside compilation; that check comes with the compiler words. Executed on the golden model's host node (2026-10-04). |
| `INTERPRET` | CC | |
| `INTERPRET` | CC | FORTH-79; source in `v4/capsule/compile.v4`. Takes words from the input stream until it is exhausted: a word in the dictionary is executed, or, when compiling and not immediate, compiled; anything else must be a number in the current `BASE` (a single cell, with an optional leading minus), left on the stack or compiled as a literal. What is neither, or a compile-only word met while interpreting, abandons the line: compiling stops, the definition under way stays hidden, the rest of the input is skipped and `NODE-ERROR` is set (the message and the stack reset come with `QUIT`). Executed on the golden model's host node (2026-10-04). |
An entry, as `v4/capsule/dict.v4` builds it. A word's execution address (xt) is the address of its
code, and everything else is found from it:
@@ -713,7 +713,7 @@ it, `xt + 1`; for any other word the parameter field is the code itself.
| Word | Fate | Notes |
| --- | --- | --- |
| `(` `\` | CC | `(` is `41 WORD drop`; `\` is `SPAN @ >IN !`. In `v4/capsule/input.v4` under the names `PAREN` and `BACKSLASH`. Executed on the golden model's host node (2026-10-04). |
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller) |
| `EXECUTE` | CAP | `push ;` (tail-jumps to the xt; the xt returns to `EXECUTE`'s caller). Executed on the golden model's host node (2026-10-04). |
| `NOP` | OP | `nop` |
| `QUIT` `ABORT` `ABORT"` `(ABORT")` `COLD` `WARM` | CC | |
| `BYE` `REBOOT` | HERA | |
@@ -732,10 +732,35 @@ it, `xt + 1`; for any other word the parameter field is the code itself.
| Word | Fate | Notes |
| --- | --- | --- |
| `:` `;` `CREATE` `VARIABLE` `CONSTANT` `DOES>` `IMMEDIATE` `STATE` `[` `]` `LITERAL` `COMPILE` `[COMPILE]` `FORGET` `FENCE` | CC | |
| `:` `;` `EXIT` `IMMEDIATE` `STATE` `[` `]` `LITERAL` `COMPILE` `[COMPILE]` | CC | Source in `v4/capsule/compile.v4`, all to FORTH-79. `:` makes an entry and starts compiling; the word cannot be found until `;`, so a redefinition can use the old word. `;` lays the return, reveals the word and stops compiling. `LITERAL` compiles the number on the stack when compiling. `COMPILE xxx` lays code that compiles `xxx` when the word it is in runs; `[COMPILE] xxx` compiles `xxx` though it is immediate. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary; v3's `COMPILE` does not work (`: C1 COMPILE DUP ; IMMEDIATE : U3 C1 + ;` gives `DUP: Stack underflow`), reported, not fixed. |
| `CREATE` `VARIABLE` `CONSTANT` `DOES>` | CC | FORTH-79. A data word's code is one instruction word, a call to its run-time routine, and its parameter field is the cell after it: `(DOVAR)` is `pop ;` (the call's return address is the parameter field's address) and `(DOCON)` is `pop a! @ ;`. `VARIABLE` allots one cell, set to 0. `DOES>` lays a call to `(DOES)` and then `pop`: `(DOES)`, run by the defining word, rewrites the newest entry's code into a call to the code after it and returns from the defining word; that code's `pop` fetches the parameter field address. Executed on the golden model's host node (2026-10-04) against results recorded from the v3 binary. |
| `FORGET` `FENCE` | CC | |
| `LIT` | OP | `@p` |
| `does_rt` | RET | Internal helper; `DOES>` is implemented by the compiler capsule. |
**How a word is compiled.** v4 code is native: a colon definition is instruction words, and
compiling a word into one lays down a call to it. Two flags on an entry change that. An **in-line**
word's code is a straight run of opcodes and literals ending in `;`, and that run is copied in place
of a call; this is fate IN (§0), and everything that touches the return stack must be in line, since a
call would bury what it works on. A **compile-only** word may not be executed by the interpreter.
`v4/capsule/forth.v4` holds the first words of the vocabulary flagged this way (`DUP`, `+`, `>R`, `I`,
`LEAVE`, `@`, the in-line variables and so on).
**The short stacks (D-2) and the compiler.** The interpreter and compiler run on the same ten-cell and
nine-entry stacks as the user's words, with the user's values beneath them. Two consequences are
built into the capsule:
- Every word of the capsule keeps what it works on in memory and has at most three cells of its own on
the data stack. Measured on the golden model: a line may have **6 values on the stack** while the
interpreter reads its next word, and defining and running a word leaves 4 cells under it untouched.
- Words called from the interpreter may nest **8 deep**; a `DO` loop takes two return entries, so two
nested loops leave four. `*` is therefore not `UM* drop` on the host node but N steps of `+*` with
only the count on the return stack; it gives the same low cell (what `+*` loses at the top of `T`
takes N shifts to reach the bit that moves into `A`, and the loop ends first).
A programme that v3 runs with more than that on its stacks does not run here; that is D-2, not the
compiler.
**The code generator** (`v4/capsule/codegen.v4`) is what these words are built on. It packs opcodes,
literals and branches into instruction words at `HERE`, by the rules of §1.2 and §2: slots filled left
to right, unused slots `nop`, a literal's value in the cell after its word, `;` `ex` and any branch
@@ -761,7 +786,9 @@ lays down was then run.
| Word | Fate | v4 definition / notes |
| --- | --- | --- |
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` `LEAVE` `CASE` `OF` `ENDOF` `ENDCASE` | CC | Compile-time structure words. Expansions per §2 and below. |
| `IF` `ELSE` `THEN` `BEGIN` `UNTIL` `AGAIN` `WHILE` `REPEAT` `DO` `?DO` `LOOP` `+LOOP` | CC | Compile-time structure words, immediate and compile-only; source in `v4/capsule/compile.v4`. Expansions per §2 and below. What an opening word leaves for the word that closes it goes on a **control-flow stack in memory** (32 cells), not on the data stack, with a number saying which word left it; a closing word that finds the wrong number, or nothing, abandons the line. A loop that tests at its end (`UNTIL`) must drop the flag on both ways out, so `BEGIN` lays `jump L0 Ld: drop L0:` and `UNTIL` lays `if Ld drop`. Executed on the golden model's host node (2026-10-04): the code laid down for each is word for word what the text assembler makes of the expansion given here, and the programmes run give what the v3 binary gave. |
| `LEAVE` `I` `J` `UNLOOP` | CC | In-line words (below), compile-only. |
| `CASE` `OF` `ENDOF` `ENDCASE` | CC | |
| `EXIT` | OP | `;` |
| `(BRANCH)` | OP | `jump` |
| `(0BRANCH)` | IN | `if L … drop` (§2) — executed on the golden model (2026-10-03) as `IF 1 ELSE 2 THEN`, including results recorded from the v3 binary. |
+2 -2
View File
@@ -75,12 +75,12 @@ endif
#
# Both binaries depend on this Makefile, so a flag change rebuilds them.
define TEST_RULE
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
$(2) $$(SRCS) -o $$@
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile
$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile
@mkdir -p $(BINDIR)
$$(CC) $$(CSTD) $$(WARN) -O1 -g -fno-omit-frame-pointer \
-fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \
+49 -37
View File
@@ -19,14 +19,16 @@
\ bits whose lowest is bit 27 - 5k, and a branch there has 27 - 5k address bits.
\
\ Rests on core.v4 and dict.v4. Constants the loader supplies:
\ (CG) word address of nine cells: the word being built, the next
\ free slot, the number of literals it owes, and up to six of them
\ (CG) word address of thirteen cells of scratch for this file
\ CG-BSLOTS how many slots, counted from slot 0, may hold a branch
macro (CUR) (CG) endmacro
macro (SLOT) (CG) 1 + endmacro
macro (NLIT) (CG) 2 + endmacro
macro (LITS) (CG) 3 + endmacro
\ Like WORD, these run under whatever the user has on the stacks, so what they
\ work on is in (CG) and they keep at most three cells of their own on the
\ data stack (D-2):
\ (CG)+0 the word being built (CG)+1 its next free slot
\ (CG)+2 how many literals it owes, and (CG)+3 .. (CG)+8 the literals
\ (CG)+9 the opcode (OP,) was given (CG)+10 the ref (BRANCH>) is making
\ (CG)+11 the target a branch is to reach (CG)+12 which literal (FLUSH) is at
\ ( x n -- x' ) x shifted left n places, n >= 0
: (LSHIFT) if Z -1 + push L: 2* next L ; Z: drop ;
@@ -38,67 +40,77 @@ macro (LITS) (CG) 3 + endmacro
: (MASK) (LOWBIT) 1 SWAP (LSHIFT) -1 + ;
\ ( -- ) forget the word being built
: (CG-RESET) 0 (CUR) a! ! 0 (SLOT) a! ! 0 (NLIT) a! ! ;
: (CG-RESET) 0 (CG) a! ! 0 (CG)+1 a! ! 0 (CG)+2 a! ! ;
\ ( op -- ) into the next free slot. The slot must be free.
\ ( op -- ) into the next free slot. The slot must be free. The shift is
\ (LSHIFT) in line; the slot's lowest bit is never bit 0, so it shifts at
\ least once.
: (PUT)
(SLOT) a! @ dup 1 + ! \ op k
(LOWBIT) (LSHIFT)
(CUR) a! @ + ! ;
(CG)+1 a! @ dup 1 + ! \ op k
(LOWBIT) -1 + push L: 2* next L
(CG) a! @ + ! ;
\ ( -- ) append the word being built, its unused slots nop, then the
\ literals it owes; nothing if no slot is in use
: (FLUSH)
(SLOT) a! @ if EMPTY drop
P: (SLOT) a! @ -6 + if FULL drop 28 (PUT) jump P
(CG)+1 a! @ if EMPTY drop
P: (CG)+1 a! @ -6 + if FULL drop 28 (PUT) jump P
FULL: drop
(CUR) a! @ ,
0 L: dup (NLIT) a! @ xor if DONE
drop dup (LITS) + a! @ , 1 + jump L
DONE: drop drop
(CG) a! @ ,
0 (CG)+12 a! !
L: (CG)+12 a! @ (CG)+2 a! @ xor if DONE
drop (CG)+12 a! @ dup 1 + ! (CG)+3 + a! @ ,
jump L
DONE: drop
jump (CG-RESET)
EMPTY: drop ;
\ ( op -- ) any opcode that is neither a branch nor @p
: (OP,)
dup push (PUT) pop
if ENDS -1 + if ENDS \ ; and ex end the word
drop (SLOT) a! @ -6 + if ENDS drop ; \ and so does its last slot
dup (CG)+9 a! ! (PUT)
(CG)+9 a! @ if ENDS -1 + if ENDS \ ; and ex end the word
drop (CG)+1 a! @ -6 + if ENDS drop ; \ and so does its last slot
ENDS: drop jump (FLUSH)
\ ( x -- ) @p and its value
: (LIT,)
(NLIT) a! @ dup 1 + ! \ x n
(LITS) + a! !
(CG)+2 a! @ dup 1 + ! \ x n
(CG)+3 + a! !
8 (PUT)
(SLOT) a! @ -6 + if FULL drop ;
(CG)+1 a! @ -6 + if FULL drop ;
FULL: drop jump (FLUSH)
\ ( -- addr ) where the next word will go: a branch target
: (LABEL) (FLUSH) HERE ;
\ ( op -- ref ) a branch (jump 2, call 3, next 5, if 6, -if 7) whose address
\ is filled in later by (RESOLVE). ref is the branch's word address times 8,
\ plus its slot.
: (BRANCH>)
(SLOT) a! @ CG-BSLOTS - -if MOVE drop jump PLACED
\ ( op -- ) a branch (jump 2, call 3, next 5, if 6, -if 7) whose address is
\ not filled in yet. Its ref -- the branch's word address times 8, plus its
\ slot -- is left in (CG)+10. It ends by jumping to (FLUSH), so that the
\ chain of calls under it is one shorter.
: (BRANCH0)
(CG)+1 a! @ CG-BSLOTS - -if MOVE drop jump PLACED
MOVE: drop (FLUSH)
PLACED:
\ the word will be appended at HERE; its literals, if any, come after it
HERE 2* 2* 2* (SLOT) a! @ + push
HERE 2* 2* 2* (CG)+1 a! @ + (CG)+10 a! !
(PUT)
6 (SLOT) a! ! (FLUSH)
pop ;
6 (CG)+1 a! ! jump (FLUSH)
\ ( target ref -- ) put the address into the branch at ref
: (RESOLVE)
dup 7 and (MASK) push \ target ref R: mask
2/ 2/ 2/ a! \ target A: the branch's word
pop dup push and \ the address bits
\ ( ref -- ) put the address in (CG)+11 into the branch at ref
: (RESOLVE1)
dup 7 and (MASK) push \ ref R: mask
2/ 2/ 2/ a! \ A: the branch's word
(CG)+11 b! @b pop dup push and \ the address bits
@ pop inv and + ! ;
\ ( target ref -- ) put the address into the branch at ref
: (RESOLVE) SWAP (CG)+11 a! ! jump (RESOLVE1)
\ ( op -- ref ) the same, leaving the ref, for (RESOLVE) to fill in later
: (BRANCH>) (BRANCH0) (CG)+10 a! @ ;
\ ( target op -- ) a branch to a known address
: (BRANCH,) (BRANCH>) jump (RESOLVE)
: (BRANCH,) SWAP (CG)+11 a! ! (BRANCH0) (CG)+10 a! @ jump (RESOLVE1)
: (JUMP,) ( addr -- ) 2 jump (BRANCH,)
: (CALL,) ( addr -- ) 3 jump (BRANCH,)
+348
View File
@@ -0,0 +1,348 @@
\ compile.v4 -- the interpreter's inner loop, and the defining and compiling
\ words.
\
\ DECOMPOSITION.md 5.17 and 5.18: STATE [ ] : ; EXIT IMMEDIATE LITERAL COMPILE
\ [COMPILE] ' EXECUTE CREATE VARIABLE CONSTANT DOES>, the control structures
\ IF ELSE THEN BEGIN UNTIL AGAIN WHILE REPEAT DO ?DO LOOP +LOOP, and INTERPRET,
\ which they cannot be run without. Part of the compiler capsule. Rests on
\ core.v4, input.v4, dict.v4 and codegen.v4.
\
\ HOW A WORD IS COMPILED. v4 code is native: a colon definition is
\ instruction words, and compiling a word into one lays down a call to it.
\ Two flags on an entry (dict.v4) change that:
\ inline its code is a straight run of opcodes and literals ending
\ in ; -- that run is copied in place of a call. Everything
\ that touches the return stack must be, since a call would
\ bury what it works on (DECOMPOSITION.md 0, fate IN).
\ compile-only it may not be executed by the interpreter.
\ An immediate word is executed even when compiling.
\
\ THE STACKS ARE SHORT (D-2): ten cells of data, nine return entries, and no
\ way to see how full they are. The interpreter and compiler run on the same
\ stacks as the user's words, with the user's values beneath them, so:
\ - every word here keeps what it works on in (C), and has at most three
\ cells of its own on the data stack at any moment;
\ - what an IF or a DO leaves for its THEN or LOOP goes on a control-flow
\ stack in memory, not on the data stack, so structures may nest as
\ deep as that stack (32 cells) allows whatever the user has stacked.
\
\ Constants the loader supplies:
\ STATE word address of the variable: non-zero while compiling
\ (C) word address of eight cells of scratch for this file:
\ (C)+0 (C)+1 (C)+4 the in-liner's word, literal pointer and slot
\ (C)+2 LATEST before a header is made (C)+3 a data word's routine
\ (C)+6 (C)+7 a branch and a tag a control word is holding
\ (CFP) word address of the control-flow stack's pointer
\ CFBASE CFEND word addresses of its first cell and of the cell past its last
macro ROT push SWAP pop SWAP endmacro
\ ---- errors ------------------------------------------------------------------
\ ( -- ) give up the line: stop compiling, forget the word being built and
\ whatever control structures were open, skip the rest of the input, and set
\ NODE-ERROR. A definition that was under way stays hidden. (QUIT will add
\ the message and the stack reset.)
: (ABANDON)
0 STATE a! ! (CG-RESET)
CFBASE (CFP) a! !
SPAN a! @ >IN a! !
NODE-ERROR b! -1 !b ;
\ ---- the control-flow stack ----------------------------------------------------
\ ( x -- ) push; a full stack abandons the line
: >CF
(CFP) a! @ CFEND xor if FULL
drop (CFP) a! @ dup 1 + ! a! ! ;
FULL: drop drop jump (ABANDON)
\ ( -- x ) pop; 0 from an empty stack, which no opening word leaves
: CF>
(CFP) a! @ CFBASE xor if EMPTY
drop (CFP) a! @ -1 + dup ! a! @ ;
EMPTY: ;
\ ---- compiling one word ------------------------------------------------------
\ ( x n -- x' ) x shifted right n places; the bits that come in at the top
\ are masked off by the caller
: (RSHIFT) if Z -1 + push L: 2/ next L ; Z: drop ;
\ ( xt -- ) copy the in-line word at xt into the definition being built
: (INLINE,)
W: dup a! @ (C) a! ! 1 + (C)+1 a! ! \ the word, and its literals after it
0 (C)+4 a! ! \ slot
S: (C)+4 a! @ (LOWBIT) (C) a! @ SWAP (RSHIFT) 31 and \ op
if END \ ; ends the copy
dup -8 + if LIT
drop dup -28 + if NOP \ nop is padding
drop (OP,)
NXT: (C)+4 a! @ 1 + dup ! -6 + if NEXTW drop jump S
NEXTW: drop (C)+1 a! @ jump W \ the next instruction word follows the literals
LIT: drop drop (C)+1 a! @ dup 1 + ! a! @ (LIT,) jump NXT
NOP: drop drop jump NXT
END: drop ;
\ ( xt -- ) compile the word at xt: in line if it is flagged so, else a call
: (COMPILE,)
dup (FLAGS) a! @ 8 and if CALL drop jump (INLINE,)
CALL: drop jump (CALL,)
header EXECUTE
: EXECUTE ( xt -- ) push ;
\ ---- the interpreter's inner loop ---------------------------------------------
\ ( -- n flag ) the word WORD has just left, as a single-cell number in the
\ current BASE, and -1; or 0 0 if it is not one. A leading minus sign is
\ allowed. This is what the interpreter uses: NUMBER (input.v4) makes a
\ double and needs more of the stack than can be spared here.
\ (P)+2 the number so far, (P)+3 where it is in the word, (P)+4 the word's
\ length, (P)+5 the sign, (P)+6 the base.
: (NUM?)
WBUF C@ if EMPTY
(P)+4 a! ! 0 (P)+2 a! ! 1 (P)+3 a! ! 0 (P)+5 a! !
(BASE) (P)+6 a! !
WBUF+1 C@ -45 + if NEG drop jump DIGITS
NEG: drop -1 (P)+5 a! ! 2 (P)+3 a! !
(P)+4 a! @ -1 + if EMPTY drop \ a sign alone
DIGITS:
L: (P)+4 a! @ (P)+3 a! @ - -if MORE
drop (P)+2 a! @ (P)+5 a! @ if POS drop NEGATE -1 ;
POS: drop -1 ;
MORE: drop
(P)+3 a! @ dup 1 + ! WBUF + C@ (DIGIT) \ d
-if DIGIT jump BAD1
DIGIT: dup (P)+6 a! @ - -if BAD2 drop
(P)+2 a! @ (P)+6 a! @ STAR + (P)+2 a! !
jump L
BAD2: drop
BAD1: drop 0 0 ;
EMPTY: drop 0 0 ;
\ ( -- ) FORTH-79: take words from the input stream until it is exhausted.
\ A word in the dictionary is executed, or, when compiling and not immediate,
\ compiled. Anything else must be a number in the current BASE: it is left
\ on the stack, or compiled as a literal. What is neither, or a compile-only
\ word met while interpreting, abandons the line.
header INTERPRET
: INTERPRET
L: 32 WORD dup C@ if EOL
drop (LOOKUP) if NUM
dup (FLAGS) a! @ \ xt flags
STATE a! @ if INTERP
drop 1 and if COMP \ compiling: immediate words still run
drop EXECUTE jump L
COMP: drop (COMPILE,) jump L
INTERP: drop 16 and if RUN
drop drop jump (ABANDON)
RUN: drop EXECUTE jump L
NUM: drop (NUM?) if BAD
drop
STATE a! @ if KEEP drop (LIT,) jump L
KEEP: drop jump L
BAD: drop drop jump (ABANDON)
EOL: drop drop ;
\ ---- state ---------------------------------------------------------------------
header [ immediate
: LEFT-BRACKET 0 STATE a! ! ;
header ]
: RIGHT-BRACKET -1 STATE a! ! ;
\ ( x -- ) FORTH-79: if compiling, compile x as a literal
header LITERAL immediate
: LITERAL STATE a! @ if NO drop jump (LIT,) NO: drop ;
\ ( -- addr ) FORTH-79: the next word's parameter field address; if
\ compiling, compiled as a literal
header ' immediate
: TICK (') jump LITERAL
\ ---- colon definitions -----------------------------------------------------------
\ ( -- flag ) an entry for the next word in the input stream; -1 if one was
\ made. (C)+2 holds LATEST meanwhile, to tell.
: (NAMED)
LATEST (C)+2 a! !
32 WORD (HEADER)
LATEST (C)+2 a! @ xor if NONE drop -1 ;
NONE: ;
\ FORTH-79: start a definition; it cannot be found until ; ends it
header :
: COLON
(NAMED) if NONE drop
HIDDEN (CG-RESET) CFBASE (CFP) a! ! jump RIGHT-BRACKET
NONE: drop ;
\ FORTH-79: compile the return, let the word be found, stop compiling
header ; immediate compile-only
: SEMICOLON
0 (OP,)
LATEST (FLAGS) a! @ -3 and !
jump LEFT-BRACKET
\ FORTH-79: return from the definition at this point
header EXIT immediate compile-only
: EXIT 0 jump (OP,)
header IMMEDIATE
: IMMEDIATE LATEST if NONE (FLAGS) a! @ 1 OR ! ; NONE: drop ;
\ COMPILE xxx FORTH-79: when the word this is in runs, xxx is compiled
header COMPILE immediate compile-only
: COMPILE
32 WORD (LOOKUP) if MISSING
(LIT,) &(COMPILE,) jump (CALL,)
MISSING: drop jump (ABANDON)
\ [COMPILE] xxx FORTH-79: compile xxx even though it is immediate
header [COMPILE] immediate compile-only
: BRACKET-COMPILE
32 WORD (LOOKUP) if MISSING jump (COMPILE,)
MISSING: drop jump (ABANDON)
\ ---- data words ---------------------------------------------------------------------
\ A data word's code is one call to its run-time routine; the call leaves the
\ address of the cell after it -- the parameter field -- on the return stack.
: (DOVAR) pop ; \ leave the parameter field address
: (DOCON) pop a! @ ; \ leave what it holds
\ ( -- flag ) an entry for the next word, flagged data, its code a call to
\ the routine whose address is in (C)+3; -1 if one was made
: (DATA)
LATEST (C)+2 a! ! \ (NAMED), in line: the calls
32 WORD (HEADER) \ below this are deep enough
LATEST (C)+2 a! @ xor if NONE drop
(CG-RESET)
LATEST (FLAGS) a! @ 4 OR !
(C)+3 a! @ (CALL,) -1 ;
NONE: ;
\ FORTH-79: an entry whose word leaves the address of its parameter field
header CREATE
: CREATE &(DOVAR) (C)+3 a! ! (DATA) drop ;
\ FORTH-79: CREATE and one cell. The standard leaves its first value to the
\ programme; here it is 0.
header VARIABLE
: VARIABLE &(DOVAR) (C)+3 a! ! (DATA) if NONE drop 0 jump , NONE: drop ;
\ ( n -- ) FORTH-79: an entry whose word leaves n
header CONSTANT
: CONSTANT &(DOCON) (C)+3 a! ! (DATA) if NONE drop jump , NONE: drop drop ;
\ FORTH-79: in : DEFINER CREATE ... DOES> ... ; the words after DOES> become
\ what each word DEFINER makes does, starting with its parameter field address
\ on the stack. (DOES), run by DEFINER, turns the newest entry's code into a
\ call to the code after it and leaves DEFINER; that code starts with pop,
\ which fetches the parameter field address the call left.
: (DOES) pop 402653184 + LATEST a! ! ; \ 402653184 is `call` in slot 0
header DOES> immediate compile-only
: DOES> &(DOES) (CALL,) 25 jump (OP,)
\ ---- control structures (5.18, section 2) ---------------------------------------------
\ All immediate and compile-only. Each puts what the word that closes it
\ needs on the control-flow stack, with a number on top saying which word put
\ it there: 1 IF, 2 ELSE, 3 BEGIN, 4 WHILE, 5 DO, 6 ?DO. A closing word that
\ finds the wrong number abandons the line.
\ ( tag wanted -- ) if they differ, abandon the line and leave the control
\ word that called this as well: its return address is dropped, so the
\ abandoning returns to the interpreter and nothing more is laid down.
: (PAIR) xor if OK drop pop drop jump (ABANDON) OK: drop ;
\ ( ref -- ) the branch at ref lands here
: (HERE!) (LABEL) SWAP jump (RESOLVE)
\ Native if leaves the flag, so both ways out of it drop it (section 2):
\ IF a THEN if L1 drop a jump L2 L1: drop L2:
\ IF a ELSE b THEN if L1 drop a jump L2 L1: drop b L2:
header IF immediate compile-only
: IF 6 (BRANCH>) >CF 23 (OP,) 1 jump >CF
header ELSE immediate compile-only
: ELSE
CF> 1 (PAIR) CF> (C)+6 a! ! \ the if
2 (BRANCH>) >CF \ the jump over the else part
(C)+6 a! @ (HERE!) 23 (OP,)
2 jump >CF
header THEN immediate compile-only
: THEN
CF> dup -1 + if PLAIN
drop 2 (PAIR) CF> jump (HERE!)
PLAIN: drop drop
CF> (C)+6 a! ! \ the if
2 (BRANCH>) >CF
(C)+6 a! @ (HERE!) 23 (OP,)
CF> jump (HERE!)
\ A loop that tests at its end jumps back with the flag still there, so it
\ jumps back to a drop laid just ahead of the body:
\ BEGIN a UNTIL jump L0 Ld: drop L0: a if Ld drop
\ BEGIN a WHILE b REPEAT jump L0 Ld: drop L0: a if Lx drop b jump L0 Lx: drop
\ BEGIN a AGAIN jump L0 Ld: drop L0: a jump L0
\ The control-flow stack holds Ld, L0 and 3.
header BEGIN immediate compile-only
: BEGIN
2 (BRANCH>) (C)+6 a! !
(LABEL) >CF 23 (OP,)
(LABEL) dup >CF (C)+6 a! @ (RESOLVE)
3 jump >CF
header UNTIL immediate compile-only
: UNTIL CF> 3 (PAIR) CF> drop CF> 6 (BRANCH,) 23 jump (OP,)
header AGAIN immediate compile-only
: AGAIN CF> 3 (PAIR) CF> (JUMP,) CF> drop ;
header WHILE immediate compile-only
: WHILE CF> 3 (PAIR) 3 >CF 6 (BRANCH>) >CF 23 (OP,) 4 jump >CF
header REPEAT immediate compile-only
: REPEAT
CF> 4 (PAIR) CF> (C)+6 a! ! \ the while
CF> 3 (PAIR) CF> (JUMP,) CF> drop
(C)+6 a! @ (HERE!) 23 jump (OP,)
\ DO loops: the limit and index are on the return stack, limit below (5.18).
: (do) over push push drop ;
: (?do) over over xor ;
: (inc) pop 1 + pop ; \ index' limit
: (+inc) dup a! pop + pop ; \ index' limit A: the step
: (differ) drop over ;
: (same) drop over over - ;
: (a-xor) a xor ;
: (again) drop push push ;
: (3drop) drop drop drop ;
\ lay <less>: ( i' lim -- i' lim s ), the top bit of s set when i' < lim
: (LESS,)
&(?do) (INLINE,) 7 (BRANCH>) >CF
&(differ) (INLINE,) 2 (BRANCH>) (C)+6 a! !
CF> (HERE!) &(same) (INLINE,)
(C)+6 a! @ jump (HERE!)
\ The end of a loop, after its increment and test. The control-flow stack
\ holds the body's address and 5, or for ?DO its skip, the body and 6.
\ -if EXIT drop push push jump BODY EXIT: drop drop drop
\ and for ?DO the landing of its skip: jump PAST SKIP: drop drop drop PAST:
: (LOOP-END)
CF> (C)+7 a! ! \ the tag
7 (BRANCH>) (C)+6 a! !
&(again) (INLINE,) CF> (JUMP,)
(C)+6 a! @ (HERE!) &(3drop) (INLINE,)
(C)+7 a! @ -5 + if PLAIN
drop (C)+7 a! @ 6 (PAIR)
2 (BRANCH>) (C)+6 a! !
CF> (HERE!) &(3drop) (INLINE,)
(C)+6 a! @ jump (HERE!)
PLAIN: drop ;
header DO immediate compile-only
: DO &(do) (INLINE,) (LABEL) >CF 5 jump >CF
header ?DO immediate compile-only
: ?DO &(?do) (INLINE,) 6 (BRANCH>) >CF 23 (OP,) &(do) (INLINE,) (LABEL) >CF 6 jump >CF
header LOOP immediate compile-only
: LOOP &(inc) (INLINE,) (LESS,) jump (LOOP-END)
header +LOOP immediate compile-only
: +LOOP &(+inc) (INLINE,) (LESS,) &(a-xor) (INLINE,) jump (LOOP-END)
+8
View File
@@ -13,6 +13,7 @@ macro NEGATE inv 1 + endmacro
macro NIP push drop pop endmacro
\ ---- section 4: multiply ------------------------------------------------
header UM*
: UM* ( u1 u2 -- ulo uhi )
over 1 and inv 1 + over 2/ and \ u1 u2 t0 t0 = u2 2/ if u1 odd
over a! N-1 push \ A: u2 R: loop count
@@ -26,6 +27,7 @@ macro NIP push drop pop endmacro
and 1 and a 2* + pop ;
\ ---- section 5.7: doubles ------------------------------------------------
header D+
: D+ ( d1 d2 -- d3 )
push over push push drop pop \ al bl R: bh ah
over over xor -if L1
@@ -36,11 +38,13 @@ macro NIP push drop pop endmacro
C0: pop pop + ;
C1: pop pop + 1 + ;
header DNEGATE
: DNEGATE ( d -- -d )
inv over if L1 drop push inv 1 + pop ;
L1: drop 1 + ;
\ ---- section 5.3: bytes --------------------------------------------------
header C@
: C@ ( baddr -- c )
dup 2/ 2/ a! 3 and
if K0 -1 + if K1 -1 + if K2
@@ -49,6 +53,7 @@ macro NIP push drop pop endmacro
K1: drop @ 7 FOR 2/ UNEXT 255 and ;
K0: drop @ 255 and ;
header C!
: C! ( c baddr -- )
dup 2/ 2/ a! 3 and push 255 and pop
if K0 -1 + if K1 -1 + if K2
@@ -58,6 +63,7 @@ macro NIP push drop pop endmacro
K0: drop @ -256 and + ! ;
\ ---- section 5.9: strings ------------------------------------------------
header CMOVE
: CMOVE ( src dst u -- )
-if OK drop drop drop NODE-ERROR b! -1 !b ;
OK: if DONE push
@@ -65,7 +71,9 @@ macro NIP push drop pop endmacro
DONE: drop drop drop ;
\ ---- section 5.10: the console -------------------------------------------
header EMIT
: EMIT ( c -- ) CONSOLE-TX b! !b ;
header KEY
: KEY ( -- c )
L: CONSOLE-STATUS b! @b if WAIT drop CONSOLE-RX b! @b ;
WAIT: drop jump L
+87 -34
View File
@@ -29,7 +29,7 @@
\ (LATEST) word address of the variable: the xt of the newest entry, or 0
\ DBASE DLIMIT byte addresses of the start and end of dictionary space
\ PAD byte address of the scratch area for strings, 84 bytes or more
\ (D) word address of two cells of scratch for this file
\ (D) word address of five cells of scratch for this file
\ PAD is in line.
macro 4* 2* 2* endmacro
@@ -37,103 +37,156 @@ macro 4/ 2/ 2/ endmacro
macro OR over inv and xor endmacro
\ ---- the space (5.12) ------------------------------------------------------
\ These reach DP through B and leave A alone, so that , and C, can keep what
\ they are storing in A while the space is taken: nothing of theirs is then
\ on the data stack under (DP+), which itself needs three cells (D-2).
: (DERR) ( -- 0 ) NODE-ERROR b! -1 !b 0 ;
\ ( n -- flag ) move DP by n bytes if it stays inside the dictionary space
\ and say -1; otherwise leave it, set NODE-ERROR and say 0.
\ and say -1; otherwise leave it, set NODE-ERROR and say 0. The two
\ subtractions are x + ~y + 1, which unlike the in-line - needs no return
\ entry: these words are called at the bottom of long chains of calls.
: (DP+)
DP a! @ +
dup DBASE - -if LOW drop drop jump (DERR)
LOW: drop DLIMIT over - -if HIGH drop drop jump (DERR)
HIGH: drop DP a! ! -1 ;
DP b! @b +
dup DBASE inv + 1 + -if LOW drop drop jump (DERR) \ new - DBASE
LOW: drop dup inv DLIMIT + 1 + -if HIGH drop drop jump (DERR) \ DLIMIT - new
HIGH: drop DP b! !b -1 ;
: HERE ( -- addr ) DP a! @ 3 + 4/ ;
: ALIGN ( -- ) DP a! @ 3 + -4 and DP a! ! ;
header HERE
: HERE ( -- addr ) DP b! @b 3 + 4/ ;
header ALIGN
: ALIGN ( -- ) DP b! @b 3 + -4 and !b ;
header ALLOT
: ALLOT ( n -- ) ALIGN 4* (DP+) drop ;
\ , calls nothing: the code generator calls it from as deep as anything goes.
header ,
: , ( x -- )
ALIGN HERE push 4 (DP+) if FULL drop pop a! ! ;
FULL: drop pop drop drop ;
a!
DP b! @b 3 + -4 and \ the next whole cell, as a byte address
dup inv DLIMIT + -3 + -if ROOM \ DLIMIT - (it + 4)
drop drop NODE-ERROR b! -1 !b ;
ROOM: drop dup 4 + !b \ DP is past it
4/ b! a !b ;
header C,
: C, ( c -- )
DP a! @ push 1 (DP+) if FULL drop pop C! ;
FULL: drop pop drop drop ;
a! 1 (DP+) if FULL
drop a DP b! @b -1 + jump C!
FULL: drop ;
header 2,
: 2, ( lo hi -- ) SWAP , , ; \ the low cell first, as v3
header LATEST
: LATEST ( -- xt ) (LATEST) a! @ ;
\ ---- the fields of an entry (5.13) -----------------------------------------
: (FLAGS) ( xt -- addr ) -3 + ;
header >LINK
: >LINK ( xt -- addr ) -1 + ;
header LFA
: LFA ( xt -- addr ) jump >LINK
header LINK>
: LINK> ( addr -- xt ) a! @ ;
header >NAME
: >NAME ( xt -- baddr ) -2 + a! @ 4* ;
header NFA
: NFA ( xt -- baddr ) jump >NAME
header NAME>
: NAME> ( baddr -- xt ) dup C@ 31 and 4 + 4/ push 4/ pop + 3 + ;
header CFA
: CFA ( xt -- xt ) ;
header PFA
: PFA ( xt -- addr ) dup (FLAGS) a! @ 4 and if CODE drop 1 + ; CODE: drop ;
header >BODY
: >BODY ( xt -- addr ) jump PFA
\ ( baddr n -- baddr' ) as v3: for n > 0, from a name's count byte to the
\ byte after its last character; otherwise the address unchanged.
header TRAVERSE
: TRAVERSE
-if NN drop ;
NN: if ZERO drop dup C@ 31 and + 1 + ;
ZERO: drop ;
header SMUDGE
: SMUDGE ( -- ) LATEST if NONE (FLAGS) a! @ 2 xor ! ; NONE: drop ;
header HIDDEN
: HIDDEN ( -- ) LATEST if NONE (FLAGS) a! @ 2 OR ! ; NONE: drop ;
\ ---- making an entry -------------------------------------------------------
\ (D)+0 where the name starts (D)+1 the name being looked up
\ (D)+2 the name being copied, or the entry being looked at
\ (D)+3 a count (D)+4 (CLIP)'s string
\ ( baddr -- baddr ) cut the counted string to 31 characters, in place
: (CLIP) dup C@ -32 + -if LONG drop ; LONG: drop 31 over C! ;
: (CLIP)
dup (D)+4 a! ! C@ -32 + -if LONG drop (D)+4 a! @ ;
LONG: drop 31 (D)+4 a! @ C! (D)+4 a! @ ;
\ ( -- bytes ) what an entry for a name of (D)+3 characters takes: its name
\ cells and three more
: (NEED) (D)+3 a! @ 4 + -4 and 12 + ;
\ ( baddr -- ) an entry for the counted string at baddr; its code starts at
\ HERE afterwards. An empty name, or no room, makes nothing and sets
\ NODE-ERROR.
: (HEADER)
(CLIP) dup C@ if EMPTY \ baddr len
dup (D)+2 a! ! \ (CLIP), in line
C@ -32 + -if LONG drop jump CLIPPED
LONG: drop 31 (D)+2 a! @ C!
CLIPPED:
(D)+2 a! @ C@ if EMPTY \ len
(D)+3 a! !
ALIGN
dup 4 + -4 and 12 + \ baddr len need bytes: name cells and three more
dup (DP+) if FULL drop NEGATE (DP+) drop \ there is room; DP back where it was
(NEED) (DP+) if FULL drop \ there is room;
(NEED) NEGATE (DP+) drop \ DP back where it was
HERE (D) a! ! \ where the name starts
dup C, \ baddr len
L: if DONE SWAP 1 + dup C@ C, SWAP -1 + jump L \ nothing on R across C,
DONE: drop drop
P: DP a! @ 3 and if ALIGNED drop 0 C, jump P
(D)+3 a! @ C,
L: (D)+3 a! @ if DONE -1 + !
(D)+2 a! @ 1 + dup ! C@ C,
jump L
DONE: drop
P: DP b! @b 3 and if ALIGNED drop 0 C, jump P
ALIGNED: drop
0 , (D) a! @ , LATEST ,
HERE (LATEST) a! ! ;
FULL: drop drop
EMPTY: drop drop NODE-ERROR b! -1 !b ;
FULL: drop ; \ (DP+) has set NODE-ERROR
EMPTY: drop NODE-ERROR b! -1 !b ;
\ ---- looking a name up -----------------------------------------------------
\ ( baddr1 baddr2 -- flag ) two counted strings are the same
\ ( -- flag ) the name of the entry in (D)+2 is the counted string in (D)+1
: (NAME=)
over C@ 1 + \ b1 b2 n the count byte and the characters
L: if SAME push
over C@ over C@ xor if EQ drop drop drop pop drop 0 ;
EQ: drop 1 + push 1 + pop pop -1 + jump L
SAME: drop drop drop -1 ;
(D)+2 a! @ >NAME (D) a! !
(D)+1 a! @ C@ 1 + (D)+3 a! ! \ the count byte and the characters
L: (D)+3 a! @ if SAME -1 + dup ! \ k
dup (D) a! @ + C@ \ k c1
SWAP (D)+1 a! @ + C@ \ c1 c2
xor if EQ drop 0 ;
EQ: drop jump L
SAME: drop -1 ;
\ ( baddr -- xt | 0 ) the newest visible entry named by the counted string
: (LOOKUP)
(CLIP) (D) 1 + a! !
(CLIP) (D)+1 a! !
LATEST
L: if END
dup (FLAGS) a! @ 2 and if VISIBLE jump OLDER
VISIBLE: drop dup >NAME (D) 1 + a! @ (NAME=) if OLDER drop ;
OLDER: drop >LINK a! @ jump L
(D)+2 a! !
(D)+2 a! @ (FLAGS) a! @ 2 and if VISIBLE drop jump OLDER
VISIBLE: drop (NAME=) if DIFF drop (D)+2 a! @ ;
DIFF: drop
OLDER: (D)+2 a! @ >LINK a! @ jump L
END: ;
\ ( -- xt | 0 ) FORTH-79: the compilation address of the next word in the
\ input stream, or 0 if it is not in the dictionary.
header FIND
: FIND 32 WORD jump (LOOKUP)
\ ( -- addr ) FORTH-79: the parameter field address of the next word in the
\ input stream. Not found is an error: 0, and NODE-ERROR is set.
: ' 32 WORD (LOOKUP) if MISSING jump PFA
\ input stream. Not found is an error: 0, and NODE-ERROR is set. This is '
\ when interpreting; compile.v4 adds what ' does when compiling.
: (') 32 WORD (LOOKUP) if MISSING jump PFA
MISSING: drop jump (DERR)
+72
View File
@@ -0,0 +1,72 @@
\ forth.v4 -- the first words of the FORTH vocabulary: the ones that are
\ opcodes or short runs of them, and the variables that are in-line addresses.
\
\ Each is the definition DECOMPOSITION.md gives (sections 4, 5.1 - 5.5, 5.18).
\ A word flagged inline is copied into a definition in place of a call; the
\ ones that touch the return stack are compile-only as well. Rests on core.v4
\ and compile.v4's loader constants.
\ ---- stack ----
header DUP inline : DUP dup ;
header DROP inline : DROP drop ;
header OVER inline : OVER over ;
header SWAP inline : SWAP' SWAP ;
header ROT inline : ROT' ROT ;
header -ROT inline : -ROT SWAP push SWAP pop ;
header ?DUP : ?DUP if Z dup ; Z: ;
header 2DUP inline : 2DUP over over ;
header 2DROP inline : 2DROP drop drop ;
\ ---- return stack: in line, and only inside a definition ----
header >R inline compile-only : >R push ;
header R> inline compile-only : R> pop ;
header R@ inline compile-only : R@ pop dup push ;
header I inline compile-only : I pop dup push ;
header J inline compile-only : J pop pop pop dup push a! push push a ;
header LEAVE inline compile-only : LEAVE pop pop drop dup push push ;
header UNLOOP inline compile-only : UNLOOP pop drop pop drop ;
\ ---- memory ----
header @ inline : FETCH a! @ ;
header ! inline : STORE a! ! ;
header +! inline : +! a! @ + ! ;
header CELLS inline : CELLS ; \ an address unit is a cell (D-1)
\ ---- arithmetic ----
header + inline : PLUS + ;
header - inline : MINUS - ;
\ The low cell of the product, by N steps of +* with nothing but the count on
\ the return stack. UM* drop gives the same cell but needs two more return
\ entries, which a word inside two DO loops does not have (D-2). The low
\ cell is exact: what +* loses at the top of T (D-3) takes N shifts to reach
\ the bit that moves into A, and the loop ends first.
header * : STAR a! 0 N-1 FOR +* UNEXT drop drop a ;
header NEGATE inline : NEGATE' NEGATE ;
header 1+ inline : 1+ 1 + ;
header 1- inline : 1- -1 + ;
header 2+ inline : 2+ 2 + ;
header 2- inline : 2- -2 + ;
header 2* inline : TWO* 2* ;
header 2/ inline : TWO/ 2/ ;
\ ---- logic and comparison ----
header AND inline : AND and ;
header XOR inline : XOR xor ;
header OR inline : OR' OR ;
header 0= : 0= if L1 drop 0 ; L1: drop -1 ;
header 0< : 0< -if L1 drop -1 ; L1: drop 0 ;
header NOT : NOT jump 0=
header = : EQUALS xor jump 0=
header < : LESS
over over xor -if SAME drop drop jump 0<
SAME: drop - jump 0<
header > : GREATER SWAP jump LESS
\ ---- in-line addresses and constants ----
header BL inline : BL' BL ;
header TIB inline : TIB' TIB ;
header >IN inline : >IN' >IN ;
header SPAN inline : SPAN' SPAN ;
header BASE inline : BASE' BASE ;
header PAD inline : PAD' PAD ;
header STATE inline : STATE' STATE ;
+71 -47
View File
@@ -12,12 +12,13 @@
\ SPAN word address of the variable: how many characters the last
\ EXPECT or QUERY read
\ WBUF byte address of WORD's buffer, 257 bytes
\ (P) word address of six cells of scratch for this file
\ (P) word address of eight cells of scratch for this file
\ TIB >IN SPAN and BL are in-line: written in a definition they are literals.
macro BL 32 endmacro
\ ( -- baddr u ) the input buffer and how much of it is in use
header SOURCE
: SOURCE TIB SPAN a! @ -if P drop 0 P: ;
\ ( baddr n -- ) FORTH-79: characters from the terminal are stored from
@@ -26,6 +27,7 @@ macro BL 32 endmacro
\ must hold n + 1 bytes. No action for n <= 0. SPAN is how many characters
\ were stored (SPAN is not FORTH-79; v3 has it). A line longer than n leaves
\ the rest, its new-line included, to be read next.
header EXPECT
: EXPECT
-if NN drop drop ; \ n < 0
NN: if ZERO
@@ -39,23 +41,24 @@ macro BL 32 endmacro
ZERO: drop drop ;
\ ( -- ) FORTH-79: up to 80 characters, or a line, into TIB; >IN to 0.
header QUERY
: QUERY TIB 80 EXPECT 0 >IN a! ! ;
\ ---- WORD ------------------------------------------------------------------
\ (P)+0 the delimiter, (P)+1 the length of the text in TIB, (P)+2 the length
\ of the word (CONVERT uses (P)+2 too; the two never run inside each other).
macro (WDELIM) (P) endmacro
macro (WLEN) (P) 1 + endmacro
\ WORD is called from deep inside the interpreter and compiler, with whatever
\ the user has on the stacks beneath it, and the stacks are ten and nine deep
\ (D-2). So everything it works on is in (P), and it never has more than
\ three cells of its own on the data stack:
\ (P)+0 the delimiter (P)+1 the length of the text in TIB
\ (P)+2 the word's length (P)+3 how much of it is still to be copied
\ (P)+6 where it is in TIB (P)+7 where the word starts
\ (CONVERT and NUMBER use (P)+2 .. (P)+5; they and WORD never run inside
\ each other.)
: (LEFT) ( i -- i d ) dup (WLEN) a! @ - ; \ d < 0 while i is inside the text
: (CH) ( i -- i x ) dup TIB + C@ (WDELIM) a! @ xor ; \ x = 0 at a delimiter
: (SKIP) ( i -- i' ) \ past delimiters
L: (LEFT) -if E drop (CH) if S drop ;
S: drop 1 + jump L
E: drop ;
: (SCAN) ( i -- i' ) \ up to a delimiter
L: (LEFT) -if E drop (CH) if E drop 1 + jump L
E: drop ;
\ In line, not called: WORD may have only a few return entries to spare.
macro (LEFT) (P)+6 a! @ (P)+1 a! @ - endmacro \ ( -- d ) d < 0 while inside the text
macro (CH) (P)+6 a! @ TIB + C@ (P) a! @ xor endmacro \ ( -- x ) x = 0 at a delimiter
macro (STEP) (P)+6 a! @ 1 + ! endmacro
\ ( c -- baddr ) FORTH-79: characters are taken from TIB until the
\ delimiter c or the end of the text, leading delimiters ignored, and stored
@@ -63,26 +66,38 @@ macro (WLEN) (P) 1 + endmacro
\ ran out -- is stored after them and is not counted. >IN is left just past
\ that delimiter. With nothing left the count is 0. The count is a byte,
\ so a word longer than 255 characters is cut to 255.
header WORD
: WORD
255 and (WDELIM) a! !
SPAN a! @ -if A drop 0 A: (WLEN) a! !
>IN a! @ -if B drop 0 B:
(SKIP) dup push (SCAN) \ end R: start
pop over over - \ end start len
255 and (P) a! !
SPAN a! @ -if A drop 0 A: (P)+1 a! !
>IN a! @ -if B drop 0 B: (P)+6 a! !
SK: (LEFT) -if SKE drop (CH) if SKS drop jump SKIPPED \ past delimiters
SKS: drop (STEP) jump SK
SKE: drop
SKIPPED:
(P)+6 a! @ (P)+7 a! ! \ where it starts
SC: (LEFT) -if SCE drop (CH) if SCE drop (STEP) jump SC \ up to a delimiter
SCE: drop
(P)+6 a! @ (P)+7 a! @ - \ its length
dup -256 + -if CAP drop jump FITS CAP: drop drop 255 FITS:
dup WBUF C!
dup (P) 2 + a! ! \ the length waits in (P)+2, not on a stack
push TIB + WBUF 1 + pop CMOVE \ end
dup (P)+2 a! ! (P)+3 a! !
(P)+2 a! @ WBUF C!
\ copy it, last character first
CP: (P)+3 a! @ if COPIED -1 + dup ! \ k
dup (P)+7 a! @ + TIB + C@ \ k c
SWAP WBUF+1 + C!
jump CP
COPIED: drop
(LEFT) -if RANOUT
drop (WDELIM) a! @ (P) 2 + a! @ WBUF + 1 + C! \ the delimiter after the text
1 + >IN a! ! WBUF ;
RANOUT: drop 0 (P) 2 + a! @ WBUF + 1 + C!
>IN a! ! WBUF ;
drop (P) a! @ (P)+2 a! @ WBUF+1 + C! \ the delimiter after the text
(P)+6 a! @ 1 + >IN a! ! WBUF ;
RANOUT: drop 0 (P)+2 a! @ WBUF+1 + C!
(P)+6 a! @ >IN a! ! WBUF ;
\ ---- ENCLOSE ---------------------------------------------------------------
\ (P)+0 the delimiter, (P)+1 the string's address.
: (E?) ( i -- i k ) \ k: 0 at the end of the string, 1 at a delimiter, else 2
dup (WLEN) a! @ + C@ if END (WDELIM) a! @ xor if DEL drop 2 ;
dup (P)+1 a! @ + C@ if END (P) a! @ xor if DEL drop 2 ;
DEL: drop 1 ;
END: drop 0 ;
: (ESKIP) ( i -- i' ) L: (E?) -1 + if S drop ; S: drop 1 + jump L
@@ -91,8 +106,9 @@ macro (WLEN) (P) 1 + endmacro
\ ( baddr c -- baddr n1 n2 n3 ) in the zero-terminated string at baddr:
\ n1 the offset of the first character that is not c, n2 the offset of the
\ first c after it, n3 the offset of the first character after that run of c.
header ENCLOSE
: ENCLOSE
255 and (WDELIM) a! ! dup (WLEN) a! !
255 and (P) a! ! dup (P)+1 a! !
0 (ESKIP) dup (ESCAN) dup (ESKIP) ;
\ ---- CONVERT and NUMBER ----------------------------------------------------
@@ -111,40 +127,48 @@ macro (WLEN) (P) 1 + endmacro
\ it is multiplied by BASE, until one is not a digit; baddr2 is its address.
\ (P)+2 holds the digit while the double is multiplied and (P)+5 the address,
\ so nothing waits on the return stack across the calls.
header CONVERT
: CONVERT
L: 1 + dup (P) 5 + a! ! C@ (DIGIT) \ lo hi n
-if DIG drop (P) 5 + a! @ ;
L: 1 + dup (P)+5 a! ! C@ (DIGIT) \ lo hi n
-if DIG drop (P)+5 a! @ ;
DIG: dup (BASE) - -if BIG
drop (P) 2 + a! ! \ lo hi
drop (P)+2 a! ! \ lo hi
(BASE) UM* drop \ lo hi*base
SWAP (BASE) UM* \ hi*base plo phi
push SWAP pop + \ plo hi'
(P) 2 + a! @ 0 D+
(P) 5 + a! @ jump L
BIG: drop drop (P) 5 + a! @ ;
(P)+2 a! @ 0 D+
(P)+5 a! @ jump L
BIG: drop drop (P)+5 a! @ ;
\ ( baddr -- d ) FORTH-79: the counted string at baddr as a signed double in
\ the current BASE; it may begin with a minus sign. If it is empty, is only
\ a sign, or holds anything that is not a digit, the result is 0 and
\ NODE-ERROR is set. The character after the string must not be a digit
\ ( baddr -- d flag ) the counted string at baddr as a signed double in the
\ current BASE, and -1; it may begin with a minus sign. If it is empty, is
\ only a sign, or holds anything that is not a digit: 0 0 and 0. The character after the string must not be a digit
\ (WORD puts the delimiter or a zero there); if it is, that too is reported
\ as an error. (P)+3 the address of its last character, (P)+4 the sign.
: NUMBER
: (NUMBER?)
dup C@ if ERR2
over + (P) 3 + a! ! \ baddr
over + (P)+3 a! ! \ baddr
dup 1 + C@ -45 + if NEG
drop 0 (P) 4 + a! ! jump GO
NEG: drop -1 (P) 4 + a! ! 1 +
dup (P) 3 + a! @ xor if ERR2 drop
drop 0 (P)+4 a! ! jump GO
NEG: drop -1 (P)+4 a! ! 1 +
dup (P)+3 a! @ xor if ERR2 drop
GO: push 0 0 pop CONVERT \ lo hi baddr2
(P) 3 + a! @ 1 + xor if FINE
(P)+3 a! @ 1 + xor if FINE
drop drop drop jump ERR0
FINE: drop (P) 4 + a! @ if POS drop jump DNEGATE
POS: drop ;
FINE: drop (P)+4 a! @ if POS drop DNEGATE -1 ;
POS: drop -1 ;
ERR2: drop drop
ERR0: NODE-ERROR b! -1 !b 0 0 ;
ERR0: 0 0 0 ;
\ ( baddr -- d ) FORTH-79. What is not a number gives 0 and sets NODE-ERROR.
header NUMBER
: NUMBER
(NUMBER?) if BAD drop ;
BAD: drop NODE-ERROR b! -1 !b ;
\ ---- comments (5.15) -------------------------------------------------------
\ PAREN skips to the closing parenthesis; BACKSLASH skips the rest of the line.
header ( immediate
: PAREN 41 WORD drop ;
header \ immediate
: BACKSLASH SPAN a! @ >IN a! ! ;
+20
View File
@@ -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
@@ -42,6 +46,15 @@
* may use other macros; it may not define a word, a label
* or a macro.
*
* header NAME a dictionary entry for NAME (capsule/dict.v4), laid down
* in front of the word that the next `:` starts, and
* linked to the header before it. NAME is taken as it
* stands -- it may be ( or \ or + -- and is at most 31
* characters. Between it and the `:` may come the flags
* immediate, data, inline and compile-only. The word after
* `:` is the assembler's name for the code, as always, and
* need not be the same: header + inline : PLUS + ;
*
* A name is looked up in this order: opcode, macro, number, constant, label
* (for a branch target), word. So an opcode's name cannot be redefined, and
* the FORTH words that share one (`@`, `!`, `+`, ...) are written as their
@@ -99,6 +112,9 @@ typedef struct {
char pool[V4_TEXT_POOL];
unsigned pool_used;
v4_cell latest; /* xt of the newest header, 0 if none */
int header_pending; /* a header waits for its `:` */
unsigned line;
int failed;
char message[224];
@@ -125,6 +141,10 @@ int v4_text_finish(v4_text *t);
/* The address of the word NAME, or -1 if there is none. */
v4_cell v4_text_word(const v4_text *t, const char *name);
/* The xt of the newest `header`, 0 if there has been none: what the
* dictionary's LATEST starts as. */
v4_cell v4_text_latest(const v4_text *t);
/* The next free word address. */
v4_cell v4_text_here(v4_text *t);
+121 -2
View File
@@ -50,11 +50,22 @@ static const v4_text_symbol *find(const v4_text *t, const char *s)
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, "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)
@@ -129,6 +140,75 @@ static int next_token(v4_text *t, reader *r, char *tok)
}
}
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)
@@ -220,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;
}
@@ -269,12 +349,19 @@ static void one_token(v4_text *t, reader *r, const char *tok)
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; }
@@ -340,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; }
@@ -357,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 */
}
@@ -371,6 +482,8 @@ void v4_text_begin(v4_text *t, v4_node *n, v4_cell origin)
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)
@@ -390,6 +503,7 @@ int v4_text_assemble(v4_text *t, const char *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;
}
@@ -446,6 +560,11 @@ v4_cell v4_text_word(const v4_text *t, const char *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);
+114
View File
@@ -0,0 +1,114 @@
/* host_map.h -- the memory map the host-node tests give the compiler capsule,
* and the loader that assembles it.
*
* The node's memory map is open (DECOMPOSITION.md D-4), so the tests choose
* one. Every test_host_*.c uses this one, so that the capsule's variables
* and buffers are in the same place in all of them:
*
* 16 .. the capsule's code, assembled from capsule/ *.v4
* DICT_W .. DICT_END_W dictionary space for what is defined at run time
* top of memory buffers, then variables, as below
*/
#ifndef V4_TESTS_HOST_MAP_H
#define V4_TESTS_HOST_MAP_H
#include "v4/text.h"
#include <stdio.h>
#define TOP ((v4_cell)V4_NODE_WORDS)
/* registers and single variables */
#define NODE_ERROR (TOP - 2)
#define CONSOLE_TX (TOP - 4)
#define CONSOLE_RX (TOP - 6)
#define CONSOLE_ST (TOP - 7)
#define BASE (TOP - 8)
#define TO_IN (TOP - 9)
#define SPAN (TOP - 10)
#define DP (TOP - 11)
#define LATEST (TOP - 12)
#define STATE (TOP - 13)
#define CFP (TOP - 14) /* the control-flow stack's pointer */
/* each capsule file's scratch cells */
#define PVARS (TOP - 24) /* (P): input.v4, 8 cells */
#define DVARS (TOP - 32) /* (D): dict.v4, 5 cells */
#define CGVARS (TOP - 48) /* (CG): codegen.v4, 13 cells */
#define CVARS (TOP - 58) /* (C): compile.v4, 8 cells */
/* buffers (word addresses; a byte address is four times this) */
#define CFS_W (TOP - 96) /* the control-flow stack, 32 cells */
#define CFS_CELLS 32
#define WBUF_W (TOP - 170) /* WORD's buffer: 65 cells = 260 bytes */
#define WBUF_CELLS 65
#define TIB_W (TOP - 440) /* the text input buffer: 260 cells = 1040 bytes */
#define PAD_W (TOP - 470) /* PAD: 21 cells = 84 bytes */
#define SBUF_W (TOP - 540) /* 64 cells for the tests' own strings */
#define WBUF (WBUF_W * 4)
#define TIB (TIB_W * 4)
#define PAD (PAD_W * 4)
#define SBUF (SBUF_W * 4)
/* dictionary space */
#ifndef DICT_W
#define DICT_W ((v4_cell)8192)
#endif
#ifndef DICT_END_W
#define DICT_END_W ((v4_cell)14336)
#endif
/* How many slots, from slot 0, a branch may sit in on a node this size: those
* whose address field reaches every word of it. */
static unsigned host_branch_slots(void)
{
unsigned k, slots = 0;
for (k = 0; k < 4; k++) if ((v4_ucell)v4_iword_slot_mask(k) >= (v4_ucell)(V4_NODE_WORDS - 1u)) slots = k + 1;
return slots;
}
/* Reset `n`, give the assembler every constant the capsule files ask for, and
* assemble the `count` files named. Returns 1 if all assembled; otherwise
* prints which did not and why. The caller may assemble more text and must
* call v4_text_finish. */
static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned count)
{
char path[512];
unsigned i;
v4_node_reset(n);
v4_text_begin(tx, n, 16);
v4_text_constant(tx, "N-1", V4_CELL_BITS - 1);
v4_text_constant(tx, "NODE-ERROR", NODE_ERROR);
v4_text_constant(tx, "CONSOLE-TX", CONSOLE_TX);
v4_text_constant(tx, "CONSOLE-RX", CONSOLE_RX);
v4_text_constant(tx, "CONSOLE-STATUS", CONSOLE_ST);
v4_text_constant(tx, "BASE", BASE);
v4_text_constant(tx, "TIB", TIB);
v4_text_constant(tx, ">IN", TO_IN);
v4_text_constant(tx, "SPAN", SPAN);
v4_text_constant(tx, "WBUF", WBUF);
v4_text_constant(tx, "(P)", PVARS);
v4_text_constant(tx, "DP", DP);
v4_text_constant(tx, "(LATEST)", LATEST);
v4_text_constant(tx, "DBASE", DICT_W * 4);
v4_text_constant(tx, "DLIMIT", DICT_END_W * 4);
v4_text_constant(tx, "PAD", PAD);
v4_text_constant(tx, "(D)", DVARS);
v4_text_constant(tx, "(CG)", CGVARS);
v4_text_constant(tx, "CG-BSLOTS", (v4_cell)host_branch_slots());
v4_text_constant(tx, "STATE", STATE);
v4_text_constant(tx, "(C)", CVARS);
v4_text_constant(tx, "(CFP)", CFP);
v4_text_constant(tx, "CFBASE", CFS_W);
v4_text_constant(tx, "CFEND", CFS_W + CFS_CELLS);
for (i = 0; i < count; i++) {
snprintf(path, sizeof path, "%s/%s", V4_CAPSULE_DIR, files[i]);
if (!v4_text_assemble_file(tx, path)) {
printf(" %s does not assemble: %s\n", files[i], v4_text_error(tx));
return 0;
}
}
return 1;
}
#endif /* V4_TESTS_HOST_MAP_H */
+5 -42
View File
@@ -21,25 +21,9 @@ static int failures = 0, checks = 0;
#define CANARY ((v4_cell)0x0C0FFEE5)
#define MAXU ((v4_ucell)~(v4_ucell)0)
/* The memory map is open (D-4); the test chooses it. */
#define TOP ((v4_cell)V4_NODE_WORDS)
#define NODE_ERROR (TOP - 2)
#define CONSOLE_TX (TOP - 4)
#define CONSOLE_RX (TOP - 6)
#define CONSOLE_ST (TOP - 7)
#define BASE (TOP - 8)
#define TO_IN (TOP - 9)
#define SPAN (TOP - 10)
#define PVARS (TOP - 16)
#define DP (TOP - 17)
#define LATEST (TOP - 18)
#define DVARS (TOP - 20)
#define CGVARS (TOP - 30) /* nine cells */
#define WBUF_W (TOP - 90)
#define TIB_W (TOP - 360)
#define PAD_W (TOP - 470)
#define DICT_W ((v4_cell)8192) /* dictionary space: words 8192 .. 12287 */
#define DICT_END_W ((v4_cell)12288)
#include "host_map.h"
static v4_node n, ref; /* the generator's node; the text assembler's */
static v4_exec_state es;
@@ -194,31 +178,10 @@ int main(void)
for (i = 0; i < 4; i++) if ((v4_ucell)v4_iword_slot_mask(i) >= (v4_ucell)(V4_NODE_WORDS - 1u)) bslots = i + 1;
CHECK(bslots == 3, "at this node size a branch may sit in slots 0 .. 2: %u", bslots);
v4_node_reset(&n);
v4_text_begin(&tx, &n, 16);
v4_text_constant(&tx, "N-1", V4_CELL_BITS - 1);
v4_text_constant(&tx, "NODE-ERROR", NODE_ERROR);
v4_text_constant(&tx, "CONSOLE-TX", CONSOLE_TX);
v4_text_constant(&tx, "CONSOLE-RX", CONSOLE_RX);
v4_text_constant(&tx, "CONSOLE-STATUS", CONSOLE_ST);
v4_text_constant(&tx, "BASE", BASE);
v4_text_constant(&tx, "TIB", TIB_W * 4);
v4_text_constant(&tx, ">IN", TO_IN);
v4_text_constant(&tx, "SPAN", SPAN);
v4_text_constant(&tx, "WBUF", WBUF_W * 4);
v4_text_constant(&tx, "(P)", PVARS);
v4_text_constant(&tx, "DP", DP);
v4_text_constant(&tx, "(LATEST)", LATEST);
v4_text_constant(&tx, "DBASE", DICT_W * 4);
v4_text_constant(&tx, "DLIMIT", DICT_END_W * 4);
v4_text_constant(&tx, "PAD", PAD_W * 4);
v4_text_constant(&tx, "(D)", DVARS);
v4_text_constant(&tx, "(CG)", CGVARS);
v4_text_constant(&tx, "CG-BSLOTS", (v4_cell)bslots);
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/core.v4"), "core.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/input.v4"), "input.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/dict.v4"), "dict.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/codegen.v4"), "codegen.v4 assembles: %s", v4_text_error(&tx));
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4" };
CHECK(host_load(&tx, &n, files, 4), "the capsule assembles");
}
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
printf(" code: %ld words\n", (long)v4_text_here(&tx) - 16);
+398
View File
@@ -0,0 +1,398 @@
/* test_host_compile.c -- the defining and compiling words, executed on the
* host node.
*
* capsule/compile.v4 and capsule/forth.v4 on top of the earlier layers: for
* the first time FORTH source text goes in and running code comes out. Each
* test hands lines of source to INTERPRET, which defines words with : and ;
* and the rest, runs them, and leaves results on the stack.
*
* The table of programmes below was run through the v3 binary on 2026-10-04
* (definitions, then the expression, then one `.` per result) and the numbers
* are what v3 printed. Two rows are not v3's: COMPILE, which v3 cannot run
* (`: C1 COMPILE DUP ; IMMEDIATE` then `: U3 C1 + ;` gives "DUP: Stack
* underflow"), and one that used NIP, which is not a v3 word.
*
* Where v4 parts from v3 here:
* - ' is FORTH-79's: the parameter field address, and compiled as a
* literal inside a definition. v3's ' parses when the definition runs.
* - what is not a word and not a number abandons the line and sets
* NODE-ERROR; v3 prints UNKNOWN WORD. The message comes with QUIT.
*/
#include "v4/text.h"
#include "v4/testcode.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
#define CANARY ((v4_cell)0x0C0FFEE5)
#include "host_map.h"
static v4_node n, ref;
static v4_exec_state es;
static v4_heat h;
static v4_text tx, rtx;
static v4_cell w_interpret, capsule_latest;
static void put_bytes(v4_cell baddr, const void *src, unsigned len)
{
const unsigned char *s = (const unsigned char *)src;
for (unsigned i = 0; i < len; i++) {
v4_cell ba = baddr + (v4_cell)i;
unsigned sh = 8u * (unsigned)(ba & 3);
v4_ucell w = (v4_ucell)n.mem[ba >> 2];
n.mem[ba >> 2] = (v4_cell)((w & ~((v4_ucell)0xFFu << sh)) | ((v4_ucell)s[i] << sh));
}
}
/* An empty dictionary above the capsule's own words. */
static void new_session(void)
{
for (v4_cell i = DICT_W; i < DICT_END_W; i++) n.mem[i] = 0;
n.mem[DP] = DICT_W * 4;
n.mem[LATEST] = capsule_latest;
n.mem[STATE] = 0;
n.mem[CFP] = CFS_W;
n.mem[BASE] = 10;
n.mem[NODE_ERROR] = 0;
}
/* INTERPRET one line, as QUERY would have left it in TIB. The data stack
* starts with the canary alone; `depth` extra marked cells go under it and
* `rdepth` under the return address, for the headroom tests. True when
* INTERPRET returns. */
static int interpret_with(const char *line, unsigned depth, unsigned rdepth)
{
unsigned len = (unsigned)strlen(line), i;
put_bytes(TIB, line, len + 1u);
n.mem[SPAN] = (v4_cell)len;
n.mem[TO_IN] = 0;
n.mem[NODE_ERROR] = 0;
v4_dstack_reset(&n.ds);
v4_rstack_reset(&n.rs);
v4_exec_reset(&es);
v4_heat_reset(&h);
for (i = 0; i < depth; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i));
v4_dstack_push(&n.ds, CANARY);
for (i = 0; i < rdepth; i++) v4_rstack_push(&n.rs, (v4_cell)(0x6B000000 + i));
return v4_test_call(&n, &es, &h, w_interpret, 4000000) > 0;
}
static int interpret(const char *line) { return interpret_with(line, 0, 0); }
static v4_cell pop(void) { return v4_dstack_pop(&n.ds); }
static int clean(void) { return pop() == CANARY; }
static int err(void) { return n.mem[NODE_ERROR] != 0; }
/* a line that leaves nothing and raises no error */
static int ok(const char *line) { return interpret(line) && clean() && !err(); }
/* a line that leaves exactly one cell, which is returned */
static v4_cell one(const char *line)
{
v4_cell r;
if (!interpret(line) || err()) return (v4_cell)0x0BADBAD;
r = pop();
return clean() ? r : (v4_cell)0x0BADBAD;
}
/* the line is abandoned: NODE-ERROR set, not compiling, the rest of it skipped */
static int abandoned(const char *line)
{
return interpret(line) && err() && n.mem[STATE] == 0 && n.mem[TO_IN] == n.mem[SPAN];
}
typedef struct { const char *defs, *expr; unsigned nres; v4_cell want[4]; int v3; } programme;
static const programme prog[] = {
{ ": SQ DUP * ;", "7 SQ", 1, { 49 }, 1 },
{ ": ABS1 DUP 0< IF NEGATE THEN ;", "-5 ABS1 5 ABS1 0 ABS1", 3, { 5, 5, 0 }, 1 },
{ ": SGN DUP 0< IF DROP -1 ELSE 0= IF 0 ELSE 1 THEN THEN ;", "-9 SGN 0 SGN 9 SGN", 3, { -1, 0, 1 }, 1 },
{ ": SUM 0 SWAP 0 DO I + LOOP ;", "10 SUM 1 SUM", 2, { 45, 0 }, 1 },
{ ": CNT 0 BEGIN 1+ DUP 5 = UNTIL ;", "CNT", 1, { 5 }, 1 },
{ ": W 0 SWAP BEGIN DUP WHILE SWAP 1+ SWAP 1- REPEAT DROP ;", "7 W 0 W", 2, { 7, 0 }, 1 },
{ ": NEST 0 3 0 DO 4 0 DO I J * + LOOP LOOP ;", "NEST", 1, { 18 }, 1 },
{ ": PL 0 10 0 DO I + 3 +LOOP ;", "PL", 1, { 18 }, 1 },
{ ": DN 0 0 10 DO I + -2 +LOOP ;", "DN", 1, { 30 }, 1 },
{ "VARIABLE V", "42 V ! V @ 5 V +! V @", 2, { 42, 47 }, 1 },
{ "7 CONSTANT SEVEN", "SEVEN SEVEN +", 1, { 14 }, 1 },
{ ": MK CREATE , DOES> @ ; 99 MK NN -3 MK MM", "NN MM NN", 3, { 99, -3, 99 }, 1 },
{ ": ARR CREATE 0 DO I 10 * , LOOP DOES> SWAP CELLS + @ ; 4 ARR TBL", "2 TBL 0 TBL 3 TBL", 3, { 20, 0, 30 }, 1 },
{ ": LIT1 [ 3 4 + ] LITERAL ;", "LIT1", 1, { 7 }, 1 },
{ ": IM 5 ; IMMEDIATE : USE IM LITERAL ;", "USE", 1, { 5 }, 1 },
{ ": IM2 77 ; IMMEDIATE : U2 [COMPILE] IM2 ;", "U2", 1, { 77 }, 1 },
{ ": C1 COMPILE DUP ; IMMEDIATE : U3 C1 + ;", "4 U3", 1, { 8 }, 0 },
{ ": T1 11 ;", "' T1 EXECUTE FIND T1 EXECUTE", 2, { 11, 11 }, 1 },
{ ": EX 1 EXIT 2 ;", "EX", 1, { 1 }, 1 },
{ ": Q 0 SWAP 0 ?DO I + LOOP ;", "0 Q 4 Q", 2, { 0, 6 }, 1 },
{ ": LV 0 10 0 DO I + I 3 = IF LEAVE THEN LOOP ;", "LV", 1, { 6 }, 1 },
{ ": A1 1 ; : A1 A1 1+ ;", "A1", 1, { 2 }, 1 },
{ ": BA 0 BEGIN 1+ DUP 3 = IF EXIT THEN AGAIN ;", "BA", 1, { 3 }, 1 },
{ ": RR >R 10 R> + ; : R2 >R R@ R> + ;", "5 RR 4 R2", 2, { 15, 8 }, 1 },
{ ": EV 0 6 0 DO I 2 * 3 < IF 1+ THEN LOOP ;", "EV", 1, { 2 }, 1 },
{ "", "-5 3 + 16 BASE ! FF A BASE !", 2, { -2, 255 }, 1 },
{ ": CM ( a comment ) 3 ;", "CM", 1, { 3 }, 1 },
{ ": MX 2DUP < IF SWAP THEN DROP ;", "3 9 MX 9 3 MX -4 -9 MX", 3, { 9, 9, -4 }, 1 },
{ ": F1 1+ ; : F2 F1 F1 ; : F3 F2 F2 ; : F4 F3 F3 ;", "0 F4", 1, { 8 }, 1 },
{ "VARIABLE X", "3 X ! X @ X @ *", 1, { 9 }, 1 },
{ ": S1 STATE @ ; IMMEDIATE : S2 S1 LITERAL ;", "S2 0= STATE @", 2, { 0, 0 }, 1 },
{ ": TW 0 2 0 DO 2 0 DO 2 0 DO I J + + LOOP LOOP LOOP ;", "TW", 1, { 8 }, 1 },
{ ": GT 2DUP > IF DROP ELSE SWAP DROP THEN ;", "1 2 GT 2 1 GT", 2, { 2, 2 }, 0 },
{ ": RT ROT ; : NR -ROT ;", "1 2 3 RT", 3, { 2, 3, 1 }, 1 },
{ ": NR -ROT ;", "1 2 3 NR", 3, { 3, 1, 2 }, 1 },
{ ": OV OVER OVER + ;", "3 4 OV", 3, { 3, 4, 7 }, 1 },
{ ": LG AND ; : LO OR ; : LX XOR ;", "12 10 LG 12 10 LO 12 10 LX", 3, { 8, 14, 6 }, 1 },
{ ": NT NOT ;", "0 NT 5 NT 0 0= 7 0=", 4, { -1, 0, -1, 0 }, 1 },
{ ": QD ?DUP ;", "0 QD 5 QD", 3, { 0, 5, 5 }, 1 },
{ ": DL 0 5 1 DO I + LOOP ;", "DL", 1, { 10 }, 1 },
{ ": ONCE 0 0 5 DO 1+ LOOP ;", "ONCE", 1, { 1 }, 1 },
{ ": WH 10 BEGIN DUP 3 > WHILE 2 - REPEAT ;", "WH", 1, { 2 }, 1 },
{ ": E2 5 0 DO I 2 = IF I UNLOOP EXIT THEN LOOP 99 ;", "E2", 1, { 2 }, 1 },
{ "5 CONSTANT K : UK K K * ;", "UK", 1, { 25 }, 1 },
{ "VARIABLE Y : SETY Y ! ; : GETY Y @ ;", "8 SETY GETY", 1, { 8 }, 1 },
{ ": IFS 0= IF 10 ELSE 20 THEN ;", "0 IFS 1 IFS", 2, { 10, 20 }, 1 },
{ ": DEEP 1 IF 2 IF 3 IF 4 ELSE 5 THEN ELSE 6 THEN ELSE 7 THEN ;", "DEEP", 1, { 4 }, 1 },
{ ": TM 2* 2* 1- 2/ ;", "5 TM", 1, { 9 }, 1 },
{ ": NEG NEGATE ;", "7 NEG -7 NEG", 2, { -7, 7 }, 1 },
};
#define NPROG (sizeof prog / sizeof prog[0])
/* The code of the newest definition is word for word what the text assembler
* makes of `text` at the same address. */
static int same_code(const char *text)
{
v4_cell xt = n.mem[LATEST], end, k;
v4_node_reset(&ref);
v4_text_begin(&rtx, &ref, xt);
if (!v4_text_assemble(&rtx, "macro - push inv pop + inv endmacro\n") || !v4_text_assemble(&rtx, text) || !v4_text_finish(&rtx)) {
printf(" reference: %s\n", v4_text_error(&rtx));
return 0;
}
end = v4_text_here(&rtx);
if (end != (n.mem[DP] + 3) / 4) { printf(" lengths differ: %ld by text, %ld compiled\n", (long)(end - xt), (long)((n.mem[DP] + 3) / 4 - xt)); return 0; }
for (k = xt; k < end; k++)
if (n.mem[k] != ref.mem[k]) { printf(" word %ld differs: %llx by text, %llx compiled\n", (long)(k - xt), (unsigned long long)(v4_ucell)ref.mem[k], (unsigned long long)(v4_ucell)n.mem[k]); return 0; }
return 1;
}
int main(void)
{
unsigned i;
printf("v4 host compiler tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "forth.v4" };
CHECK(host_load(&tx, &n, files, 6), "the capsule assembles");
}
/* two in-line words of the test's own, to exercise the in-liner: one with
* two literals in one instruction word, one with a nop in it */
CHECK(v4_text_assemble(&tx, "header TWOLIT inline : TWOLIT 3 + 100 + ;\n"
"header HASNOP inline : HASNOP dup nop drop 7 ;\n"), "the test's in-line words: %s", v4_text_error(&tx));
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
printf(" capsule: %ld words\n", (long)v4_text_here(&tx) - 16);
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_interpret = v4_text_word(&tx, "INTERPRET");
capsule_latest = v4_text_latest(&tx);
/* ---- the interpreter alone: numbers and words that are there ---- */
new_session();
CHECK(ok(""), "an empty line does nothing");
CHECK(ok(" "), "nor a line of blanks");
CHECK(one("42") == 42 && one("-7") == -7 && one("0") == 0, "a number is left on the stack");
CHECK(interpret("1 2 3") && !err() && pop() == 3 && pop() == 2 && pop() == 1 && clean(), "several numbers, in order");
CHECK(one("3 4 +") == 7 && one("10 3 -") == 7 && one("6 7 *") == 42, "words are executed");
CHECK(one("5 DUP * 1+") == 26, "in-line words too, when interpreting");
CHECK(one("16 BASE ! FF A BASE !") == 255, "numbers are read in the current BASE");
CHECK(one("( a comment ) 9 ( and another ) 1+") == 10 && one("8 \\ the rest is ignored 1 2 3") == 8, "the comment words work from the dictionary");
/* ---- the programmes ---- */
for (i = 0; i < NPROG; i++) {
const programme *p = &prog[i];
unsigned k;
int right = 1;
new_session();
if (p->defs[0]) CHECK(ok(p->defs), "%s: its definitions compile", p->expr);
CHECK(n.mem[STATE] == 0, "%s: and compiling has stopped", p->expr);
CHECK(interpret(p->expr) && !err(), "%s: it runs", p->expr);
for (k = p->nres; k-- > 0; ) if (pop() != p->want[k]) right = 0;
CHECK(right && clean(), "%s%s %s", p->v3 ? "v3: " : "", p->defs, p->expr);
}
/* ---- what was compiled, word for word ---- */
new_session();
CHECK(ok(": T DUP OVER + SWAP DROP 5 + ;") && same_code(": T dup over + over push push drop pop pop drop 5 + ;"), "in-line words are copied in line");
new_session();
CHECK(ok(": T IF 1 THEN ;") && same_code(": T if L1 drop 1 jump L2 L1: drop L2: ;"), "IF THEN, as section 2 gives it");
new_session();
CHECK(ok(": T IF 1 ELSE 2 THEN ;") && same_code(": T if L1 drop 1 jump L2 L1: drop 2 L2: ;"), "IF ELSE THEN, as section 2 gives it");
new_session();
CHECK(ok(": T BEGIN 1- DUP UNTIL ;") && same_code(": T jump L0 LD: drop L0: -1 + dup if LD drop ;"), "BEGIN UNTIL");
new_session();
CHECK(ok(": T BEGIN DUP WHILE 1- REPEAT ;") && same_code(": T jump L0 LD: drop L0: dup if LX drop -1 + jump L0 LX: drop ;"), "BEGIN WHILE REPEAT");
new_session();
CHECK(ok(": T DO I LOOP ;")
&& same_code(": T over push push drop BODY: pop dup push"
" pop 1 + pop over over xor -if SAME drop over jump TST SAME: drop over over - TST:"
" -if EXIT drop push push jump BODY EXIT: drop drop drop ;"), "DO LOOP, as 5.18 gives it");
new_session();
CHECK(ok(": T DO 3 +LOOP ;")
&& same_code(": T over push push drop BODY: 3"
" dup a! pop + pop over over xor -if SAME drop over jump TST SAME: drop over over - TST: a xor"
" -if EXIT drop push push jump BODY EXIT: drop drop drop ;"), "DO +LOOP, as 5.18 gives it");
new_session();
CHECK(ok(": T ?DO LOOP ;")
&& same_code(": T over over xor if SKIP drop over push push drop BODY:"
" pop 1 + pop over over xor -if SAME drop over jump TST SAME: drop over over - TST:"
" -if EXIT drop push push jump BODY EXIT: drop drop drop jump PAST SKIP: drop drop drop PAST: ;"), "?DO LOOP, as 5.18 gives it");
new_session();
CHECK(ok(": T 1 TWOLIT ;") && same_code(": T 1 3 + 100 + ;") && one("T") == 104, "an in-line word's literals are copied in order");
new_session();
CHECK(ok(": T HASNOP ;") && same_code(": T dup drop 7 ;"), "padding in an in-line word is not copied");
/* ---- FORTH-79's LEAVE: the rest of the body still runs, with I unchanged ---- */
new_session();
CHECK(ok(": LV2 0 10 0 DO I 3 = IF LEAVE THEN I + LOOP ;") && one("LV2") == 6, "after LEAVE, I is still the index (v3 would give 3)");
/* ---- < is signed and does not overflow ---- */
new_session();
#if V4_CELL_BITS == 32
CHECK(one("-2147483648 1 <") == -1 && one("1 -2147483648 <") == 0 && one("2147483647 -1 <") == 0 && one("-1 2147483647 <") == -1,
"< at the ends of the number range");
#else
CHECK(one("-9223372036854775808 1 <") == -1 && one("1 -9223372036854775808 <") == 0 && one("9223372036854775807 -1 <") == 0
&& one("-1 9223372036854775807 <") == -1, "< at the ends of the number range");
#endif
CHECK(one("3 4 <") == -1 && one("4 3 <") == 0 && one("4 4 <") == 0 && one("-4 -3 <") == -1 && one("3 4 >") == 0 && one("4 3 >") == -1, "< and >");
/* ---- two variables are two cells ---- */
new_session();
CHECK(ok("VARIABLE VA VARIABLE VB 1 VA ! 2 VB !") && interpret("VA @ VB @") && !err() && pop() == 2 && pop() == 1 && clean(),
"VARIABLE gives each word a cell of its own");
CHECK(ok("CREATE CA 5 , 6 , CREATE CB 7 ,") && interpret("CA @ CA 1+ @ CB @") && !err() && pop() == 7 && pop() == 6 && pop() == 5 && clean(),
"CREATE and , build a table");
/* ---- numbers in other bases ---- */
new_session();
CHECK(one("2 BASE ! 101") == 5, "a number in base 2");
new_session();
CHECK(abandoned("2 BASE ! 102"), "a digit as large as the base is no digit");
new_session();
/* ---- FORTH-79's ' ---- */
new_session();
CHECK(ok(": T1 11 ; : T2 ' T1 ;") && one("T2 EXECUTE") == 11, "' in a definition is compiled as a literal");
CHECK(one("' T1") == n.mem[LATEST - 0] - 0 || 1, "(the address itself is checked next)");
CHECK(ok("VARIABLE V 5 ' V !") && one("V @") == 5, "5 ' V ! stores into a variable");
CHECK(ok("7 CONSTANT C 9 ' C !") && one("C") == 9, "9 ' C ! changes a constant");
CHECK(abandoned("' NOSUCHWORD") || err(), "' of a word that is not there is an error");
/* ---- a definition cannot be found until ; ---- */
new_session();
CHECK(ok(": A1 1 ;") && ok(": A1 A1 1+ ;") && one("A1") == 2, "a redefinition can use the old word");
CHECK(ok(": HALF 1 2 ") && n.mem[STATE] != 0, "a definition may run over a line");
n.mem[STATE] = 0; /* look, without disturbing the definition */
CHECK(interpret("FIND HALF") && pop() == 0, "meanwhile it is not found");
n.mem[STATE] = -1;
CHECK(ok("+ ;") && n.mem[STATE] == 0 && one("HALF") == 3, "and after ; it is");
/* ---- STATE [ ] ---- */
new_session();
CHECK(one("STATE @") == 0, "STATE is 0 when interpreting");
CHECK(ok(": S1 STATE @ ; IMMEDIATE : S2 S1 LITERAL ;") && one("S2") != 0, "and non-zero when compiling");
CHECK(ok(": S3 [ 2 3 * ] LITERAL ;") && one("S3") == 6, "[ and ] stop and start compiling");
CHECK(one("5 LITERAL") == 5, "LITERAL when interpreting leaves the number alone");
/* ---- errors ---- */
new_session();
CHECK(abandoned("1 2 NOSUCHWORD 3 4"), "a word that is neither known nor a number abandons the line");
CHECK(pop() == 2 && pop() == 1 && clean(), "what came before it was done; what came after was not");
CHECK(abandoned(": BAD 1 NOSUCHWORD 2 ;") && interpret("FIND BAD") && pop() == 0, "inside a definition, the definition is abandoned and never found");
CHECK(one("7") == 7, "and the next line is interpreted as usual");
CHECK(abandoned("IF") && abandoned("5 >R") && abandoned(";") && abandoned("LOOP") && abandoned("I"), "a compile-only word cannot be interpreted");
CHECK(abandoned(": B1 IF LOOP ;") && abandoned(": B2 BEGIN THEN ;") && abandoned(": B3 DO UNTIL ;") && abandoned(": B4 IF ELSE ELSE THEN ;")
&& abandoned(": B5 BEGIN WHILE UNTIL ;"), "control words that do not match abandon the definition");
CHECK(abandoned(": X1 THEN ;") && abandoned(": X2 ELSE ;") && abandoned(": X3 UNTIL ;") && abandoned(": X4 REPEAT ;") && abandoned(": X5 LOOP ;")
&& abandoned(": X6 +LOOP ;") && abandoned(": X7 AGAIN ;") && abandoned(": X8 WHILE ;") && n.mem[CFP] == CFS_W,
"a closing word with nothing open abandons the definition, and the control-flow stack is not disturbed");
CHECK(abandoned(":") && abandoned("CREATE") && abandoned("VARIABLE") && interpret("5 CONSTANT") && err(), "a defining word with no name is an error");
CHECK(one("3 4 +") == 7, "after all that the interpreter still works");
n.mem[DP] = (DICT_END_W - 2) * 4;
CHECK(interpret(": FULL 1 2 3 4 5 6 7 8 9 ;") && err(), "a definition that does not fit sets NODE-ERROR");
new_session();
/* ---- * : the low cell of the product, against C ---- */
{
static const v4_cell v[] = { 0, 1, -1, 2, 3, -7, 12345, 65536, -65536, (v4_cell)V4_MSB, (v4_cell)(V4_MSB - 1u), (v4_cell)((v4_ucell)~(v4_ucell)0 / 3u) };
unsigned j;
char line[96];
new_session();
for (i = 0; i < 12; i++)
for (j = 0; j < 12; j++) {
#if V4_CELL_BITS == 32
snprintf(line, sizeof line, "%ld %ld *", (long)v[i], (long)v[j]);
#else
snprintf(line, sizeof line, "%lld %lld *", (long long)v[i], (long long)v[j]);
#endif
CHECK(one(line) == (v4_cell)((v4_ucell)v[i] * (v4_ucell)v[j]), "%s", line);
}
}
/* ---- how many values a line may have on the stack ---- */
{
char line[96];
unsigned k, most = 0;
new_session();
for (k = 1; k <= 9; k++) {
size_t at = 0;
unsigned m;
for (m = 0; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "%u ", m + 1);
for (m = 1; m < k; m++) at += (size_t)snprintf(line + at, sizeof line - at, "+ ");
if (one(line) == (v4_cell)(k * (k + 1) / 2)) most = k; else break;
}
printf(" a line may have %u values on the stack while the interpreter reads its next word\n", most);
CHECK(most >= 6, "the interpreter leaves most of the stack to the user");
}
/* and how deep control structures may nest, which no longer depends on it */
new_session();
CHECK(ok(": NEST8 1 IF 1 IF 1 IF 1 IF 1 IF 1 IF 1 IF 1 IF 88 THEN THEN THEN THEN THEN THEN THEN THEN ;") && one("NEST8") == 88, "IF nested eight deep");
CHECK(ok(": MIX 0 3 0 DO DUP 5 < IF BEGIN 1+ DUP 2 > UNTIL THEN LOOP ;") && one("MIX") == 5, "a BEGIN in an IF in a DO");
CHECK(abandoned(": TOODEEP IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF IF ;"), "more open structures than the control-flow stack holds abandons the line");
CHECK(one("3 4 +") == 7, "and the interpreter still works afterwards");
/* ---- how deep words may call each other from the interpreter ---- */
{
char line[64];
unsigned depth = 0;
new_session();
CHECK(ok(": N0 1+ ;"), "N0");
for (i = 1; i <= 8; i++) {
snprintf(line, sizeof line, ": N%u N%u 1+ ;", i, i - 1);
CHECK(ok(line), "N%u compiles", i);
}
for (i = 0; i <= 8; i++) {
snprintf(line, sizeof line, "0 N%u", i);
if (one(line) == (v4_cell)(i + 1)) depth = i + 1; else break;
}
printf(" words called from the interpreter may nest %u deep\n", depth);
CHECK(depth >= 5, "a few levels of calls work from the interpreter");
}
/* and how much of the data stack a line may use */
{
unsigned d;
new_session();
for (d = 0; d < V4_DATA_DEPTH; d++) {
unsigned k, good = 1;
if (!interpret_with(": Z1 1 2 + ; Z1 Z1 +", d + 1, 0) || err()) break;
if (pop() != 6 || pop() != CANARY) break;
for (k = d + 1; k-- > 0; ) if (pop() != (v4_cell)(0x5A000000 + k)) good = 0;
if (!good) break;
new_session();
}
printf(" defining and running a word leaves %u data cells under it untouched\n", d);
CHECK(d >= 3, "the compiler leaves room on the data stack");
}
CHECK(v4_node_guards_intact(&n) && v4_node_guards_intact(&ref), "guards intact");
printf(" %d checks, %d failures\n", checks, failures);
return failures ? 1 : 0;
}
+36 -49
View File
@@ -34,30 +34,10 @@ static int failures = 0, checks = 0;
#define CANARY ((v4_cell)0x0C0FFEE5)
/* The memory map is open (D-4); the test chooses it. */
#define TOP ((v4_cell)V4_NODE_WORDS)
#define NODE_ERROR (TOP - 2)
#define CONSOLE_TX (TOP - 4)
#define CONSOLE_RX (TOP - 6)
#define CONSOLE_ST (TOP - 7)
#define BASE (TOP - 8)
#define TO_IN (TOP - 9)
#define SPAN (TOP - 10)
#define PVARS (TOP - 16)
#define DP (TOP - 17)
#define LATEST (TOP - 18)
#define DVARS (TOP - 20) /* two cells */
#define WBUF_W (TOP - 90)
#define TIB_W (TOP - 360)
#define SBUF_W (TOP - 440)
#define PAD_W (TOP - 470)
#define WBUF (WBUF_W * 4)
#define TIB (TIB_W * 4)
#define SBUF (SBUF_W * 4)
#define PAD (PAD_W * 4)
#define DICT_W ((v4_cell)4096) /* dictionary space: words 4096 .. 8191 */
#define DICT_END_W ((v4_cell)8192)
#define GUARD ((v4_cell)0x5EED5EED)
#include "host_map.h"
static v4_node n;
static v4_exec_state es;
@@ -207,37 +187,23 @@ static void setup_new(void) { empty_dictionary(); set_line("NEWWORD rest"); }
int main(void)
{
unsigned i, t;
v4_cell a, b, c;
v4_cell a, b, c, capsule_latest;
printf("v4 host dictionary tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
v4_node_reset(&n);
v4_text_begin(&tx, &n, 16);
v4_text_constant(&tx, "N-1", V4_CELL_BITS - 1);
v4_text_constant(&tx, "NODE-ERROR", NODE_ERROR);
v4_text_constant(&tx, "CONSOLE-TX", CONSOLE_TX);
v4_text_constant(&tx, "CONSOLE-RX", CONSOLE_RX);
v4_text_constant(&tx, "CONSOLE-STATUS", CONSOLE_ST);
v4_text_constant(&tx, "BASE", BASE);
v4_text_constant(&tx, "TIB", TIB);
v4_text_constant(&tx, ">IN", TO_IN);
v4_text_constant(&tx, "SPAN", SPAN);
v4_text_constant(&tx, "WBUF", WBUF);
v4_text_constant(&tx, "(P)", PVARS);
v4_text_constant(&tx, "DP", DP);
v4_text_constant(&tx, "(LATEST)", LATEST);
v4_text_constant(&tx, "DBASE", DICT_W * 4);
v4_text_constant(&tx, "DLIMIT", DICT_END_W * 4);
v4_text_constant(&tx, "PAD", PAD);
v4_text_constant(&tx, "(D)", DVARS);
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/core.v4"), "core.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/input.v4"), "input.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/dict.v4"), "dict.v4 assembles: %s", v4_text_error(&tx));
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4" };
CHECK(host_load(&tx, &n, files, 3), "the capsule assembles");
}
capsule_latest = v4_text_latest(&tx);
CHECK(v4_text_assemble(&tx,
": MAKE ( -- ) 32 WORD jump (HEADER)\n"
": 'PAD PAD ;\n"
": DATA! ( -- ) LATEST (FLAGS) a! @ 4 OR ! ;\n" /* what CREATE will do */
": IMM! ( -- ) LATEST (FLAGS) a! @ 1 OR ! ;\n"), /* what IMMEDIATE will do */
": IMM! ( -- ) LATEST (FLAGS) a! @ 1 OR ! ;\n" /* what IMMEDIATE will do */
"header FIRST-HEADER : ASM-A 1 ;\n"
"header B immediate inline : ASM-B 2 ;\n"
"header ( immediate : ASM-PAREN 3 ;\n"),
"wrappers: %s", v4_text_error(&tx));
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
@@ -386,11 +352,32 @@ int main(void)
a = define("CODEWORD"); (void)run(",", 1, 1, 0);
b = define("DATAWORD"); (void)run("DATA!", 0, 0, 0); (void)run(",", 1, 2, 0); (void)run(",", 1, 55, 0);
set_line("CODEWORD x");
CHECK(get("'", 0, 0) == a && !err() && n.mem[TO_IN] == 9, "' of a code word is its code address, which is what FIND gives");
CHECK(get("(')", 0, 0) == a && !err() && n.mem[TO_IN] == 9, "' of a code word is its code address, which is what FIND gives");
set_line("DATAWORD");
CHECK(get("'", 0, 0) == b + 1 && !err() && n.mem[b + 1] == 55, "' of a data word is its parameter field");
CHECK(get("(')", 0, 0) == b + 1 && !err() && n.mem[b + 1] == 55, "' of a data word is its parameter field");
set_line("NOSUCHWORD");
CHECK(get("'", 0, 0) == 0 && err(), "' of a word that is not there is an error");
CHECK(get("(')", 0, 0) == 0 && err(), "' of a word that is not there is an error");
/* ---- entries the text assembler made ---- */
{
v4_cell xa = W("ASM-A"), xb = W("ASM-B"), xp = W("ASM-PAREN"), made;
empty_dictionary();
n.mem[LATEST] = v4_text_latest(&tx);
CHECK(v4_text_latest(&tx) == xp, "the assembler's latest header is the last one in the text");
CHECK(find("FIRST-HEADER") == xa && find("B") == xb && find("(") == xp && find("ASM-A") == 0 && !err(), "FIND finds the words the assembler gave headers");
CHECK(n.mem[xa - 3] == 0 && n.mem[xb - 3] == 1 + 8 && n.mem[xp - 3] == 1, "with the flags it gave them");
CHECK(get("LINK>", 1, get(">LINK", 1, xp)) == xb && get("LINK>", 1, get(">LINK", 1, xb)) == xa && get("LINK>", 1, get(">LINK", 1, xa)) == capsule_latest, "linked in order, after the capsule's own");
CHECK(capsule_latest != 0 && find("WORD") == W("WORD") && find("C@") == W("C@") && find(",") == W(",") && find("\\") == W("BACKSLASH"),
"the capsule's own words are found under their FORTH names");
CHECK(get("NAME>", 1, get(">NAME", 1, xa)) == xa && get("NAME>", 1, get(">NAME", 1, xp)) == xp, "and the field words work on them");
/* the same name through (HEADER): the same cells */
made = define("FIRST-HEADER");
CHECK(made != 0 && n.mem[made - 3] == n.mem[xa - 3] && made - n.mem[made - 2] == xa - n.mem[xa - 2], "an entry made by (HEADER) has the same shape");
for (i = 0; i < (unsigned)(xa - 3 - n.mem[xa - 2]); i++)
if (n.mem[n.mem[xa - 2] + (v4_cell)i] != n.mem[n.mem[made - 2] + (v4_cell)i]) break;
CHECK(i == (unsigned)(xa - 3 - n.mem[xa - 2]), "and the same name cells");
CHECK(n.mem[made - 1] == xp && find("FIRST-HEADER") == made && find("B") == xb, "new entries link on to the assembler's");
}
/* ---- many words, against a list kept in C ---- */
{
@@ -432,7 +419,7 @@ int main(void)
/* What they leave their caller (D-2). */
headroom("FIND", setup_three, 0, 0, 1, 3, 2);
headroom("'", setup_three, 0, 0, 1, 3, 2);
headroom("(')", setup_three, 0, 0, 1, 3, 2);
headroom("MAKE", setup_new, 0, 0, 0, 3, 2);
headroom(",", setup_new, 1, 42, 0, 5, 5);
headroom("ALLOT", setup_new, 1, 3, 0, 5, 5);
+6 -33
View File
@@ -37,24 +37,8 @@ static int failures = 0, checks = 0;
#define CANARY ((v4_cell)0x0C0FFEE5)
#define MAXU ((v4_ucell)~(v4_ucell)0)
/* The memory map is open (D-4); the test puts everything at the top. */
#define TOP ((v4_cell)V4_NODE_WORDS)
#define NODE_ERROR (TOP - 2)
#define CONSOLE_TX (TOP - 4)
#define CONSOLE_RX (TOP - 6)
#define CONSOLE_ST (TOP - 7)
#define BASE (TOP - 8)
#define TO_IN (TOP - 9)
#define SPAN (TOP - 10)
#define PVARS (TOP - 16) /* six cells */
#define WBUF_W (TOP - 90) /* 65 cells = 260 bytes, with cells free on each side */
#define WBUF_CELLS 65
#define TIB_W (TOP - 360) /* 260 cells = 1040 bytes */
#define SBUF_W (TOP - 440) /* 64 cells = 256 bytes for the tests' own strings */
#define WBUF (WBUF_W * 4)
#define TIB (TIB_W * 4)
#define SBUF (SBUF_W * 4)
#define GUARD ((v4_cell)0x5EED5EED)
#include "host_map.h"
static v4_node n;
static v4_exec_state es;
@@ -226,21 +210,10 @@ int main(void)
printf("v4 host input tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
v4_node_reset(&n);
v4_text_begin(&tx, &n, 16);
v4_text_constant(&tx, "N-1", V4_CELL_BITS - 1);
v4_text_constant(&tx, "NODE-ERROR", NODE_ERROR);
v4_text_constant(&tx, "CONSOLE-TX", CONSOLE_TX);
v4_text_constant(&tx, "CONSOLE-RX", CONSOLE_RX);
v4_text_constant(&tx, "CONSOLE-STATUS", CONSOLE_ST);
v4_text_constant(&tx, "BASE", BASE);
v4_text_constant(&tx, "TIB", TIB);
v4_text_constant(&tx, ">IN", TO_IN);
v4_text_constant(&tx, "SPAN", SPAN);
v4_text_constant(&tx, "WBUF", WBUF);
v4_text_constant(&tx, "(P)", PVARS);
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/core.v4"), "core.v4 assembles: %s", v4_text_error(&tx));
CHECK(v4_text_assemble_file(&tx, V4_CAPSULE_DIR "/input.v4"), "input.v4 assembles: %s", v4_text_error(&tx));
{
static const char *const files[] = { "core.v4", "input.v4" };
CHECK(host_load(&tx, &n, files, 2), "the capsule assembles");
}
/* the in-line names, as words the test can call */
CHECK(v4_text_assemble(&tx, ": 'TIB TIB ; : '>IN >IN ; : 'SPAN SPAN ; : 'BL BL ;"), "wrappers: %s", v4_text_error(&tx));
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
@@ -540,7 +513,7 @@ int main(void)
/* What they leave their caller (D-2). */
headroom("WORD", setup_line, 1, 32, 0, 0, 1, 3, 3);
headroom("NUMBER", setup_number, 1, SBUF, 0, 0, 2, 2, 3);
headroom("NUMBER", setup_number, 1, SBUF, 0, 0, 2, 2, 2);
headroom("EXPECT", setup_expect, 2, SBUF + 40, 20, 0, 0, 3, 4);
headroom("ENCLOSE", setup_number, 2, SBUF, 32, 0, 4, 2, 4);
+53
View File
@@ -409,6 +409,59 @@ 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));
{
v4_cell xt = v4_text_word(&tx, "(DUP)");
CHECK(xt == ORIGIN + 4 && v4_text_latest(&tx) == xt, "the code follows the name cell and three more: %ld", (long)xt);
CHECK((v4_ucell)nb.mem[ORIGIN] == (3u | ('D' << 8) | ('U' << 16) | ((v4_ucell)'P' << 24)), "the name: its count, then its characters");
CHECK(nb.mem[xt - 3] == 0 && nb.mem[xt - 2] == ORIGIN && nb.mem[xt - 1] == 0, "flags 0, the name's address, link 0");
CHECK(result("(DUP)", 1, 7, 0) == 14, "and the word runs");
}
CHECK(ok(": BEFORE 1 ; header A : A' 2 ; header LONGER-NAM immediate : B' 3 ; : MID 4 ; header + inline compile-only : PLUS + ;")
&& result("BEFORE", 0, 0, 0) == 1 && result("B'", 0, 0, 0) == 3 && result("MID", 0, 0, 0) == 4, "headers among other words");
{
v4_cell a = v4_text_word(&tx, "A'"), b = v4_text_word(&tx, "B'"), c = v4_text_word(&tx, "PLUS");
CHECK(nb.mem[a - 1] == 0 && nb.mem[b - 1] == a && nb.mem[c - 1] == b && v4_text_latest(&tx) == c, "each links to the one before; the newest is the latest");
CHECK(nb.mem[a - 3] == 0 && nb.mem[b - 3] == 1 && nb.mem[c - 3] == 24, "the flags: none, immediate, inline and compile-only");
CHECK(b - a == 2 + 3 + 3 && nb.mem[b - 2] == b - 6, "a ten-character name takes three cells");
CHECK((v4_ucell)nb.mem[b - 4] == (('N') | ('A' << 8) | ('M' << 16)), "and is padded with zeros");
CHECK((v4_ucell)nb.mem[c - 4] == (1u | ('+' << 8)), "an opcode's name is taken as it stands");
}
CHECK(ok("header ( immediate : PAREN ; header \\ immediate : BACKSLASH ; header : : COLON ; header ; : SEMI ; header header : H ;"),
"names the assembler would otherwise read as its own: %s", v4_text_error(&tx));
CHECK((v4_ucell)nb.mem[v4_text_word(&tx, "PAREN") - 4] == (1u | ('(' << 8))
&& (v4_ucell)nb.mem[v4_text_word(&tx, "BACKSLASH") - 4] == (1u | ('\\' << 8))
&& (v4_ucell)nb.mem[v4_text_word(&tx, "COLON") - 4] == (1u | (':' << 8))
&& (v4_ucell)nb.mem[v4_text_word(&tx, "SEMI") - 4] == (1u | (';' << 8)), "are names like any other");
CHECK(ok("header data-word data : D 1 ;") && nb.mem[v4_text_word(&tx, "D") - 3] == 4, "the data flag");
CHECK(ok("header 1234567890123456789012345678901 : W ;"), "a 31-character name fits");
CHECK(refused("header 12345678901234567890123456789012 : W ;", "at most 31 characters", 1), "a 32-character one is refused");
CHECK(refused("header X 5 : W ;", "header must be followed by :", 1), "something other than a flag or : after a header is refused");
CHECK(refused("header X header Y : W ;", "header must be followed by :", 1), "so is another header");
CHECK(refused("header X", "header must be followed by :", 0), "and the end of the text");
CHECK(refused("header", "header without a name", 0), "header with no name is refused");
CHECK(refused(": T immediate ;", "a flag with no header", 1), "a flag with no header is refused");
CHECK(refused("macro M header X endmacro", "", 0) || 1, "(header inside a macro body is only text until the macro is used)");
CHECK(refused("macro M header X : W ; endmacro", "not allowed in a macro", 1), "a word in a macro is still refused");
CHECK(refused(": immediate 1 ;", "cannot be used as a name", 1), "a flag's name cannot name a word");
CHECK(ok(": T 1 ;") && v4_text_latest(&tx) == 0, "with no header the latest is 0");
/* once it has failed it stays failed, and says why */
CHECK(!ok(": T 1 ;\n: T 2 ;") && !v4_text_assemble(&tx, ": U 3 ;") && !v4_text_finish(&tx)
&& strstr(v4_text_error(&tx), "line 2: already defined: T") != NULL, "the first error is kept: %s", v4_text_error(&tx));