use std::io::{self, BufRead, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Parser;
use crate::aot;
use crate::fusevm_bridge::{eval_expr, eval_file, eval_source};
use crate::ported::eval::encode::encode_tv2echo;
use crate::ported::message::did_emsg;
use crate::script_cache;
use crate::viml_lexer::VimlError;
#[derive(Parser, Debug)]
#[command(
name = "vimlrs",
version,
about = "VimL (Vimscript) interpreter on the fusevm bytecode VM"
)]
pub struct Cli {
#[arg(short = 'e', long = "expr", value_name = "EXPR")]
expr: Option<String>,
#[arg(short = 'c', long = "cmd", value_name = "CMD")]
cmd: Option<String>,
#[arg(short = 'b', long = "build", value_name = "OUT")]
build: Option<PathBuf>,
#[arg(short = 'n', long = "native")]
native: bool,
#[arg(long = "clear-cache")]
clear_cache: bool,
#[arg(long = "lsp")]
lsp: bool,
#[arg(long = "dap")]
dap: bool,
#[arg(long = "disasm")]
disasm: bool,
files: Vec<PathBuf>,
}
fn had_error() -> bool {
did_emsg.with(|d| d.get()) != 0
}
fn exit_for_errors() -> ExitCode {
if had_error() {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
pub fn run() -> ExitCode {
let cli = Cli::parse();
if cli.lsp {
return match crate::lsp::run_stdio() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("vimlrs: lsp: {e}");
ExitCode::FAILURE
}
};
}
if cli.dap {
return match crate::dap::run_stdio() {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("vimlrs: dap: {e}");
ExitCode::FAILURE
}
};
}
crate::fusevm_disasm::set_enabled(cli.disasm);
if cli.clear_cache {
if let Some(cache) = script_cache::CACHE.as_ref() {
if let Err(e) = cache.clear() {
eprintln!("vimlrs: clear-cache: {e}");
return ExitCode::FAILURE;
}
}
return ExitCode::SUCCESS;
}
if let Some(out) = cli.build {
let result = if cli.native {
aot::build_native(&cli.files, &out)
} else {
aot::build(&cli.files, &out)
};
return match result {
Ok(p) => {
println!("{}", p.display());
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
};
}
if let Some(src) = cli.expr {
return match eval_expr(&src) {
Ok(v) => {
if !had_error() {
println!("{}", encode_tv2echo(&v));
}
exit_for_errors()
}
Err(e) => fail(e),
};
}
if let Some(src) = cli.cmd {
return match eval_source(&src) {
Ok(_) => exit_for_errors(),
Err(e) => fail(e),
};
}
if !cli.files.is_empty() {
let arglist: Vec<String> = cli
.files
.iter()
.map(|p| p.to_string_lossy().into_owned())
.collect();
crate::ported::eval::funcs::set_arglist(&arglist);
for path in &cli.files {
if let Err(e) = eval_file(path) {
return fail(e);
}
}
return exit_for_errors();
}
repl()
}
fn fail(e: VimlError) -> ExitCode {
eprintln!("{e}");
ExitCode::FAILURE
}
fn repl() -> ExitCode {
let stdin = io::stdin();
let mut out = io::stdout();
let prompt = |out: &mut io::Stdout| {
let _ = write!(out, "vimlrs> ");
let _ = out.flush();
};
prompt(&mut out);
for line in stdin.lock().lines() {
let line = match line {
Ok(l) => l,
Err(_) => break,
};
if line.trim().is_empty() {
prompt(&mut out);
continue;
}
match eval_source(&line) {
Ok(Some(v)) => println!("{}", encode_tv2echo(&v)),
Ok(None) => {}
Err(e) => eprintln!("{e}"),
}
prompt(&mut out);
}
println!();
ExitCode::SUCCESS
}