From 5dccc1ac9ebe2a14a4de3481e83a166ec42b56e5 Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Mon, 13 Apr 2026 18:33:13 +0200 Subject: [PATCH] Add WORDS for Programming-Tools word set Walk dictionary linked list, print all visible word names. Uses pending_define mechanism for dictionary access. --- crates/core/src/dictionary.rs | 22 ++++++++++++++++++ crates/core/src/outer.rs | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/crates/core/src/dictionary.rs b/crates/core/src/dictionary.rs index 3fdb2f6..eeaaf50 100644 --- a/crates/core/src/dictionary.rs +++ b/crates/core/src/dictionary.rs @@ -409,6 +409,28 @@ impl Dictionary { Ok(()) } + /// Return names of all visible (non-hidden) words, newest first. + pub fn visible_words(&self) -> Vec { + 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 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]) + .to_string(); + names.push(name); + } + let link = self.read_u32_unchecked(addr); + if link == addr { + break; + } + addr = link; + } + names + } + /// Get a reference to the raw memory buffer. pub fn memory(&self) -> &[u8] { &self.memory diff --git a/crates/core/src/outer.rs b/crates/core/src/outer.rs index ffe7735..bbbbbb9 100644 --- a/crates/core/src/outer.rs +++ b/crates/core/src/outer.rs @@ -2584,6 +2584,7 @@ impl ForthVM { // -- Programming-Tools word set -- self.register_n_to_r()?; + self.register_words()?; // -- Search-Order word set -- self.register_search_order()?; @@ -4704,6 +4705,7 @@ impl ForthVM { self.dictionary.set_current_wid(top); } } + 40 => self.do_words(), _ => {} } } @@ -5328,6 +5330,16 @@ impl ForthVM { Ok(()) } + /// WORDS ( -- ) Print all visible dictionary words. + fn do_words(&mut self) { + let names = self.dictionary.visible_words(); + let mut out = self.output.lock().unwrap(); + for name in &names { + out.push_str(name); + out.push(' '); + } + } + /// Register Search-Order word set words. fn register_search_order(&mut self) -> anyhow::Result<()> { // FORTH-WORDLIST ( -- wid ) @@ -5553,6 +5565,17 @@ impl ForthVM { Ok(()) } + /// Register WORDS for the Programming-Tools word set. + fn register_words(&mut self) -> anyhow::Result<()> { + let pending = Arc::clone(&self.pending_define); + let func: HostFn = Box::new(move |_ctx: &mut dyn HostAccess| { + pending.lock().unwrap().push(40); // WORDS action + Ok(()) + }); + self.register_host_primitive("WORDS", false, func)?; + Ok(()) + } + /// Register UNESCAPE, SUBSTITUTE, REPLACES for the String word set. fn register_string_substitution(&mut self) -> anyhow::Result<()> { // UNESCAPE ( c-addr1 u1 c-addr2 -- c-addr2 u2 ) @@ -7942,6 +7965,27 @@ mod tests { ); } + // =================================================================== + // WORDS (Programming-Tools) + // =================================================================== + + #[test] + fn test_words_lists_defined_words() { + let output = eval_output("WORDS"); + // Should contain standard primitives + assert!(output.contains("DUP")); + assert!(output.contains("DROP")); + assert!(output.contains("SWAP")); + assert!(output.contains("+")); + assert!(output.contains("WORDS")); + } + + #[test] + fn test_words_includes_user_defined() { + let output = eval_output(": MYTEST 42 ; WORDS"); + assert!(output.contains("MYTEST")); + } + // =================================================================== // Double DOES>: Forth 2012 WEIRD: W1 test // ===================================================================