feat(repl): usability batch - errors, WORDS, .S, BYE, DUMP, history
Core:
- Uncaught THROW prints its standard message ("Stack underflow
(throw -4)"; unknown codes as "Catch = <n>") instead of the
"forth-throw" sentinel. ABORT" text is carried as a structured
payload and shown only when the -2 throw goes uncaught -- CATCH
stays silent and the payload cannot go stale. ABORT throws -1
through the same path.
- WORDS: optional same-line substring filter (WORDS FDEPTH), skips
internal words (new INTERNAL header flag, set at create for
underscore-prefixed names), wraps at 78 columns, prints a count.
ORDER names wids (FORTH / wid#N) instead of Rust debug output.
- .S honors BASE. New: F.S (float stack), DUMP (hex+ASCII,
bounds-checked, 4K cap), ? (fetch-and-print, boot.fth). BYE is a
real word now: sets a VM flag the driver honors (exits REPL,
stops rest of line/file).
CLI:
- Persistent history (~/.local/state/wafer/history, 0600 perms,
$WAFER_HISTORY override), Tab completion over the live dictionary
(snapshot refreshed after each line), Up/Down do prefix history
search, Ctrl-C clears the line instead of exiting.
Web:
- History survives reloads (localStorage, cap 200, dedup, init-code
runs excluded), User Words palette populated via new words()
export, stack bar annotates non-decimal BASE, base() reads the
real BASE sysvar instead of returning a hardcoded 10.
This commit is contained in:
@@ -18,6 +18,8 @@ pub mod flags {
|
||||
pub const IMMEDIATE: u8 = 0x80;
|
||||
/// Word is hidden (being compiled, not yet findable).
|
||||
pub const HIDDEN: u8 = 0x40;
|
||||
/// Word is an implementation detail: findable, but skipped by WORDS.
|
||||
pub const INTERNAL: u8 = 0x20;
|
||||
/// Mask for the name length (lower 5 bits).
|
||||
pub const LENGTH_MASK: u8 = 0x1F;
|
||||
/// Maximum word name length.
|
||||
@@ -95,11 +97,17 @@ impl Dictionary {
|
||||
// Write link field (points to previous LATEST)
|
||||
self.write_u32_unchecked(entry_start, self.latest);
|
||||
|
||||
// Write flags byte: HIDDEN | length, optionally IMMEDIATE
|
||||
// Write flags byte: HIDDEN | length, optionally IMMEDIATE.
|
||||
// Underscore-prefixed names are implementation details by repo
|
||||
// convention (see tools/editor-support): flag them INTERNAL so
|
||||
// WORDS and completion skip them while FIND still works.
|
||||
let mut flag_byte = flags::HIDDEN | (name_len as u8 & flags::LENGTH_MASK);
|
||||
if immediate {
|
||||
flag_byte |= flags::IMMEDIATE;
|
||||
}
|
||||
if name_bytes.first() == Some(&b'_') {
|
||||
flag_byte |= flags::INTERNAL;
|
||||
}
|
||||
self.memory[(entry_start + 4) as usize] = flag_byte;
|
||||
|
||||
// Write name bytes
|
||||
@@ -410,12 +418,15 @@ impl Dictionary {
|
||||
}
|
||||
|
||||
/// Return names of all visible (non-hidden) words, newest first.
|
||||
pub fn visible_words(&self) -> Vec<String> {
|
||||
/// With `include_internal` false, words flagged INTERNAL are skipped.
|
||||
pub fn visible_words(&self, include_internal: bool) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
let mut addr = self.latest;
|
||||
while addr != 0 {
|
||||
let flags_byte = self.memory[(addr + 4) as usize];
|
||||
if flags_byte & flags::HIDDEN == 0 {
|
||||
let skip = flags_byte & flags::HIDDEN != 0
|
||||
|| (!include_internal && flags_byte & flags::INTERNAL != 0);
|
||||
if !skip {
|
||||
let name_len = (flags_byte & flags::LENGTH_MASK) as usize;
|
||||
let name_start = (addr + 5) as usize;
|
||||
let name = String::from_utf8_lossy(&self.memory[name_start..name_start + name_len])
|
||||
|
||||
Reference in New Issue
Block a user