use std::collections::HashMap; use std::fs::File; use std::io::{PipeReader, PipeWriter}; use std::path::PathBuf; use std::process::Child; use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use crate::parse::{self, Ast, PostExpansion, PreExpansion}; use crate::*; mod builtin; #[derive(Debug)] pub enum ExecError { UnknownVariable(BString), ExecError(i32), SpawnIO(String, std::io::Error), IO(std::io::Error), ErrorStack(Vec), Panic, } impl ExecError { pub fn error_message(&self) -> String { match self { ExecError::UnknownVariable(items) => { format!("unknown variable: {}", String::from_utf8_lossy(&items)) } ExecError::ExecError(exit_code) => format!("{exit_code}"), ExecError::SpawnIO(cmd, error) => match error.kind() { io::ErrorKind::NotFound => format!("{cmd} not found"), io::ErrorKind::PermissionDenied => { format!("{cmd} is not executable") } io::ErrorKind::FileTooLarge => format!("{cmd} is too massive"), io::ErrorKind::ResourceBusy | io::ErrorKind::ExecutableFileBusy => { format!("{cmd} is busy") } io::ErrorKind::TooManyLinks => { format!("{cmd} could not be resolved") } io::ErrorKind::InvalidFilename => { format!("{cmd} is not a valid file name") } io::ErrorKind::ArgumentListTooLong => format!("too many arguments"), io::ErrorKind::Interrupted => format!("got interrupted"), io::ErrorKind::Unsupported => format!("{cmd} is not supported"), e => format!("I am surprised you can get this error here: {e:?}"), }, ExecError::IO(error) => format!("{error:?}"), ExecError::Panic => format!("worker thread panicked"), ExecError::ErrorStack(stack) => { let mut out = String::new(); for e in stack.iter() { if !out.is_empty() { out += "\n"; } out += &e.error_message(); } out } } } } impl From for ExecError { fn from(value: BuiltinError) -> Self { match value { BuiltinError::IO(error) => Self::IO(error), BuiltinError::Exit(code) => Self::ExecError(code), } } } impl From for ExecError { fn from(value: std::io::Error) -> Self { Self::IO(value) } } #[derive(Clone)] pub struct Executor { se: Arc>, args: Option>, expand_commands: bool, } pub enum Input { Stdin, Pipe(PipeReader), File(File), } pub enum Output { Stdout, Pipe(PipeWriter), File(File), } impl Into for Input { fn into(self) -> Stdio { match self { Input::Stdin => Stdio::inherit(), Input::Pipe(reader) => reader.into(), Input::File(file) => file.into(), } } } impl Into for Output { fn into(self) -> Stdio { match self { Output::Stdout => Stdio::inherit(), Output::Pipe(writer) => writer.into(), Output::File(file) => file.into(), } } } impl Read for Input { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { Input::Stdin => io::stdin().read(buf), Input::Pipe(reader) => reader.read(buf), Input::File(file) => file.read(buf), } } } impl Write for Output { fn write(&mut self, buf: &[u8]) -> io::Result { match self { Output::Stdout => io::stdout().write(buf), Output::Pipe(writer) => writer.write(buf), Output::File(file) => file.write(buf), } } fn flush(&mut self) -> io::Result<()> { match self { Output::Stdout => io::stdout().flush(), Output::Pipe(writer) => writer.flush(), Output::File(file) => file.flush(), } } } enum SpawnedCmd { Builtin(JoinHandle>), Fun(JoinHandle>), Child(Child), SpawnError(io::Error), } impl SpawnedCmd { fn join(self) -> Result<(), ExecError> { match self { SpawnedCmd::Builtin(handle) => handle.join().map_err(|_| ExecError::Panic)??, SpawnedCmd::Fun(handle) => handle.join().map_err(|_| ExecError::Panic)??, SpawnedCmd::Child(mut child) => { let exit_code = child.wait()?; match exit_code.code() { Some(0) => (), Some(x) => Err(ExecError::ExecError(x))?, None => Err(ExecError::ExecError(-1))?, } } SpawnedCmd::SpawnError(err) => Err(err)?, } Ok(()) } } impl Executor { pub fn new_for_completion(se: Arc>) -> Self { Self { se, args: None, expand_commands: false, } } fn spawn_cmd( &mut self, cmd: CommandKind, args: Vec, mut stdin: Input, mut stdout: Output, ) -> SpawnedCmd { match cmd { CommandKind::Builtin(builtin) => { builtin.special(self.se.clone(), &args[1..]); let cloned_session = self.se.clone(); let handle = std::thread::spawn(move || { builtin.io(cloned_session, &args[1..], &mut stdin, &mut stdout) }); SpawnedCmd::Builtin(handle) } CommandKind::Fun(ast) => { let mut this = self.clone(); this.args = Some(args); let handle = std::thread::spawn(move || { let ast = ast.expand(&mut this)?; this.execute(ast, stdin, stdout)?; Ok(()) }); SpawnedCmd::Fun(handle) } CommandKind::Path(path) => { let mut command = Command::new(&path); for arg in args.iter().skip(1) { command.arg(OsStr::from_bytes(arg)); } command.stdin(stdin); command.stdout(stdout); crate::export_fun::prepare_command(self.se.clone(), &mut command); match command.spawn() { Ok(c) => SpawnedCmd::Child(c), Err(e) => SpawnedCmd::SpawnError(e), } } } } fn execute_pipeline( &mut self, pipes: parse::Pipes, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { let mut inputs = Vec::with_capacity(pipes.cmds.len()); let mut outputs = Vec::with_capacity(pipes.cmds.len()); let mut executors = Vec::with_capacity(pipes.cmds.len()); inputs.push(stdin); for _ in 1..pipes.cmds.len() { let (r, w) = io::pipe().unwrap(); inputs.push(Input::Pipe(r)); outputs.push(Output::Pipe(w)); } outputs.push(stdout); assert_eq!(pipes.cmds.len(), inputs.len()); assert_eq!(pipes.cmds.len(), outputs.len()); for (mut cmd, (stdin, stdout)) in pipes .cmds .into_iter() .zip(inputs.into_iter().zip(outputs.into_iter())) { let dc = get_command_kind(&self.se.lock().unwrap(), &cmd.cmd[..]); cmd.args.insert(0, cmd.cmd.clone()); executors.push(self.spawn_cmd(dc, cmd.args, stdin, stdout)); } let mut code = None; for e in executors { if let Some(err) = e.join().err() { code = Some(err); } } if let Some(err) = code { Err(err) } else { Ok(()) } } fn execute_var_assign(&mut self, va: parse::VarAssign) -> Result<(), ExecError> { self.se.lock().unwrap().vars.insert(va.var, va.val); Ok(()) } fn execute_fun_decl(&mut self, fd: parse::FunDecl) -> Result<(), ExecError> { self.se .lock() .unwrap() .funs .insert(fd.name.clone(), *fd.body.body); crate::export_fun::create_function_hook(self.se.clone(), &fd.name); // TODO: very ugly to ad-hoc keep export stuff & session data in sync here Ok(()) } fn execute( &mut self, ast: Ast, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { match ast { Ast::FunDecl(fd) => self.execute_fun_decl(fd), Ast::VarAssign(va) => self.execute_var_assign(va), Ast::Pipes(pipes) => self.execute_pipeline(pipes, stdin, stdout), } } pub fn execute_fun( session: Arc>, invoke: Vec, stdin: Input, stdout: Output, ) -> Result<(), ExecError> { let mut this = Self { se: session, args: None, expand_commands: true, }; let cmd = parse::Command { cmd: invoke[0].clone(), args: invoke[1..].to_vec(), }; this.execute_pipeline(parse::Pipes { cmds: vec![cmd] }, stdin, stdout) } } impl parse::Expander for Executor { type Error = ExecError; fn expand_var( &mut self, var: BString, default: Option, ) -> Result { if var.is_empty() { return Err(ExecError::UnknownVariable(var)); } if var[0].is_ascii_digit() { if let Some(x) = String::from_utf8(var.clone()) .ok() .and_then(|x| x.parse::().ok()) { if let Some(args) = &self.args { if x < args.len() { return Ok(args[x].clone()); } } else if let Some(arg) = std::env::args_os().skip(x).next() { return Ok(arg.into_encoded_bytes()); } } } if let Some(val) = self.se.lock().unwrap().vars.get(&var) { return Ok(val.clone()); } match std::env::var_os(OsStr::from_bytes(&var)) { Some(val) => Ok(val.as_bytes().to_vec()), None => match default { Some(d) => Ok(d), None => Err(ExecError::UnknownVariable(var)), }, } } fn expand_cmd(&mut self, ast: Ast) -> Result { if !self.expand_commands { return Err(ExecError::ExecError(-1)); } let (stdin, _) = io::pipe().unwrap(); let (mut expansion, stdout) = io::pipe().unwrap(); let mut this = self.clone(); let t = std::thread::spawn(move || this.execute(ast, Input::Pipe(stdin), Output::Pipe(stdout))); let mut buf = Vec::new(); expansion.read_to_end(&mut buf).unwrap(); t.join().unwrap()?; if buf.last() == Some(&b'\n') { buf.pop(); } Ok(buf) } } fn exec(se: Arc>, ast: Ast) -> Result<(), ExecError> { let mut exec = Executor { se, args: None, expand_commands: true, }; let ast = ast.expand(&mut exec)?; exec.execute(ast, Input::Stdin, Output::Stdout) } pub fn run(se: Arc>, cmd: Vec) { let parsed = parse::do_parse(&cmd); let parsed = match parsed { Ok(p) => p, Err(err) => { se.lock().unwrap().raw.disable(); println!("{:?}: {}", err.0, String::from_utf8_lossy(&err.1)); print!("{}", se.lock().unwrap().prompt()); std::io::stdout().lock().flush().unwrap(); se.lock().unwrap().raw.enable(); return; } }; se.lock().unwrap().raw.disable(); let result = exec(se.clone(), parsed); se.lock().unwrap().raw.enable(); let status_string = match result { Ok(_) => String::new(), Err(e) => format!("{}\r\n", e.error_message()), }; print!("\r{status_string}{}", se.lock().unwrap().prompt()); let _ = std::io::stdout().lock().flush(); } #[derive(Debug)] pub enum BuiltinError { IO(std::io::Error), Exit(i32), } impl From for BuiltinError { fn from(value: std::io::Error) -> Self { Self::IO(value) } } type BuiltinResult = Result<(), BuiltinError>; #[allow(unused_variables)] pub trait Builtin: Send + Sync { fn name(&self) -> &str; fn special(&self, session: Arc>, args: &[BString]) {} fn io( &self, session: Arc>, args: &[BString], stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> BuiltinResult; } const BUILTINS: &[&'static dyn Builtin] = &[ &builtin::cd, &builtin::clear, #[cfg(debug_assertions)] &builtin::re, &builtin::sink("to", false), &builtin::sink("into", false), &builtin::sink("append", true), &builtin::from, &builtin::builtins, &builtin::_type, &builtin::history, &builtin::escape, &builtin::parse, &builtin::null, ]; pub fn builtin_map() -> HashMap { let mut map = HashMap::new(); for &b in BUILTINS { map.insert(b.name().as_bytes().to_vec(), b); } map } #[derive(Clone)] pub enum CommandKind { Builtin(&'static dyn Builtin), Fun(Ast), Path(PathBuf), } pub fn get_command_kind(se: &Session, cmd: &bstr) -> CommandKind { let path_cmd = CommandKind::Path(PathBuf::from(OsStr::from_bytes(cmd))); if cmd.contains(&b'/') { path_cmd } else if let Some(fun) = se.funs.get(cmd) { CommandKind::Fun(fun.clone()) } else if let Some(builtin) = se.builtins.get(cmd) { CommandKind::Builtin(*builtin) } else { path_cmd } }