mumu 0.9.1

Lava Mumu is a language for those in the now and that know
Documentation
// src/modules/repl/main_loop.rs

use crate::parser::lexer::tokenize;
use crate::parser::core::driver::{parse_tokens, ParseError};
use crate::parser::ast::{Stmt, Expr};
use crate::parser::interpreter::Interpreter;
use crate::{value_to_string, Value};

use super::history::{load_history, save_history_line};
use super::autocomplete::reset_known_commands_from_interp;
use super::input::read_line_with_arrows;
use super::render::{forcibly_clear_line, redraw_full, short_type_name};

enum ParseResult {
    Incomplete,
    Error(String),
    Success(Vec<Stmt>),
}

// UPDATED: add handling for AssignmentDestructure
fn attempt_parse(source: &str, verbose: bool) -> Result<ParseResult, String> {
    if verbose {
        eprintln!("[attempt_parse] => starting. source.len()={}", source.len());
    }
    let tokens = match tokenize(source, verbose) {
        Ok(t) => t,
        Err(lex_err) => {
            if verbose {
                eprintln!("[attempt_parse] => lex error => {}", lex_err.message);
            }
            return Err(lex_err.message);
        }
    };
    if verbose {
        eprintln!("[attempt_parse] => tokens => {:?}", tokens);
    }
    match parse_tokens(&tokens, verbose) {
        Ok(stmts) => {
            if stmts.is_empty() && !source.trim().is_empty() {
                if verbose {
                    eprintln!("[attempt_parse] => stmts.is_empty => Incomplete");
                }
                Ok(ParseResult::Incomplete)
            } else {
                if verbose {
                    eprintln!("[attempt_parse] => parse success => got {} stmts", stmts.len());
                }
                Ok(ParseResult::Success(stmts))
            }
        }
        Err(ParseError { msg, .. }) => {
            if verbose {
                eprintln!("[attempt_parse] => parse error => {}", msg);
            }
            let lower = msg.to_lowercase();
            if lower.contains("eof") || lower.contains("unexpected end of input") {
                Ok(ParseResult::Incomplete)
            } else {
                Ok(ParseResult::Error(msg))
            }
        }
    }
}

fn is_extend_or_include_call(stmt: &Stmt) -> bool {
    match stmt {
        Stmt::ImportSo { .. } => true,
        Stmt::Assignment { right, .. } => is_extend_or_include_expr(right),
        Stmt::ExprStmt { expr, .. } => is_extend_or_include_expr(expr),
        Stmt::AssignmentDestructure { right, .. } => is_extend_or_include_expr(right),
        Stmt::AssignmentScoped { right, .. } => is_extend_or_include_expr(right),
    }
}
fn is_extend_or_include_expr(expr: &Expr) -> bool {
    match expr {
        Expr::FunctionCall { callee, .. } => {
            match &**callee {
                Expr::Ident { name, .. } if name == "extend" || name == "include" => true,
                _ => false,
            }
        }
        _ => false,
    }
}

