Files
LithosAnanake/v3/src/word_source/dictionary_manipulation_words.c
T
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00

469 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* dictionary_manipulation_words.c - FORTH-79 Dictionary Manipulation Words */
#include "include/dictionary_manipulation_words.h"
#include "../../include/word_registry.h"
#include "../../include/log.h"
/* Global state for dictionary manipulation */
static cell_t state_variable = 0; /* STATE - compilation state */
/* Helper function to calculate aligned address */
static uintptr_t align_address(uintptr_t addr) {
size_t alignment = sizeof(cell_t);
return (addr + alignment - 1) & ~(alignment - 1);
}
/* Helper function to get name field from execution token */
static char *get_name_field(DictEntry *entry) {
if (entry == NULL) {
return NULL;
}
return (char *) entry->name;
}
/* Helper function to get body address from execution token */
static void *get_body_address(DictEntry *entry) {
if (entry == NULL) {
return NULL;
}
/* Body follows the header structure */
uintptr_t body_addr = (uintptr_t) entry + sizeof(DictEntry) + entry->name_len;
return (void *) align_address(body_addr);
}
/* Helper function to find dictionary entry from name field */
static DictEntry *find_entry_from_name(VM *vm, char *name_field) {
DictEntry *entry;
entry = vm->latest;
while (entry != NULL) {
if ((char *) entry->name == name_field) {
return entry;
}
entry = entry->link;
}
return NULL;
}
/* Helper function to traverse name field */
static char *traverse_name_field(char *name_addr, int direction) {
uint8_t *ptr;
uint8_t name_len;
if (name_addr == NULL) {
return NULL;
}
/* FORTH-79 names start with length byte */
ptr = (uint8_t *) name_addr;
if (direction > 0) {
/* Forward traversal - skip over name */
name_len = *ptr & 0x1F; /* Mask off flags */
return (char *) (ptr + name_len + 1);
} else {
/* Backward traversal - find start of name */
/* This is more complex - we need to scan backwards */
/* For simplicity, assume we're at the start already */
return name_addr;
}
}
/**
* @brief FORTH word SMUDGE - Toggle smudge bit of latest word
* @param vm Pointer to VM instance
* @stack ( -- )
* @note Compile-only word
*/
static void dictionary_m_word_smudge(VM *vm) {
// compile-only: error if used while interpreting
if (vm->mode != MODE_COMPILE) {
// correct
vm->error = 1;
return;
}
// existing body that toggles/sets the smudge/hidden bit on the *latest* word
// (leave your current logic exactly as-is)
}
/* >BODY ( xt -- addr ) Convert execution token to body */
void dictionary_m_word_to_body(VM *vm) {
if (vm->dsp < 0) {
log_message(LOG_ERROR, ">BODY: Stack underflow");
vm->error = 1;
return;
}
cell_t xt = vm_pop(vm);
log_message(LOG_DEBUG, ">BODY: POP: %ld (dsp=%d)", (long) xt, vm->dsp);
DictEntry *entry = (DictEntry *) (intptr_t) xt;
if (!entry) {
log_message(LOG_ERROR, ">BODY: Invalid execution token (NULL)");
vm->error = 1;
return;
}
// Use the VM's dictionary function to get the data field
cell_t *data_field = vm_dictionary_get_data_field(entry);
if (!data_field) {
log_message(LOG_ERROR, ">BODY: Unable to get data field address for entry %p", (void *) entry);
vm->error = 1;
return;
}
cell_t body_addr = (cell_t)(intptr_t)data_field;
log_message(LOG_DEBUG, ">BODY: xt=%p -> body=%p", (void *) entry, (void *) data_field);
vm_push(vm, body_addr);
log_message(LOG_DEBUG, ">BODY: PUSH: %ld (dsp=%d)", (long) body_addr, vm->dsp);
}
/* >NAME ( xt -- addr ) Convert execution token to name */
void dictionary_m_word_to_name(VM *vm) {
cell_t xt;
DictEntry *entry;
char *name_field;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
xt = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) xt;
name_field = get_name_field(entry);
if (name_field == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)name_field);
}
/* NAME> ( addr -- xt ) Convert name to execution token */
void dictionary_m_word_name_to(VM *vm) {
cell_t addr;
char *name_field;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
name_field = (char *) (uintptr_t) addr;
entry = find_entry_from_name(vm, name_field);
if (entry == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)entry);
}
/* >LINK ( addr -- addr ) Get link field address */
void dictionary_m_word_to_link(VM *vm) {
cell_t addr;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
if (entry == NULL) {
vm->error = 1;
return;
}
/* Link field is part of the DictEntry structure */
vm_push(vm, (cell_t)(uintptr_t) & entry->link);
}
/* LINK> ( addr -- addr ) Get next word from link */
void dictionary_m_word_link_from(VM *vm) {
cell_t addr;
DictEntry **link_field;
DictEntry *next_entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
link_field = (DictEntry **) (uintptr_t) addr;
if (link_field == NULL) {
vm->error = 1;
return;
}
next_entry = *link_field;
vm_push(vm, (cell_t)(uintptr_t)next_entry);
}
/* CFA ( addr -- xt ) Get code field address */
void dictionary_m_word_cfa(VM *vm) {
cell_t addr;
DictEntry *entry;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
if (entry == NULL) {
vm->error = 1;
return;
}
/* In our implementation, the execution token is the entry itself */
vm_push(vm, (cell_t)(uintptr_t)entry);
}
/* LFA ( addr -- addr ) Get link field address */
void dictionary_m_word_lfa(VM *vm) {
dictionary_m_word_to_link(vm); /* Same as >LINK */
}
/* NFA ( addr -- addr ) Get name field address */
void dictionary_m_word_nfa(VM *vm) {
cell_t addr;
DictEntry *entry;
char *name_field;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
name_field = get_name_field(entry);
if (name_field == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)name_field);
}
/* PFA ( addr -- addr ) Get parameter field address */
void dictionary_m_word_pfa(VM *vm) {
cell_t addr;
DictEntry *entry;
void *body_addr;
if (vm->dsp < 0) {
vm->error = 1;
return;
}
addr = vm_pop(vm);
entry = (DictEntry *) (uintptr_t) addr;
body_addr = get_body_address(entry);
if (body_addr == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)body_addr);
}
/* TRAVERSE ( addr n -- addr ) Move through name field */
void dictionary_m_word_traverse(VM *vm) {
cell_t n, addr;
char *name_addr;
char *result_addr;
if (vm->dsp < 1) {
vm->error = 1;
return;
}
n = vm_pop(vm);
addr = vm_pop(vm);
name_addr = (char *) (uintptr_t) addr;
result_addr = traverse_name_field(name_addr, (int) n);
if (result_addr == NULL) {
vm->error = 1;
return;
}
vm_push(vm, (cell_t)(uintptr_t)result_addr);
}
/* INTERPRET ( -- ) Text interpreter */
void dictionary_m_word_interpret(VM *vm) {
/* Simply call the VM's built-in interpreter - it handles everything */
/* The VM already has proper input management, word parsing, and execution */
/* In FORTH-79, INTERPRET processes the current input stream */
/* Since vm_interpret handles input parsing and execution, we just ensure */
/* we're in the right mode and let the VM do the work */
/* Set interpretation mode */
vm->mode = MODE_INTERPRET;
state_variable = 0;
/* The actual interpretation happens through vm_interpret() calls */
/* This word exists mainly for completeness and mode setting */
}
static void dictionary_m_word_find(VM *vm) {
char namebuf[128];
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
if (nlen <= 0) {
vm->error = 1; // real input underflow
return;
}
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
if (e) {
vm_push(vm, (cell_t)(uintptr_t)e); // compilation address; swap to CFA if you prefer later
} else {
vm_push(vm, 0); // miss is NOT an error
}
}
/* ' ( -- xt ) FORTH-79 tick: parse next word, push its execution token.
* Non-immediate — in compile mode the interpreter compiles a call to ',
* so this function only ever runs in interpret mode. */
static void dictionary_m_word_tick(VM *vm) {
char namebuf[128];
int nlen = vm_parse_word(vm, namebuf, sizeof namebuf);
if (nlen <= 0) {
log_message(LOG_ERROR, "': unable to parse word");
vm->error = 1;
return;
}
DictEntry *e = vm_find_word(vm, namebuf, (size_t) nlen);
if (!e) {
log_message(LOG_ERROR, "': word '%.*s' not found", nlen, namebuf);
vm->error = 1;
return;
}
log_message(LOG_DEBUG, "': found '%.*s' xt=%p", nlen, namebuf, (void *) e);
vm_push(vm, (cell_t)(uintptr_t)e);
}
// in src/word_source/dictionary_manipulation_words.c
static void dictionary_m_word_hidden(VM *vm) {
// compile-only guard (matches your other compile-only words)
if (vm->mode != MODE_COMPILE) {
vm->error = 1;
return;
}
// get the latest entry (use whatever you use elsewhere; many places access vm->latest)
DictEntry *e = vm->latest;
if (!e) {
vm->error = 1;
return;
}
// Set the hidden/smudge flag. Prefer a named flag if you have it.
// If your codebase uses WORD_HIDDEN, use that.
// If it uses the same bit as SMUDGE, use that (often called WORD_SMUDGED).
#ifdef WORD_HIDDEN
e->flags |= WORD_HIDDEN;
#else
// Fallback: if you only have a toggling SMUDGE path, set conditionally using it.
if (!(e->flags & WORD_SMUDGED)) {
// call your existing SMUDGE implementation to flip it on once
dictionary_word_smudge(vm);
// ensure we didn't accidentally set an error
if (vm->error) return;
}
#endif
}
/*
* @brief Register all dictionary manipulation words with the VM
* @param vm Pointer to VM instance
* @details Registers standard FORTH-79 dictionary manipulation words
*/
void register_dictionary_manipulation_words(VM *vm) {
/* Register all dictionary manipulation words */
/* [, ], STATE are NOT registered here: defining_words.c registers the
* same three names later in boot order (word_registry.c Module 17 vs
* this file's Module 13), and FORTH's newest-first dictionary lookup
* means defining_words.c's versions are the only ones ever reachable.
* The versions formerly here also wrote a dead file-scope static
* instead of vm->state_addr -- see proof/StarForth_Defining_Words.thy
* and proof/FINDINGS.md §1. */
register_word(vm, "SMUDGE", dictionary_m_word_smudge);
register_word(vm, "HIDDEN", dictionary_m_word_hidden);
register_word(vm, ">BODY", dictionary_m_word_to_body);
register_word(vm, ">NAME", dictionary_m_word_to_name);
register_word(vm, "NAME>", dictionary_m_word_name_to);
register_word(vm, ">LINK", dictionary_m_word_to_link);
register_word(vm, "LINK>", dictionary_m_word_link_from);
register_word(vm, "CFA", dictionary_m_word_cfa);
register_word(vm, "LFA", dictionary_m_word_lfa);
register_word(vm, "NFA", dictionary_m_word_nfa);
register_word(vm, "PFA", dictionary_m_word_pfa);
register_word(vm, "TRAVERSE", dictionary_m_word_traverse);
register_word(vm, "INTERPRET", dictionary_m_word_interpret);
register_word(vm, "FIND", dictionary_m_word_find);
register_word(vm, "'", dictionary_m_word_tick);
}