#![cfg(feature = "repl")]
pub mod commands;
pub mod completion;
pub mod evaluation;
pub mod formatting;
pub mod state;
use anyhow::{Context, Result};
use rustyline::error::ReadlineError;
use rustyline::{Config, Editor};
use std::path::PathBuf;
use crate::frontend::parser::Parser;
use crate::runtime::interpreter::Interpreter;
use crate::runtime::value::Value;
use self::commands::{CommandContext, CommandRegistry, CommandResult};
use self::state::ReplState;
pub struct ExtremeQualityRepl {
commands: CommandRegistry,
state: ReplState,
interpreter: Interpreter,
work_dir: PathBuf,
}
impl ExtremeQualityRepl {
pub fn new(work_dir: PathBuf) -> Result<Self> {
Ok(Self {
commands: CommandRegistry::new(),
state: ReplState::new(),
interpreter: Interpreter::new(),
work_dir,
})
}
pub fn run(&mut self) -> Result<()> {
self.print_welcome();
let config = Config::builder().history_ignore_space(true).build();
let mut editor = Editor::<()>::with_config(config)?;
loop {
let prompt = self.get_prompt();
match editor.readline(&prompt) {
Ok(line) => {
if self.process_line(&line)? {
break; }
}
Err(ReadlineError::Interrupted) => {
println!("\nUse :quit to exit");
}
Err(ReadlineError::Eof) => {
println!("\nGoodbye!");
break;
}
Err(err) => {
eprintln!("Error: {:?}", err);
break;
}
}
}
self.save_history()?;
Ok(())
}
fn process_line(&mut self, line: &str) -> Result<bool> {
if line.trim().is_empty() {
return Ok(false);
}
self.state.add_to_history(line.to_string());
if line.starts_with(':') {
self.process_command(line)
} else {
self.evaluate_expression(line)?;
Ok(false)
}
}
fn process_command(&mut self, line: &str) -> Result<bool> {
let parts: Vec<&str> = line.split_whitespace().collect();
let command = parts.first().copied().unwrap_or("");
let mut context = CommandContext {
args: parts[1..].to_vec(),
state: &mut self.state,
};
match self.commands.execute(command, &mut context)? {
CommandResult::Exit => Ok(true),
CommandResult::Success(output) => {
println!("{}", output);
Ok(false)
}
CommandResult::ModeChange(mode) => {
println!("Switched to {} mode", mode);
Ok(false)
}
CommandResult::Silent => Ok(false),
}
}
fn evaluate_expression(&mut self, input: &str) -> Result<()> {
let mut parser = Parser::new(input);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(e) => {
eprintln!("Parse error: {}", e);
return Ok(());
}
};
if self.state.is_debug_mode() {
println!("AST: {:#?}", ast);
}
match self.interpreter.evaluate(&ast) {
Ok(value) => {
self.print_value(&value);
}
Err(e) => {
eprintln!("Evaluation error: {}", e);
}
}
Ok(())
}
fn print_value(&self, value: &Value) {
match value {
Value::Unit => {} Value::String(s) => println!("{}", s),
_ => println!("{}", value),
}
}
fn get_prompt(&self) -> String {
match self.state.get_mode() {
state::ReplMode::Debug => "debug> ".to_string(),
state::ReplMode::Transpile => "transpile> ".to_string(),
state::ReplMode::Ast => "ast> ".to_string(),
state::ReplMode::Normal => "ruchy> ".to_string(),
}
}
fn print_welcome(&self) {
println!("Ruchy REPL v{}", env!("CARGO_PKG_VERSION"));
println!("Type :help for commands or expressions to evaluate");
}
fn save_history(&self) -> Result<()> {
let history_file = self.work_dir.join("repl_history.txt");
let history = self.state.get_history().join("\n");
std::fs::write(history_file, history).context("Failed to save history")
}
pub fn load_history(&mut self) -> Result<()> {
let history_file = self.work_dir.join("repl_history.txt");
if !history_file.exists() {
return Ok(());
}
let contents = std::fs::read_to_string(history_file)?;
for line in contents.lines() {
self.state.add_to_history(line.to_string());
}
Ok(())
}
}
#[cfg(test)]
#[ignore = "New REPL tests not completed"]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_repl_creation() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf());
assert!(repl.is_ok());
}
#[test]
fn test_prompt_generation() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
assert_eq!(repl.get_prompt(), "ruchy> ");
repl.state.set_mode(state::ReplMode::Debug);
assert_eq!(repl.get_prompt(), "debug> ");
repl.state.set_mode(state::ReplMode::Transpile);
assert_eq!(repl.get_prompt(), "transpile> ");
}
#[test]
fn test_empty_line_processing() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
let should_exit = repl.process_line("").expect("operation should succeed in test");
assert!(!should_exit);
let should_exit = repl.process_line(" ").expect("operation should succeed in test");
assert!(!should_exit);
}
#[test]
fn test_command_detection() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
let should_exit = repl.process_line(":help").expect("operation should succeed in test");
assert!(!should_exit);
let should_exit = repl.process_line(":quit").expect("operation should succeed in test");
assert!(should_exit);
}
#[test]
fn test_history_saving_and_loading() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
repl.state.add_to_history("test1".to_string());
repl.state.add_to_history("test2".to_string());
repl.save_history().expect("operation should succeed in test");
let mut repl2 = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
repl2.load_history().expect("operation should succeed in test");
assert_eq!(repl2.state.get_history(), &["test1", "test2"]);
}
#[test]
fn test_value_printing() {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
repl.print_value(&Value::Unit);
repl.print_value(&Value::Int(42));
repl.print_value(&Value::String("hello".to_string()));
}
#[test]
fn test_complexity_compliance() {
}
#[cfg(test)]
#[ignore = "REPL property tests not implemented"]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_repl_never_panics_on_input(input: String) {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
let _ = repl.process_line(&input);
}
#[test]
fn test_commands_never_panic(cmd in ":[a-z]{1,20}") {
let temp_dir = TempDir::new().expect("operation should succeed in test");
let mut repl = ExtremeQualityRepl::new(temp_dir.path().to_path_buf()).expect("operation should succeed in test");
let _ = repl.process_line(&cmd);
}
}
}
}