Implement core Forth runtime: dictionary, codegen, outer interpreter, REPL
- Dictionary: linked-list word headers in simulated linear memory with create/find/reveal, case-insensitive lookup, IMMEDIATE flag support - WASM codegen: IR-to-WASM translation via wasm-encoder with full validation; all stack, arithmetic, comparison, logic, memory, control flow, and return stack operations; wasmtime execution tests - Outer interpreter: tokenizer, number parsing (decimal/$hex/#dec/%bin), interpret/compile dispatch, control structures (IF/ELSE/THEN, BEGIN/UNTIL, BEGIN/WHILE/REPEAT), RECURSE, comments, string output - 40+ primitive words registered via JIT-compiled WASM modules linked to shared memory/globals/table - Interactive REPL with rustyline, piped input, and file execution - 145 tests passing across dictionary, codegen, and runtime
This commit is contained in:
+76
-5
@@ -1,6 +1,7 @@
|
||||
//! WAFER CLI: Interactive REPL and AOT compiler for WAFER Forth.
|
||||
|
||||
use clap::Parser;
|
||||
use wafer_core::outer::ForthVM;
|
||||
|
||||
/// WAFER: WebAssembly Forth Engine in Rust
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -21,21 +22,91 @@ struct Cli {
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut vm = ForthVM::new()?;
|
||||
|
||||
match cli.file {
|
||||
Some(ref _file) => {
|
||||
// TODO: Step 9 - Load and execute Forth file
|
||||
eprintln!("WAFER: file execution not yet implemented");
|
||||
Some(ref file) => {
|
||||
let source = std::fs::read_to_string(file)?;
|
||||
vm.evaluate(&source)?;
|
||||
let output = vm.take_output();
|
||||
if !output.is_empty() {
|
||||
print!("{output}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// TODO: Step 9 - Interactive REPL
|
||||
// Check if stdin is a pipe (not a TTY)
|
||||
if !atty_is_tty() {
|
||||
// Non-interactive: read all of stdin and evaluate
|
||||
let mut input = String::new();
|
||||
std::io::Read::read_to_string(&mut std::io::stdin(), &mut input)?;
|
||||
// Evaluate line-by-line to handle multi-line input
|
||||
for line in input.lines() {
|
||||
match vm.evaluate(line) {
|
||||
Ok(()) => {
|
||||
let output = vm.take_output();
|
||||
if !output.is_empty() {
|
||||
print!("{output}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Interactive REPL
|
||||
println!(
|
||||
"WAFER v{} - WebAssembly Forth Engine in Rust",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
println!("Type BYE to exit.");
|
||||
eprintln!("REPL not yet implemented");
|
||||
|
||||
let mut rl = rustyline::DefaultEditor::new()?;
|
||||
loop {
|
||||
let prompt = if vm.is_compiling() { " ] " } else { "> " };
|
||||
match rl.readline(prompt) {
|
||||
Ok(line) => {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.eq_ignore_ascii_case("BYE") {
|
||||
break;
|
||||
}
|
||||
let _ = rl.add_history_entry(&line);
|
||||
match vm.evaluate(&line) {
|
||||
Ok(()) => {
|
||||
let output = vm.take_output();
|
||||
if !output.is_empty() {
|
||||
print!("{output}");
|
||||
}
|
||||
if !vm.is_compiling() {
|
||||
println!(" ok");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(
|
||||
rustyline::error::ReadlineError::Interrupted
|
||||
| rustyline::error::ReadlineError::Eof,
|
||||
) => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Readline error: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if stdin is a terminal (TTY).
|
||||
fn atty_is_tty() -> bool {
|
||||
use std::io::IsTerminal;
|
||||
std::io::stdin().is_terminal()
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ license.workspace = true
|
||||
[dependencies]
|
||||
wasm-encoder = { workspace = true }
|
||||
wasmparser = { workspace = true }
|
||||
wasmtime = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
wasmtime = { workspace = true }
|
||||
|
||||
+1389
-11
File diff suppressed because it is too large
Load Diff
@@ -297,9 +297,7 @@ impl Dictionary {
|
||||
/// Toggle the IMMEDIATE flag on the most recent word.
|
||||
pub fn toggle_immediate(&mut self) -> WaferResult<()> {
|
||||
if self.latest == 0 && self.here == DICTIONARY_BASE {
|
||||
return Err(WaferError::CompileError(
|
||||
"no word defined yet".to_string(),
|
||||
));
|
||||
return Err(WaferError::CompileError("no word defined yet".to_string()));
|
||||
}
|
||||
let flags_addr = (self.latest + 4) as usize;
|
||||
if flags_addr >= self.memory.len() {
|
||||
|
||||
+1326
-8
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user