Files
LithosAnanake/src/word_source/editor_words.c
T
Robert Allan JamesandClaude Sonnet 5 4485c3893b Fix riscv64 hosted acceptance-test doc; port 3 clang-surfaced bugs from punch list
Addresses items #1 (partial) and #2 of
docs/working/archive/session-logs/2026-07-24-punch-list.md.

docs/lithosananke/hosted-acceptance-test/README.md:
- riscv64 leg used riscv64-linux-gnu-gcc, which fails to build this tree
  (nanosleep visibility under -std=c99). Replaced with the working
  clang-18 --target=riscv64-linux-gnu --sysroot=/usr/riscv64-linux-gnu
  invocation, verified end-to-end.
- All three arch sections referenced a -c "<script>" flag that has never
  existed in cli.c/main.c. Corrected to the working
  `echo "..." | starforth -s` pattern, verified on all three architectures.
- Updated Prerequisites: qemu-user alone is sufficient (guest binaries are
  static; qemu-user-static provides static *emulators*, not required here).

Source fixes (ported from the old pre-split monorepo's master, commit
4db9946a, where they were made but never carried over to this line):
- src/math_portable.c: `-100LL << 16` is UB (shifting a negative value)
  under clang's -Wshift-negative-value; changed to `-(100LL << 16)`.