pub fn run_repl_mode(verbose: bool) -> Result<(), String> {
    use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
    let version = env!("CARGO_PKG_VERSION");
    println!("Burn in Lava {}", version);
    enable_raw_mode().map_err(|e| format!("enable_raw_mode error: {}", e))?;
    let mut history = load_history();
    let mut interpreter = Interpreter::new();
    interpreter.set_verbose(verbose);

    crate::modules::compose::register_compose_and_pipe(&mut interpreter);
    crate::modules::sput::register_sput(&mut interpreter);
    crate::modules::slog::register_slog(&mut interpreter);
    crate::modules::ink::register_ink(&mut interpreter);
    crate::modules::include::register_include(&mut interpreter);

    reset_known_commands_from_interp(&interpreter);

    let mut partial_code = String::new();

    loop {
        let prompt_str = if partial_code.is_empty() { "> " } else { "... " };
        let user_line = match read_line_with_arrows(
            verbose,
            &mut history,
            prompt_str,
            &mut interpreter,
            &redraw_full,
        ) {
            Ok(ln) => ln,
            Err(e) => {
                disable_raw_mode().ok();
                return Err(e);
            }
        };
        if partial_code.is_empty() && (user_line == ":q" || user_line == ":quit") {
            disable_raw_mode().ok();
            println!();
            interpreter.remove_all_pollers();
            return Ok(());
        }
        if user_line == "<<<EXIT>>>" {
            break;
        }
        save_history_line(&mut history, &user_line);
        partial_code.push_str(&user_line);
        partial_code.push('\n');
        let parse_result = match attempt_parse(&partial_code, verbose) {
            Ok(r) => r,
            Err(lex_msg) => {
                if verbose {
                    eprintln!("[REPL] => Lex error => {}", lex_msg);
                }
                let mut stdout = std::io::stdout();
                forcibly_clear_line(&mut stdout)?;
                eprintln!("Error: \"{}\"", lex_msg);
                partial_code.clear();
                continue;
            }
        };
        match parse_result {
            ParseResult::Incomplete => {
                if verbose {
                    eprintln!("[REPL] => parse => incomplete => prompt for more lines");
                }
                continue;
            }
            ParseResult::Error(msg) => {
                if verbose {
                    eprintln!("[REPL] => parse error => {}", msg);
                }
                let mut stdout = std::io::stdout();
                forcibly_clear_line(&mut stdout)?;
                eprintln!("Parse error: {}", msg);
                partial_code.clear();
            }
            ParseResult::Success(stmts) => {
                if verbose {
                    eprintln!("[REPL] => parse success => got {} stmts", stmts.len());
                }
                let mut stdout = std::io::stdout();
                forcibly_clear_line(&mut stdout)?;
                let mut last_val = None;
                let mut suppress_last_val = false;
                for (stmt_index, stmt) in stmts.iter().enumerate() {
                    let old_names: Vec<String> = interpreter
                        .get_dynamic_functions_for_clone()
                        .keys()
                        .cloned()
                        .collect();
                    let result = interpreter.exec_statement(stmt);
                    match result {
                        Ok(v) => {
                            let show_new_funcs = is_extend_or_include_call(stmt);
                            if show_new_funcs {
                                let new_names: Vec<String> = interpreter
                                    .get_dynamic_functions_for_clone()
                                    .keys()
                                    .cloned()
                                    .collect();
                                let mut newly_imported: Vec<String> = new_names
                                    .iter()
                                    .filter(|nm| !old_names.contains(nm))
                                    .cloned()
                                    .collect();
                                newly_imported.sort();
                                reset_known_commands_from_interp(&interpreter);
                                if !newly_imported.is_empty() {
                                    use crossterm::style::{SetForegroundColor, ResetColor, Color};
                                    use crossterm::queue;
                                    queue!(stdout, SetForegroundColor(Color::DarkRed)).ok();
                                    let val = Value::StrArray(newly_imported);
                                    print!("{}", value_to_string(&val));
                                    queue!(stdout, ResetColor).ok();
                                    println!();
                                }
                                suppress_last_val = true;
                            }
                            if matches!(stmt, Stmt::Assignment { .. }) {
                                reset_known_commands_from_interp(&interpreter);
                            }
                            last_val = Some(v);
                        }
                        Err(exec_err) => {
                            eprintln!("Runtime error in stmt {}: {}", stmt_index + 1, exec_err);
                            last_val = None;
                            break;
                        }
                    }
                }
                if let Some(val) = last_val {
                    if !suppress_last_val {
                        use crossterm::style::{SetForegroundColor, ResetColor, Color};
                        use crossterm::queue;
                        queue!(stdout, SetForegroundColor(Color::DarkRed)).ok();
                        print!("{}", value_to_string(&val));
                        queue!(stdout, ResetColor).ok();
                        let tname = short_type_name(&val);
                        queue!(stdout, SetForegroundColor(Color::DarkMagenta)).ok();
                        print!(" ({})", tname);
                        queue!(stdout, ResetColor).ok();
                        println!();
                    }
                }
                partial_code.clear();
            }
        }
    }
    disable_raw_mode().ok();
    interpreter.remove_all_pollers();
    Ok(())
}