scala 0.1.1

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::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())
}