- src/physics_pipelining_metrics.c: removed dead q48_mul_q48()
  (-Wunused-function under clang; GCC doesn't flag this by default).
- src/word_source/editor_words.c: removed dead set_scr() (same reason).

These three were required just to get the documented clang build to
compile at all. The punch list's higher-severity item — a genuine
SIGSEGV-causing register-reuse hazard in vm_pop_asm/vm_rpop_asm
(include/vm_asm_opt_riscv64.h) — is intentionally NOT included here; it's
a separate, more careful change and isn't required for this build to
succeed (latent only under clang; this repo's kernel build uses GCC).

Verified: all three hosted builds compile and run correctly (amd64
native, aarch64 via qemu-aarch64, riscv64 via qemu-riscv64), each
printing "3 Goodbye!" for the piped `1 2 + . BYE` script. All three
Makefile.starkernel builds (amd64/aarch64/riscv64) still compile cleanly
with these shared vendored-source changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:25:40 -04:00

344 lines
11 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) 20232025 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) 20232025 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.
*/
#include "include/editor_words.h" /* register_editor_words prototype */
#include "include/block_words.h" /* mark_buffer_dirty(), MAX_BLOCKS, BLOCK_SIZE */
#include "../../include/vm.h" /* VM, vm_load_cell, vm_store_cell, vm_addr_ok, vm_ptr */
#include "../../include/word_registry.h" /* register_word */
#include "../../include/log.h" /* log_message */
#include <stdio.h> /* fputs, fflush, puts, printf */
#include <string.h> /* memset, memcpy, strlen, isdigit, isspace */
#include <ctype.h> /* isdigit, isspace */
/* -------- helpers -------- */
/** @brief Get current screen number from VM
* @param vm Pointer to VM instance
* @return Current screen number
*/
static inline cell_t current_scr(VM *vm) {
return vm_load_cell(vm, vm->scr_addr);
}
/** @brief Get pointer to specific line in a screen
* @param vm Pointer to VM instance
* @param scr Screen number (1-based)
* @param line Line number (0-15)
* @param out Output parameter for line pointer
* @return 1 if successful, 0 if parameters invalid
*/
static int line_ptr(VM *vm, cell_t scr, cell_t line, unsigned char **out) {
if (!vm || !out) return 0;
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) return 0;
if (line < 0 || line > 15) return 0;
/* Use block subsystem to get block buffer */
extern uint8_t *blk_get_buffer(uint32_t block_num, int writable);
extern int blk_is_valid(uint32_t block_num);
if (!blk_is_valid((uint32_t) scr)) return 0;
uint8_t *block = blk_get_buffer((uint32_t) scr, 1); /* writable for editing */
if (!block) return 0;
*out = block + (line * 64);
return 1;
}
/** @brief Print 64-character line with character filtering
* @param p Pointer to 64-byte line buffer
*/
static void print_line_64(const unsigned char *p) {
#ifdef STARFORTH_ANSI
/* No cursor moves unless asked; just render content cleanly. */
#endif
for (int i = 0; i < 64; ++i) {
unsigned char c = p[i];
if (c == 0) { putchar(' '); } else if (c >= 32 && c <= 126) { putchar((int) c); } else {
putchar('.');
}
}
putchar('\n');
}
/* -------- words -------- */
/* L ( u -- ) : print line u of current SCR (0..15). Errors on empty/range/invalid SCR. */
static void editor_word_l(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "L: stack underflow (needs line# 0..15)");
return;
}
cell_t line = vm_pop(vm);
if (line < 0 || line > 15) {
vm->error = 1;
log_message(LOG_ERROR, "L: line out of range (0..15)");
return;
}
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "L: SCR out of range (SCR=%ld)", (long) scr);
return;
}
unsigned char *lp = NULL;
if (!line_ptr(vm, scr, line, &lp)) {
vm->error = 1;
log_message(LOG_ERROR, "L: address invalid for SCR=%ld line=%ld", (long) scr, (long) line);
return;
}
print_line_64(lp);
}
/* S ( c-addr len u -- ) : set line u from buffer (pads with spaces to 64, truncates if longer). */
static void editor_word_s(VM *vm) {
if (!vm) return;
/* Pop in the order that lets us validate line range before address checks (useful in tests). */
if (vm->dsp < 2) {
vm->error = 1;
log_message(LOG_ERROR, "S: stack underflow (needs c-addr len line#)");
return;
}
cell_t line = vm_pop(vm);
if (line < 0 || line > 15) {
vm->error = 1;
log_message(LOG_ERROR, "S: line out of range (0..15)");
/* Drain remaining args to keep stack sane */
(void) vm_pop(vm);
(void) vm_pop(vm);
return;
}
cell_t len = vm_pop(vm);
cell_t addr = vm_pop(vm);
if (len < 0) len = 0; /* defensive: negative length treated as 0 */
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "S: SCR out of range (SCR=%ld)", (long) scr);
return;
}
unsigned char *dst = NULL;
if (!line_ptr(vm, scr, line, &dst)) {
vm->error = 1;
log_message(LOG_ERROR, "S: destination address invalid");
return;
}
/* Source pointer */
if (!vm_addr_ok(vm, (vaddr_t) addr, (size_t) len)) {
vm->error = 1;
log_message(LOG_ERROR, "S: source range invalid (addr=%ld len=%ld)",
(long) addr, (long) len);
return;
}
const unsigned char *src = (const unsigned char *) vm_ptr(vm, (vaddr_t) addr);
/* Write: pad spaces, then copy up to 64 */
memset(dst, ' ', 64);
size_t n = (len > 64) ? 64 : (size_t) len;
if (n > 0) memcpy(dst, src, n);
mark_buffer_dirty(vm);
}
/* SHOW ( -- ) : print the whole 16x64 screen with simple line numbers. */
static void editor_word_show(VM *vm) {
if (!vm) return;
cell_t scr = current_scr(vm);
if (scr < 1 || scr >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "SHOW: SCR out of range (SCR=%ld)", (long) scr);
return;
}
#ifdef STARFORTH_ANSI
/* Clear + home (optional; compile-time opt-in) */
fputs("\x1b[2J\x1b[H", stdout);
printf("\x1b[1mScreen %ld\x1b[0m:\n", (long) scr);
#else
printf("Screen %ld:\n", (long) scr);
#endif
for (cell_t line = 0; line < 16; ++line) {
unsigned char *lp = NULL;
if (!line_ptr(vm, scr, line, &lp)) {
vm->error = 1;
return;
}
#ifdef STARFORTH_ANSI
printf("\x1b[90m%2ld:\x1b[0m ", (long) line);
#else
printf("%2ld: ", (long) line);
#endif
print_line_64(lp);
}
}
/* EDIT ( u -- ) : tiny line-editor shell (stdin/stdout). No raw mode, no curses. */
/* EDIT ( u -- ) : tiny line-editor shell (stdin/stdout). No raw mode, no curses. */
static void editor_word_edit(VM *vm) {
if (!vm) return;
if (vm->dsp < 0) {
vm->error = 1;
log_message(LOG_ERROR, "EDIT: stack underflow (needs block#)");
return;
}
cell_t blk = vm_pop(vm);
if (blk < 1 || blk >= (cell_t) MAX_BLOCKS) {
vm->error = 1;
log_message(LOG_ERROR, "EDIT: block out of range");
return;
}
vm_store_cell(vm, vm->scr_addr, blk);
editor_word_show(vm);
if (vm->error) return;
char buf[1024];
for (;;) {
printf("SCR %ld> ", (long) vm_load_cell(vm, vm->scr_addr));
fflush(stdout);
if (!fgets(buf, sizeof buf, stdin)) break;
/* Trim trailing newline */
size_t L = strlen(buf);
if (L && buf[L - 1] == '\n') buf[L - 1] = '\0';
if (buf[0] == 'q' || buf[0] == 'Q') break;
else if (buf[0] == 'h' || buf[0] == 'H') {
puts("Commands: p=print, l <n>=line, s <n> <text>=set, n=next, b=prev, w=write, q=quit");
} else if (buf[0] == 'p' || buf[0] == 'P') {
editor_word_show(vm);
} else if (buf[0] == 'l' || buf[0] == 'L') {
long ln = -1;
/* Simple number parsing to avoid sscanf */
const char *p = buf + 1;
while (*p == ' ' || *p == '\t') p++;
if (isdigit(*p)) {
ln = 0;
while (isdigit(*p)) {
ln = ln * 10 + (*p - '0');
p++;
}
vm_push(vm, (cell_t) ln);
editor_word_l(vm);
} else {
puts("usage: l <0..15>");
}
} else if (buf[0] == 's' || buf[0] == 'S') {
long ln = -1;
const char *p = buf + 1;
while (*p && isspace((unsigned char) *p)) ++p;
/* Parse number manually */
if (isdigit(*p)) {
ln = 0;
while (isdigit(*p)) {
ln = ln * 10 + (*p - '0');
p++;
}
/* Move past any remaining spaces */
while (*p && isspace((unsigned char) *p)) ++p;
/* p is now the text to write */
size_t tlen = strlen(p);
cell_t scr = vm_load_cell(vm, vm->scr_addr);
unsigned char *dst = NULL;
if (ln < 0 || ln > 15 || scr < 1 || scr >= (cell_t) MAX_BLOCKS ||
!line_ptr(vm, scr, (cell_t) ln, &dst)) {
puts("error: bad SCR/line");
} else {
memset(dst, ' ', 64);
size_t n = (tlen > 64) ? 64 : tlen;
if (n) memcpy(dst, p, n);
/* ✅ Use helpers declared in block_words.h (no implicit decl) */
mark_buffer_dirty(vm);
puts("ok");
}
} else {
puts("usage: s <0..15> <text>");
}
} else if (buf[0] == 'n' || buf[0] == 'N') {
cell_t s = vm_load_cell(vm, vm->scr_addr);
if (s + 1 < (cell_t) MAX_BLOCKS) vm_store_cell(vm, vm->scr_addr, s + 1);
editor_word_show(vm);
} else if (buf[0] == 'b' || buf[0] == 'B') {
cell_t s = vm_load_cell(vm, vm->scr_addr);
if (s - 1 >= 1) vm_store_cell(vm, vm->scr_addr, s - 1);
editor_word_show(vm);
} else if (buf[0] == 'w' || buf[0] == 'W') {
/* ✅ No more implicit decls: use helpers */
mark_buffer_dirty(vm);
save_all_buffers(vm);
puts("saved");
} else {
puts("h for help");
}
if (vm->error) {
puts("error");
vm->error = 0; /* keep shell alive */
}
}
}
/* -------- registration -------- */
void register_editor_words(VM *vm) {
if (!vm) return;
register_word(vm, "L", editor_word_l); /* non-79 */
register_word(vm, "S", editor_word_s); /* non-79 */
register_word(vm, "SHOW", editor_word_show); /* non-79 */
register_word(vm, "EDIT", editor_word_edit); /* non-79 */
}