scala 0.1.0

A experimental Scala interpreter written in Rust: lexer, parser, type inference, and tree-walking evaluation with a REPL.
Documentation
use crate::interpreter::Interpreter;
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 the type of an expression");
                            continue;
                        }
                        if trimmed == ":reset" {
                            interp = Interpreter::new();
                            println!("Environment reset.");
                            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;
        }

        match interp.run_source(trimmed) {
            Ok(Value::Unit) => {}
            Ok(v) => println!("{}", v),
            Err(e) => eprintln!("{}", e),
        }
    }
}