use crate::{Lambdust, Error, Result};
use std::io::{self, Write, BufRead};
#[cfg(feature = "minimal-repl")]
use colored::{Colorize};
#[derive(Debug, Clone)]
pub struct MinimalReplConfig {
pub use_colors: bool,
pub prompt: String,
pub show_timing: bool,
}
impl Default for MinimalReplConfig {
fn default() -> Self {
Self {
use_colors: true,
prompt: "λ> ".to_string(),
show_timing: false,
}
}
}
pub struct MinimalRepl {
config: MinimalReplConfig,
history: Vec<String>,
}
impl MinimalRepl {
pub fn new() -> Self {
Self {
config: MinimalReplConfig::default(),
history: Vec::new(),
}
}
pub fn with_config(config: MinimalReplConfig) -> Self {
Self {
config,
history: Vec::new(),
}
}
pub fn run(&mut self, lambdust: &mut Lambdust) -> Result<()> {
self.print_welcome();
let stdin = io::stdin();
let mut stdout = io::stdout();
loop {
self.print_prompt(&mut stdout)?;
let mut input = String::new();
match stdin.lock().read_line(&mut input) {
Ok(0) => break, Ok(_) => {
let input = input.trim();
if input.is_empty() {
continue;
}
if self.handle_meta_command(input, lambdust)? {
continue;
}
self.history.push(input.to_string());
self.evaluate_and_print(input, lambdust);
}
Err(e) => {
eprintln!("Error reading input: {e}");
break;
}
}
}
self.print_goodbye();
Ok(())
}
fn print_welcome(&self) {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
println!("{}", "Welcome to Lambdust (λust) - Minimal REPL".bright_green().bold());
println!("Type :help for available commands, :quit to exit");
} else {
println!("Welcome to Lambdust (λust) - Minimal REPL");
println!("Type :help for available commands, :quit to exit");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("Welcome to Lambdust (λust) - Minimal REPL");
println!("Type :help for available commands, :quit to exit");
}
println!();
}
fn print_goodbye(&self) {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
println!("\n{}", "Goodbye!".bright_blue());
} else {
println!("\nGoodbye!");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("\nGoodbye!");
}
}
fn print_prompt(&self, stdout: &mut io::Stdout) -> io::Result<()> {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
print!("{}", self.config.prompt.bright_cyan());
} else {
print!("{}", self.config.prompt);
}
}
#[cfg(not(feature = "minimal-repl"))]
{
print!("{}", self.config.prompt);
}
stdout.flush()
}
fn handle_meta_command(&self, input: &str, _lambdust: &mut Lambdust) -> Result<bool> {
if !input.starts_with(':') {
return Ok(false);
}
let command = &input[1..];
match command {
"quit" | "q" | "exit" => std::process::exit(0),
"help" | "h" => {
self.print_help();
Ok(true)
}
"history" => {
self.print_history();
Ok(true)
}
"clear" => {
for _ in 0..50 {
println!();
}
Ok(true)
}
_ => {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
eprintln!("{}: {}", "Unknown command".red(), command);
} else {
eprintln!("Unknown command: {command}");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
eprintln!("Unknown command: {}", command);
}
Ok(true)
}
}
}
fn print_help(&self) {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
println!("{}", "Available commands:".bright_yellow().bold());
println!(" {} - Show this help", ":help, :h".cyan());
println!(" {} - Exit REPL", ":quit, :q, :exit".cyan());
println!(" {} - Show command history", ":history".cyan());
println!(" {} - Clear screen", ":clear".cyan());
println!();
println!("{}", "Examples:".bright_yellow().bold());
println!(" {}", "(+ 1 2 3)".green());
println!(" {}", "(define x 42)".green());
println!(" {}", "(lambda (x) (* x x))".green());
} else {
println!("Available commands:");
println!(" :help, :h - Show this help");
println!(" :quit, :q, :exit - Exit REPL");
println!(" :history - Show command history");
println!(" :clear - Clear screen");
println!();
println!("Examples:");
println!(" (+ 1 2 3)");
println!(" (define x 42)");
println!(" (lambda (x) (* x x))");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("Available commands:");
println!(" :help, :h - Show this help");
println!(" :quit, :q, :exit - Exit REPL");
println!(" :history - Show command history");
println!(" :clear - Clear screen");
println!();
println!("Examples:");
println!(" (+ 1 2 3)");
println!(" (define x 42)");
println!(" (lambda (x) (* x x))");
}
}
fn print_history(&self) {
if self.history.is_empty() {
println!("No history available.");
return;
}
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
println!("{}", "Command history:".bright_yellow());
for (i, cmd) in self.history.iter().enumerate() {
println!(" {:3}: {}", i + 1, cmd);
}
} else {
println!("Command history:");
for (i, cmd) in self.history.iter().enumerate() {
println!(" {:3}: {}", i + 1, cmd);
}
}
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("Command history:");
for (i, cmd) in self.history.iter().enumerate() {
println!(" {:3}: {}", i + 1, cmd);
}
}
}
fn evaluate_and_print(&self, input: &str, lambdust: &mut Lambdust) {
let start_time = if self.config.show_timing {
Some(std::time::Instant::now())
} else {
None
};
match lambdust.eval(input, Some("<repl>")) {
Ok(value) => {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
println!("{}", format!("=> {value}").green());
} else {
println!("=> {value}");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("=> {}", value);
}
if let Some(start) = start_time {
let elapsed = start.elapsed();
#[cfg(feature = "minimal-repl")]
{
println!("(evaluated in {elapsed:?})");
}
#[cfg(not(feature = "minimal-repl"))]
{
println!("(evaluated in {:?})", elapsed);
}
}
}
Err(e) => {
#[cfg(feature = "minimal-repl")]
{
if self.config.use_colors {
eprintln!("{}: {}", "Error".red().bold(), e);
} else {
eprintln!("Error: {e}");
}
}
#[cfg(not(feature = "minimal-repl"))]
{
eprintln!("Error: {}", e);
}
}
}
}
}
impl Default for MinimalRepl {
fn default() -> Self {
Self::new()
}
}
pub fn start_minimal_repl(lambdust: &mut Lambdust) -> Result<()> {
MinimalRepl::new().run(lambdust)
}
pub fn start_minimal_repl_with_config(
lambdust: &mut Lambdust,
config: MinimalReplConfig
) -> Result<()> {
MinimalRepl::with_config(config).run(lambdust)
}