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.
|
//! WAFER CLI: Interactive REPL and AOT compiler for WAFER Forth.
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use wafer_core::outer::ForthVM;
|
||||||
|
|
||||||
/// WAFER: WebAssembly Forth Engine in Rust
|
/// WAFER: WebAssembly Forth Engine in Rust
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -21,21 +22,91 @@ struct Cli {
|
|||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let mut vm = ForthVM::new()?;
|
||||||
|
|
||||||
match cli.file {
|
match cli.file {
|
||||||
Some(ref _file) => {
|
Some(ref file) => {
|
||||||
// TODO: Step 9 - Load and execute Forth file
|
let source = std::fs::read_to_string(file)?;
|
||||||
eprintln!("WAFER: file execution not yet implemented");
|
vm.evaluate(&source)?;
|
||||||
|
let output = vm.take_output();
|
||||||
|
if !output.is_empty() {
|
||||||
|
print!("{output}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
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!(
|
println!(
|
||||||
"WAFER v{} - WebAssembly Forth Engine in Rust",
|
"WAFER v{} - WebAssembly Forth Engine in Rust",
|
||||||
env!("CARGO_PKG_VERSION")
|
env!("CARGO_PKG_VERSION")
|
||||||
);
|
);
|
||||||
println!("Type BYE to exit.");
|
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(())
|
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]
|
[dependencies]
|
||||||
wasm-encoder = { workspace = true }
|
wasm-encoder = { workspace = true }
|
||||||
wasmparser = { workspace = true }
|
wasmparser = { workspace = true }
|
||||||
|
wasmtime = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
insta = { 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.
|
/// Toggle the IMMEDIATE flag on the most recent word.
|
||||||
pub fn toggle_immediate(&mut self) -> WaferResult<()> {
|
pub fn toggle_immediate(&mut self) -> WaferResult<()> {
|
||||||
if self.latest == 0 && self.here == DICTIONARY_BASE {
|
if self.latest == 0 && self.here == DICTIONARY_BASE {
|
||||||
return Err(WaferError::CompileError(
|
return Err(WaferError::CompileError("no word defined yet".to_string()));
|
||||||
"no word defined yet".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let flags_addr = (self.latest + 4) as usize;
|
let flags_addr = (self.latest + 4) as usize;
|
||||||
if flags_addr >= self.memory.len() {
|
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