Add quotations [: ... ;] (Forth 2012 Core-ext 6.2.0455)
State-smart anonymous xt builder. Interpret mode leaves the xt on the data stack; compile mode emits a literal push into the enclosing word, so `: APPLY EXECUTE ; [: 1 2 + ;] APPLY` prints 3. Supported nested inside colon definitions via a new compile-frame stack (`Vec<CompileFrame>`). Each frame snapshots `compiling_name`, `compiling_word_id`, `compiling_word_addr`, `compiling_ir`, `control_stack`, `saw_create_in_def`, `compiling_locals`, and `state`. The inner [: ... ;] compiles its body as an anonymous word; on ;] the outer frame pops back and the xt is either pushed to the data stack (interpret mode) or compiled as a literal (compile mode). Also fixes a latent bug: `finish_colon_def` used to reveal `latest`, which breaks when intermediate dict entries (now including quotations) move `latest`. Each definition now tracks its own `compiling_word_addr` and uses `reveal_at`, matching the existing DOES> pattern. Five tests cover interpret, compile, inside-a-colon-def, two-level nesting, and the control-stack-travels-with-frame regression (outer IF/ELSE/THEN must still match around an inner [: ;]).
This commit is contained in:
+147
-2
@@ -261,6 +261,26 @@ pub struct ForthVM<R: Runtime> {
|
|||||||
next_wid: Arc<Mutex<u32>>,
|
next_wid: Arc<Mutex<u32>>,
|
||||||
/// xorshift64 PRNG state for RANDOM / RND-SEED.
|
/// xorshift64 PRNG state for RANDOM / RND-SEED.
|
||||||
rng_state: Arc<Mutex<u64>>,
|
rng_state: Arc<Mutex<u64>>,
|
||||||
|
/// Stacked compile state for nested definitions (quotations `[: ;]`).
|
||||||
|
compile_frames: Vec<CompileFrame>,
|
||||||
|
/// Dictionary address of the word currently being compiled. Set by
|
||||||
|
/// `start_colon_def` / `start_noname_def` / `start_quotation` so that
|
||||||
|
/// `finish_colon_def` can use `reveal_at` instead of `reveal()` — the
|
||||||
|
/// latter breaks when intermediate dictionary entries (quotations,
|
||||||
|
/// `DOES>` actions) have moved `latest`.
|
||||||
|
compiling_word_addr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snapshot of one compilation context. Pushed by `[:`, popped by `;]`.
|
||||||
|
struct CompileFrame {
|
||||||
|
compiling_name: Option<String>,
|
||||||
|
compiling_word_id: Option<WordId>,
|
||||||
|
compiling_word_addr: u32,
|
||||||
|
compiling_ir: Vec<IrOp>,
|
||||||
|
control_stack: Vec<ControlEntry>,
|
||||||
|
saw_create_in_def: bool,
|
||||||
|
compiling_locals: Vec<String>,
|
||||||
|
state: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> ForthVM<R> {
|
impl<R: Runtime> ForthVM<R> {
|
||||||
@@ -336,6 +356,8 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
.unwrap_or(0xDEAD_BEEF_CAFE_BABE);
|
.unwrap_or(0xDEAD_BEEF_CAFE_BABE);
|
||||||
Arc::new(Mutex::new(if seed == 0 { 0xDEAD_BEEF_CAFE_BABE } else { seed }))
|
Arc::new(Mutex::new(if seed == 0 { 0xDEAD_BEEF_CAFE_BABE } else { seed }))
|
||||||
},
|
},
|
||||||
|
compile_frames: Vec::new(),
|
||||||
|
compiling_word_addr: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
vm.register_primitives()?;
|
vm.register_primitives()?;
|
||||||
@@ -565,6 +587,15 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
return self.finish_colon_def();
|
return self.finish_colon_def();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quotations `[: ... ;]` — state-smart anonymous xt, nestable inside
|
||||||
|
// colon definitions via the compile-frame stack.
|
||||||
|
if token_upper == "[:" {
|
||||||
|
return self.start_quotation();
|
||||||
|
}
|
||||||
|
if token_upper == ";]" {
|
||||||
|
return self.finish_quotation();
|
||||||
|
}
|
||||||
|
|
||||||
// Words that must be handled in the outer interpreter because they
|
// Words that must be handled in the outer interpreter because they
|
||||||
// modify Rust-side VM state that host functions cannot access.
|
// modify Rust-side VM state that host functions cannot access.
|
||||||
match token_upper.as_str() {
|
match token_upper.as_str() {
|
||||||
@@ -1824,6 +1855,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
.dictionary
|
.dictionary
|
||||||
.create(&name, false)
|
.create(&name, false)
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
self.compiling_word_addr = self.dictionary.latest();
|
||||||
// Reveal immediately so it gets an xt but isn't findable by name
|
// Reveal immediately so it gets an xt but isn't findable by name
|
||||||
// (since the name is internal)
|
// (since the name is internal)
|
||||||
self.dictionary.reveal();
|
self.dictionary.reveal();
|
||||||
@@ -1858,6 +1890,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
self.compiling_name = Some(name);
|
self.compiling_name = Some(name);
|
||||||
self.compiling_word_id = Some(word_id);
|
self.compiling_word_id = Some(word_id);
|
||||||
|
self.compiling_word_addr = self.dictionary.latest();
|
||||||
self.compiling_ir.clear();
|
self.compiling_ir.clear();
|
||||||
self.control_stack.clear();
|
self.control_stack.clear();
|
||||||
self.state = -1;
|
self.state = -1;
|
||||||
@@ -1867,6 +1900,72 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `[:` — start a quotation. Saves the current compile frame (if any)
|
||||||
|
/// and begins compiling an anonymous inner definition. The inner xt is
|
||||||
|
/// produced by `;]`.
|
||||||
|
fn start_quotation(&mut self) -> anyhow::Result<()> {
|
||||||
|
let frame = CompileFrame {
|
||||||
|
compiling_name: self.compiling_name.take(),
|
||||||
|
compiling_word_id: self.compiling_word_id.take(),
|
||||||
|
compiling_word_addr: self.compiling_word_addr,
|
||||||
|
compiling_ir: std::mem::take(&mut self.compiling_ir),
|
||||||
|
control_stack: std::mem::take(&mut self.control_stack),
|
||||||
|
saw_create_in_def: self.saw_create_in_def,
|
||||||
|
compiling_locals: std::mem::take(&mut self.compiling_locals),
|
||||||
|
state: self.state,
|
||||||
|
};
|
||||||
|
self.compile_frames.push(frame);
|
||||||
|
|
||||||
|
let name = format!("_quot_{}_", self.next_table_index);
|
||||||
|
let word_id = self
|
||||||
|
.dictionary
|
||||||
|
.create(&name, false)
|
||||||
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
self.compiling_word_addr = self.dictionary.latest();
|
||||||
|
self.dictionary.reveal();
|
||||||
|
|
||||||
|
self.compiling_name = Some(name);
|
||||||
|
self.compiling_word_id = Some(word_id);
|
||||||
|
self.compiling_ir.clear();
|
||||||
|
self.control_stack.clear();
|
||||||
|
self.state = -1;
|
||||||
|
self.saw_create_in_def = false;
|
||||||
|
self.next_table_index = self.next_table_index.max(word_id.0 + 1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `;]` — finish the current quotation. Compiles its body as an anonymous
|
||||||
|
/// word, pops the saved outer frame, and either pushes the new xt on the
|
||||||
|
/// data stack (interpret mode) or emits a literal push into the outer IR
|
||||||
|
/// (compile mode).
|
||||||
|
fn finish_quotation(&mut self) -> anyhow::Result<()> {
|
||||||
|
if self.compile_frames.is_empty() {
|
||||||
|
anyhow::bail!(";]: no matching [:");
|
||||||
|
}
|
||||||
|
let inner_xt = self
|
||||||
|
.compiling_word_id
|
||||||
|
.ok_or_else(|| anyhow::anyhow!(";]: no active quotation"))?
|
||||||
|
.0;
|
||||||
|
self.finish_colon_def()?;
|
||||||
|
|
||||||
|
let frame = self.compile_frames.pop().unwrap();
|
||||||
|
self.compiling_name = frame.compiling_name;
|
||||||
|
self.compiling_word_id = frame.compiling_word_id;
|
||||||
|
self.compiling_word_addr = frame.compiling_word_addr;
|
||||||
|
self.compiling_ir = frame.compiling_ir;
|
||||||
|
self.control_stack = frame.control_stack;
|
||||||
|
self.saw_create_in_def = frame.saw_create_in_def;
|
||||||
|
self.compiling_locals = frame.compiling_locals;
|
||||||
|
self.state = frame.state;
|
||||||
|
|
||||||
|
if self.state != 0 {
|
||||||
|
self.push_ir(IrOp::PushI32(inner_xt as i32));
|
||||||
|
} else {
|
||||||
|
self.push_data_stack(inner_xt as i32)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Run all enabled optimization passes on an IR sequence.
|
/// Run all enabled optimization passes on an IR sequence.
|
||||||
fn optimize_ir(&self, ir: Vec<IrOp>, bodies: &HashMap<WordId, Vec<IrOp>>) -> Vec<IrOp> {
|
fn optimize_ir(&self, ir: Vec<IrOp>, bodies: &HashMap<WordId, Vec<IrOp>>) -> Vec<IrOp> {
|
||||||
optimize(ir, &self.config.opt, bodies)
|
optimize(ir, &self.config.opt, bodies)
|
||||||
@@ -1972,8 +2071,13 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// Instantiate and install in the table
|
// Instantiate and install in the table
|
||||||
self.instantiate_and_install(&compiled, word_id)?;
|
self.instantiate_and_install(&compiled, word_id)?;
|
||||||
|
|
||||||
// Reveal the word
|
// Reveal the word by its saved address (not LATEST, which may have
|
||||||
self.dictionary.reveal();
|
// moved due to intermediate dict entries — quotations, DOES> helpers).
|
||||||
|
if self.compiling_word_addr != 0 {
|
||||||
|
self.dictionary.reveal_at(self.compiling_word_addr);
|
||||||
|
} else {
|
||||||
|
self.dictionary.reveal();
|
||||||
|
}
|
||||||
// Check if IMMEDIATE was toggled (the word might be immediate)
|
// Check if IMMEDIATE was toggled (the word might be immediate)
|
||||||
let is_immediate = self.dictionary.find(&name).is_some_and(|(_, _, imm)| imm);
|
let is_immediate = self.dictionary.find(&name).is_some_and(|(_, _, imm)| imm);
|
||||||
self.sync_word_lookup(&name, word_id, is_immediate);
|
self.sync_word_lookup(&name, word_id, is_immediate);
|
||||||
@@ -7675,6 +7779,47 @@ mod tests {
|
|||||||
assert_eq!(vm.take_output(), "test");
|
assert_eq!(vm.take_output(), "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// Quotations: [: ... ;]
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quotation_interpret() {
|
||||||
|
assert_eq!(eval_stack("[: 42 ;] EXECUTE"), vec![42]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quotation_compile_mode() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate(": APPLY EXECUTE ;").unwrap();
|
||||||
|
vm.evaluate("[: 1 2 + ;] APPLY .").unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "3 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quotation_inside_colon_def() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate(": MYDUP [: DUP ;] EXECUTE ;").unwrap();
|
||||||
|
vm.evaluate("5 MYDUP").unwrap();
|
||||||
|
assert_eq!(vm.data_stack(), vec![5, 5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quotation_nested() {
|
||||||
|
assert_eq!(eval_stack("[: [: 1 ;] EXECUTE ;] EXECUTE"), vec![1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_quotation_inside_if() {
|
||||||
|
// Control stack must travel with the saved frame so the outer IF/ELSE
|
||||||
|
// still finds its matching THEN after an inner [: ... ;].
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate(": CHOOSE IF [: 1 ;] ELSE [: 2 ;] THEN EXECUTE ;")
|
||||||
|
.unwrap();
|
||||||
|
vm.evaluate("-1 CHOOSE 0 CHOOSE").unwrap();
|
||||||
|
assert_eq!(vm.data_stack(), vec![2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// Structures (BEGIN-STRUCTURE / +FIELD / FIELD: / CFIELD: / END-STRUCTURE)
|
// Structures (BEGIN-STRUCTURE / +FIELD / FIELD: / CFIELD: / END-STRUCTURE)
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user