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};
use std::io::Write;
enum ParseResult {
Incomplete,
Error(String),
Success(Vec<Stmt>),
}
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)?;
for line in lex_msg.lines() {
crossterm::queue!(stdout, crossterm::cursor::MoveToColumn(0)).ok();
writeln!(stdout, "Error: {}", line.trim_end()).ok();
stdout.flush().ok();
}
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)?;
for line in msg.lines() {
crossterm::queue!(stdout, crossterm::cursor::MoveToColumn(0)).ok();
writeln!(stdout, "Parse error: {}", line.trim_end()).ok();
stdout.flush().ok();
}
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) => {
let mut stdout = std::io::stdout();
forcibly_clear_line(&mut stdout).ok();
for line in exec_err.lines() {
crossterm::queue!(stdout, crossterm::cursor::MoveToColumn(0)).ok();
writeln!(stdout, "{}", line.trim_end()).ok();
stdout.flush().ok();
}
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(())
}