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>
469 lines
13 KiB
C
469 lines
13 KiB
C
/*
|
||
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);
|
||
} |