use crate::interpreter::Interpreter;
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::ty::TypeEnv;
use crate::typechecker;
use crate::value::Value;
pub fn run_repl() {
println!("scala REPL (type :quit to exit, :help for help)");
let mut interp = Interpreter::new();
let rl = rustyline::DefaultEditor::new();
match rl {
Ok(mut rl) => {
loop {
let prompt = "scala> ";
match rl.readline(prompt) {
Ok(line) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if trimmed == ":quit" || trimmed == ":q" {
break;
}
if trimmed == ":help" {
println!("Commands:");
println!(" :quit, :q Exit the REPL");
println!(" :help Show this help");
println!(" :reset Reset the environment");
println!(" :type <expr> Show inferred type (prelude only — REPL defs ignored)");
continue;
}
if trimmed == ":reset" {
interp = Interpreter::new();
println!("Environment reset.");
continue;
}
if trimmed.starts_with(":type") {
let expr_src = trimmed.strip_prefix(":type").unwrap_or("").trim();
if expr_src.is_empty() {
eprintln!("usage: :type <expression>");
continue;
}
match preview_expression_type(expr_src) {
Ok(s) => println!("{}", s),
Err(msg) => eprintln!("{}", msg),
}
continue;
}
let full_input = if needs_continuation(&line) {
let mut buf = line.clone();
loop {
match rl.readline(" | ") {
Ok(cont) => {
buf.push_str("\n");
buf.push_str(&cont);
if !needs_continuation(&cont) && !buf.trim().ends_with('{') {
break;
}
}
Err(_) => break,
}
}
buf
} else {
line.clone()
};
let _ = rl.add_history_entry(&full_input);
match interp.run_source(&full_input) {
Ok(Value::Unit) => {}
Ok(v) => println!("{}", v),
Err(e) => eprintln!("{}", e),
}
}
Err(rustyline::error::ReadlineError::Interrupted) => continue,
Err(rustyline::error::ReadlineError::Eof) => break,
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
}
Err(_) => {
eprintln!("Warning: line editing not available, using basic input");
run_basic_repl(&mut interp);
}
}
}
fn needs_continuation(input: &str) -> bool {
let open = input.chars().filter(|&c| c == '{' || c == '(' || c == '[').count();
let close = input.chars().filter(|&c| c == '}' || c == ')' || c == ']').count();
if open > close {
return true;
}
let trimmed = input.trim();
trimmed.ends_with("def")
|| trimmed.ends_with("val")
|| trimmed.ends_with("var")
|| trimmed.ends_with("if")
|| trimmed.ends_with("else")
|| trimmed.ends_with("match")
|| trimmed.ends_with("case")
|| trimmed.ends_with("=>")
|| trimmed.ends_with("=")
|| trimmed.ends_with("while")
|| trimmed.ends_with("for")
|| trimmed.ends_with("yield")
|| trimmed.ends_with("try")
|| trimmed.ends_with("catch")
|| trimmed.ends_with("finally")
|| trimmed.ends_with("class")
|| trimmed.ends_with("trait")
|| trimmed.ends_with("object")
|| trimmed.ends_with("new")
|| trimmed.ends_with("extends")
|| trimmed.ends_with("with")
}
fn run_basic_repl(interp: &mut Interpreter) {
use std::io::{self, Write, BufRead};
let stdin = io::stdin();
let mut stdout = io::stdout();
loop {
print!("scala> ");
stdout.flush().unwrap();
let mut input = String::new();
match stdin.lock().read_line(&mut input) {
Ok(0) => break,
Ok(_) => {}
Err(_) => break,
}
let trimmed = input.trim();
if trimmed.is_empty() {
continue;
}
if trimmed == ":quit" || trimmed == ":q" {
break;
}
if trimmed == ":help" {
println!("Commands:");
println!(" :quit, :q Exit");
println!(" :help This help");
println!(" :reset Reset interpreter");
println!(" :type <expr> Type (prelude only)");
continue;
}
if trimmed == ":reset" {
*interp = Interpreter::new();
println!("Environment reset.");
continue;
}
if trimmed.starts_with(":type") {
let expr_src = trimmed.strip_prefix(":type").unwrap_or("").trim();
if expr_src.is_empty() {
eprintln!("usage: :type <expression>");
continue;
}
match preview_expression_type(expr_src) {
Ok(s) => println!("{}", s),
Err(msg) => eprintln!("{}", msg),
}
continue;
}
match interp.run_source(trimmed) {
Ok(Value::Unit) => {}
Ok(v) => println!("{}", v),
Err(e) => eprintln!("{}", e),
}
}
}
fn preview_expression_type(expr_src: &str) -> Result<String, String> {
let tokens = Lexer::tokenize(expr_src).map_err(|e| e.message)?;
let expr = Parser::parse_expr(tokens).map_err(|e| e.message)?;
let mut env = TypeEnv::new();
env.define_builtin_types();
typechecker::typecheck_expr_standalone(&expr, &mut env)
.map(|ty| ty.to_string())
.map_err(|e| e.to_string())
}