Files
LithosAnanake/src/repl.c
T
Robert Allan JamesandClaude Sonnet 5 cc6c8c43f3 Fix ABORT to actually unwind to QUIT instead of one level
ABORT is documented and tested in this codebase as standard FORTH-79
behavior -- system_words_test.c:63: "Should clear stacks and return to
QUIT" -- meaning it should unwind all the way back to the outermost
interpreter loop, abandoning whatever's left of the current line/block.
The implementation only unwound one level: every place that checked
vm->abort_requested cleared it the instant it saw it, so it never
survived to propagate past the first nested frame.

This surfaced via Artemis's ART-HALT-UNRECOG (capsules/artemis/init.4th):
on an unrecognized disk it correctly printed "ARTEMIS HALT: unrecognized
disk content" and called ABORT, but WELCOME (the next line in the same
block) ran anyway, and Artemis announced ready to Hermes and joined the
fleet normally -- contradicting .claude/ARTEMIS.md's "Refuse to mount...
do not overwrite it" requirement. Root cause is general, not
Artemis-specific, and present identically in both the hosted and kernel
VM cores.

Fixed at every level execution can nest through, verified by exhaustively
grepping every !vm->error-gated continuation loop and adding the parallel
!vm->abort_requested check:

- execute_colon_word (src/vm.c, src/starkernel/vm/vm_core.c): stop
  clearing the flag on return -- every colon-word call is a recursive
  call to this same function, so leaving it set lets every enclosing
  frame's own check also unwind.
- vm_interpret (src/vm.c, src/starkernel/vm/vm_core.c): stop parsing
  further words in the current input string once the flag is set.
- exec_block_with_retry (src/starkernel/capsule/capsule_loader.c):
  capsule birth's line-by-line block executor -- stop processing further
  lines in the current block, but return 0 (not -1), so
  capsule_exec_payload still loads later blocks in the same capsule
  payload. Returning -1 here would have silently broken word definitions
  in blocks that come after the aborting one for reasons unrelated to
  why it aborted (concretely, Artemis's ART-PING/LOAD-DOE in blocks
  4851/4852, which follow the entry block 4133).
- THRU and --> (src/word_source/block_words.c): stop processing further
  blocks/lines in their own loops.
- DODOES (src/word_source/defining_words.c): the CREATE...DOES> runtime
  has its own hand-rolled execution loop, separate from
  execute_colon_word -- same bug class, same fix. Also guarded the
  post-loop "if (vm->rsp < base_rsp) vm->rsp = base_rsp" clamp so it
  doesn't fire on an abort exit -- ABORT's own reset_vm_state() already
  set rsp; restoring it to base_rsp would have partially undone that.
- Both REPL loops (src/repl.c, src/starkernel/repl.c x2 call sites):
  clear the flag after each line, mirroring the existing vm->error
  pattern, so a mid-line abort doesn't silently freeze subsequent
  interactive input.

Verified directly: ": AB-TEST 1 2 3 ABORT 999 . ;  AB-TEST 42 . CR
777 . CR" -- 999 never prints (stops mid-colon-word), 42 never prints
(stops the rest of the same line), 777 prints fine (next line
unaffected). Artemis: WELCOME/"Artemis ready" no longer fires after the
halt message. No regression: all three architectures still show PASS:
persist-read, PASS: E2E msg flow, and matching dict_hash on the normal
(non-aborted) boot path; hosted test suite 965 passed / 0 failed.

Known follow-up, not fixed here (see memory for details): Artemis still
announces ready to Hermes via a separate call path (CD-INIT, block 4141)
that never went through capsule_exec_payload's block chain in the first
place, and the disk file still picks up incidental writes even on a
correctly-halted boot -- likely generic block-subsystem housekeeping,
not traced yet.

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

144 lines
4.6 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/repl.h"
#include "../include/log.h"
#include "../include/vm.h"
#include <stdio.h>
/**
* @brief Extension point for non-recoverable VM errors (weak symbol).
*
* Called by the REPL when @c EMERGENCY_CONSOLE_ENABLED = 0 and an error
* occurs. The default implementation logs the fault at @c LOG_ERROR level
* and sets @c vm->halted = 1, causing the REPL loop to exit cleanly.
*
* Platforms that need hardware reset, watchdog kick, or JTAG debug-probe
* integration should provide a strong definition in a platform-specific file
* (e.g., @c starkernel/hal/fault.c) to override this weak default.
*
* @param vm Active VM whose @c vm->error triggered the fault path
*/
__attribute__((weak)) void vm_fault_handler(VM *vm) {
log_message(LOG_ERROR, "VM fault — emergency console disabled; halting");
vm->halted = 1;
}
/**
* @brief Starts the Forth REPL (Read-Eval-Print Loop)
*
* @param vm Pointer to the VM structure
* @param script_mode If 1, suppress prompts and "ok" output (for piped input)
*
* @details This function implements an interactive REPL for the Forth interpreter.
* It continuously reads input from the user, interprets it, and prints the result
* until the VM is halted or an error occurs.
*
* In interactive mode (script_mode=0):
* - Prints colored prompt "ok> "
* - Acknowledges successful commands with " ok"
* - Reports errors with " ERROR"
*
* In script mode (script_mode=1):
* - Suppresses all prompts and status messages
* - Suitable for piped input (heredoc, pipes, redirects)
*/
void vm_repl(VM *vm, int script_mode) {
if (!script_mode) {
log_message(LOG_INFO, "Starting Forth REPL");
}
char input[256];
while (!vm->halted && !vm->error) {
/* Print prompt only in interactive mode */
if (!script_mode) {
if (vm->zuse_session)
printf("\033[36mzuse)ok>\033[0m \033[92m");
else
printf("\033[36mok>\033[0m \033[92m");
fflush(stdout);
}
if (!fgets(input, sizeof(input), stdin)) {
if (!script_mode) {
printf("\033[0m");
fflush(stdout);
}
break;
}
if (!script_mode) {
printf("\033[0m");
fflush(stdout);
}
vm_interpret(vm, input);
/* ABORT stops mid-line (see vm_interpret/execute_colon_word) but the
* flag is left set for the caller to consume -- this REPL line is
* that boundary. Clear it here so the next line isn't silently
* refused by vm_interpret's own abort_requested check. */
vm->abort_requested = 0;
if (!script_mode) {
/* Print status only in interactive mode */
if (!vm->error) {
printf(" ok\n");
} else {
printf(" ERROR\n");
#if EMERGENCY_CONSOLE_ENABLED
vm->error = 0; /* recover: allow next input */
#else
vm_fault_handler(vm); /* non-recoverable: extension point */
#endif
}
} else {
if (vm->error) {
#if EMERGENCY_CONSOLE_ENABLED
vm->error = 0;
#else
vm_fault_handler(vm);
#endif
}
}
}
}