#[cfg(test)] pub mod tests; pub mod lib; use rustyline::error::ReadlineError; use rustyline::Editor; use lib::environment::Environment; const EXIT_STRINGS: &[&str; 3] = &[",q", "exit", "quit"]; fn read(rl: &mut Editor) -> Result { let mut readline = rl.readline(">> "); let mut full_line = String::new(); fn count_parens(s: &str) -> Result { let mut lcount = 0; let mut rcount = 0; for c in s.chars() { match c { '(' => lcount += 1, ')' => rcount += 1, _ => {} } if rcount > lcount { return Err(()) } } Ok(lcount == rcount) } let mut loop_count = 0; loop { match readline { Ok(line) => { full_line.push_str(&line); match count_parens(&full_line) { Ok(true) => return Ok(full_line), Ok(false) => readline = rl.readline("... "), Err(_) => return Err("unexpected \")\"".to_string()) } }, Err(ReadlineError::Interrupted) => { return Err("user interrupt".to_string()) }, Err(ReadlineError::Eof) => { if loop_count > 0 { return Err("end of file".to_string()); } else { return Ok(EXIT_STRINGS[0].to_string()); } }, Err(err) => { return Err(format!("{:?}", err)); } } loop_count += 1 } } fn means_exit(input: &str) -> bool { EXIT_STRINGS.iter().any(|&s| s == input) } fn eval(env: &mut Environment, input: &str) -> String { let sexp = match lib::tokenize::tokenize(input) { Ok(x) => x, Err(f) => return f }; let res = lib::eval::eval(&sexp, env); match res { Ok(x) => format!("{}", x), Err(f) => format!("Error: {}", f) } } fn main() { let mut env = Environment::new(); let hist_file = "history.txt"; // `()` can be used when no completer is required let mut rl = Editor::<()>::new(); if rl.load_history(hist_file).is_err() { println!("No previous history."); } loop { let input_line = read(&mut rl); match input_line { Err(e) => { println!("Error: {}", e); continue; } Ok(expr) => { if means_exit(&expr) { break; } rl.add_history_entry(expr.as_str()); println!("{}", eval(&mut env, &expr)); } }; } rl.save_history(hist_file).unwrap(); }