use crate::rw::*; use std::collections::HashMap; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Instant; use crate::parse::{self, Ast, PostExpansion, PreExpansion}; use crate::wait::{ChildWaiter, ThreadWaiter}; use crate::*; mod builtin; mod var; pub use var::Vars; pub use var::WatchId; #[derive(Debug)] pub enum ExecError { UnknownVariable(BString), ExecError(i32), SpawnIO(String, std::io::Error), IO(std::io::Error), ErrorStack(Vec), Panic, AliasDepthExceeded, Parse(crate::parse::ParseError), Break, Continue, NoCaseMatch, } 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 => String::from("too many arguments"), io::ErrorKind::Interrupted => String::from("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 => String::from("worker thread panicked"), ExecError::ErrorStack(stack) => { let mut out = String::new(); for e in stack.iter() { if !out.is_empty() { out += "\r\n"; } out += &e.error_message(); } out } ExecError::AliasDepthExceeded => "alias depth exceeded".to_string(), ExecError::Parse(pe) => format!("parse error: {pe:?}"), ExecError::Break => "break: only useful in loops".to_string(), ExecError::Continue => "continue: only useful in loops".to_string(), ExecError::NoCaseMatch => "case: no pattern matched".to_string(), } } } impl From for ExecError { fn from(value: BuiltinError) -> Self { match value { BuiltinError::IO(error) => Self::IO(error), BuiltinError::Exit(code) => Self::ExecError(code), BuiltinError::Break => Self::Break, BuiltinError::Continue => Self::Continue, } } } 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 struct SpawnedPipeline { executors: Vec, cancel: Vec, } pub enum SpawnedCmd { Builtin(ThreadWaiter>), Fun(ThreadWaiter>), Child(ChildWaiter), SpawnError(io::Error), Joined(Result<(), ExecError>), Pipeline(SpawnedPipeline), } trait ExecResultAsBool { fn as_bool(&self) -> Result; } impl ExecResultAsBool for Result<(), ExecError> { /// returns the boolean value interpreted from this exit code. /// /// in case the exit code indicates divergence (break/continue) that "error" will be propagated fn as_bool(&self) -> Result { match self { Ok(_) | Err(ExecError::ExecError(0)) => Ok(true), Err(ExecError::Break) => Err(ExecError::Break), Err(ExecError::Continue) => Err(ExecError::Continue), Err(_) => Ok(false), } } } impl SpawnedCmd { pub fn join(self) -> Result<(), ExecError> { match self { SpawnedCmd::Builtin(handle) => { handle.into_inner().join().map_err(|_| ExecError::Panic)?? } SpawnedCmd::Fun(handle) => { handle.into_inner().join().map_err(|_| ExecError::Panic)?? } SpawnedCmd::Child(child) => { let mut child = child.into_inner(); 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)?, SpawnedCmd::Joined(res) => res?, SpawnedCmd::Pipeline(pipes) => { let joined: Vec<_> = pipes .executors .into_iter() .filter_map(|e| e.join().err()) .collect(); match joined.len() { 0 => (), 1 => return Err(joined.into_iter().next().unwrap()), _ => return Err(ExecError::ErrorStack(joined)), } } } Ok(()) } /// returns whether the spawned command is already joined pub fn join_timeout(&mut self, timeout_ms: u16) -> bool { match self { SpawnedCmd::Builtin(tw) => tw.try_join(timeout_ms), SpawnedCmd::Fun(tw) => tw.try_join(timeout_ms), SpawnedCmd::Child(child) => !matches!(child.wait(timeout_ms), Ok(None)), SpawnedCmd::SpawnError(_) => true, SpawnedCmd::Joined(_) => true, SpawnedCmd::Pipeline(pipes) => pipes .executors .iter_mut() .all(|e| e.join_timeout(timeout_ms)), } } pub fn cancel(&mut self) { match self { SpawnedCmd::Pipeline(pipes) => { for c in pipes.cancel.iter_mut() { c.cancel(); } } SpawnedCmd::Builtin(_) | SpawnedCmd::Fun(_) | SpawnedCmd::Child(_) | SpawnedCmd::SpawnError(_) | SpawnedCmd::Joined(_) => (), } } } impl Executor { fn exec_loop(&mut self, mut s: SpawnedCmd, cs: &mut [Canceler]) -> Result<(), ExecError> { let begin = Instant::now(); while !s.join_timeout(50) { if let Ok(se) = self.se.try_lock() && ctrlc::pressed_since(&se, begin) { for c in cs.iter_mut() { c.cancel(); } s.cancel(); break; } } s.join() } pub fn new_for_completion(se: Arc>) -> Self { Self { se, args: None, expand_commands: false, } } pub fn new(se: Arc>) -> Self { Self { se, args: None, expand_commands: true, } } fn spawn_cmd( &mut self, cmd: CommandKind, args: Vec, mut stdin: InputReader, mut stdout: OutputWriter, ) -> SpawnedCmd { match cmd { CommandKind::Builtin(builtin) => { let mut builtin = builtin.clone_box(); builtin.special(self.se.clone(), &args[1..]); let cloned_session = self.se.clone(); let handle = wait::spawn(move || { builtin.io(cloned_session, &args[1..], &mut stdin, &mut stdout) }); SpawnedCmd::Builtin(handle) } CommandKind::Fun(body) => { let mut this = self.clone(); this.args = Some(args); this.execute_block(body, stdin, stdout) } CommandKind::Path(path) => { let mut command = Command::new(&path); for arg in args.iter().skip(1) { command.arg(OsStr::from_bytes(arg)); } command.stdin(Input::from(stdin)); command.stdout(Output::from(stdout)); crate::export_fun::prepare_command(self.se.clone(), &mut command); command.env_clear(); for (var, val) in self.se.lock().unwrap().vars.export() { command.env(OsStr::from_bytes(var), OsStr::from_bytes(val.as_ref())); } match command.spawn() { Ok(c) => SpawnedCmd::Child(ChildWaiter::new(c).unwrap()), Err(e) => SpawnedCmd::SpawnError(e), } } } } fn execute_block( &mut self, block: parse::Block, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { let mut this = self.clone(); let handle = wait::spawn(move || -> Result<(), ExecError> { this.execute_block_inner(&block, stdin, stdout) }); SpawnedCmd::Fun(handle) } fn execute_pipeline( &mut self, pipes: parse::Pipes, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { let mut inputs = Vec::with_capacity(pipes.cmds.len()); let mut outputs = Vec::with_capacity(pipes.cmds.len()); let mut cancel = Vec::with_capacity(pipes.cmds.len() * 2); let mut executors = Vec::with_capacity(pipes.cmds.len()); inputs.push(stdin); for _ in 1..pipes.cmds.len() { let (r, w) = io::pipe().unwrap(); let (i, c1) = InputReader::new(Input::Pipe(r)); let (o, c2) = OutputWriter::new(Output::Pipe(w)); inputs.push(i); outputs.push(o); cancel.push(c1); cancel.push(c2); } 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)); } SpawnedCmd::Pipeline(SpawnedPipeline { executors, cancel }) } fn execute_var_assign(&mut self, va: parse::VarAssign) -> SpawnedCmd { self.se.lock().unwrap().vars.set(va.var, va.val); SpawnedCmd::Joined(Ok(())) } fn execute_fun_decl(&mut self, fd: parse::FunDecl) -> SpawnedCmd { self.se .lock() .unwrap() .funs .insert(fd.name.clone(), fd.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 SpawnedCmd::Joined(Ok(())) } fn execute( &mut self, ast: Ast, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { 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), Ast::If(cond) => self.execute_if(cond, stdin, stdout), Ast::While(w) => self.execute_while(w, stdin, stdout), Ast::Case(c) => self.execute_case(c, 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(), }; let (stdin, c1) = InputReader::new(stdin); let (stdout, c2) = OutputWriter::new(stdout); let cmd = this.execute_pipeline(parse::Pipes { cmds: vec![cmd] }, stdin, stdout); this.exec_loop(cmd, &mut [c1, c2]) } fn execute_block_inner( &mut self, block: &Block, stdin: InputReader, stdout: OutputWriter, ) -> Result<(), ExecError> { for cmd in block.commands.iter() { let cmd = cmd.clone().expand(self)?; self.execute(cmd, stdin.try_clone()?, stdout.try_clone()?) .join()?; } Ok(()) } fn execute_if( &mut self, cond: parse::If, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { let mut this = self.clone(); let handle = wait::spawn(move || -> Result<(), ExecError> { let res = this .execute_pipeline(cond.condition, stdin.try_clone()?, stdout.try_clone()?) .join(); let block = if res.as_bool()? { cond.true_block } else { cond.false_block }; this.execute_block_inner(&block, stdin.try_clone()?, stdout.try_clone()?) }); SpawnedCmd::Fun(handle) } fn execute_while( &mut self, w: parse::While, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { let mut this = self.clone(); let parse::While { condition, block } = w; let handle = wait::spawn(move || -> Result<(), ExecError> { while { let condition = condition.clone().expand(&mut this)?; this.execute_pipeline(condition, stdin.try_clone()?, stdout.try_clone()?) .join() .as_bool()? } { let res = this.execute_block_inner(&block, stdin.try_clone()?, stdout.try_clone()?); match res { Ok(_) => (), Err(ExecError::Break) => break, Err(ExecError::Continue) => continue, Err(e) => Err(e)?, } } Ok(()) }); SpawnedCmd::Fun(handle) } fn execute_case( &mut self, c: parse::Case, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { for branch in c.branches.into_iter() { // TODO: do not compile every time let compiled = branch.pattern.compile(); if compiled.matches(&c.discriminant) { return self.execute_block(branch.block, stdin, stdout); } } SpawnedCmd::Joined(Err(ExecError::NoCaseMatch)) } pub fn execute_script( &mut self, s: parse::Script, stdin: InputReader, stdout: OutputWriter, ) -> SpawnedCmd { self.execute_block( parse::Block { commands: s.stmts, finished_parsing: true, }, 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)); } // TODO: this should probably also go somewhere in the var module. if var[0].is_ascii_digit() && 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().nth(x) { return Ok(arg.into_encoded_bytes()); } } if let Some(val) = self.se.lock().unwrap().vars.lookup(&var) { return Ok(val.into_owned()); } if let Some(dfl) = default { return Ok(dfl); } 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 (stdin, c1) = InputReader::new(Input::Pipe(stdin)); let (mut expansion, stdout) = io::pipe().unwrap(); let (stdout, c2) = OutputWriter::new(Output::Pipe(stdout)); let mut this = self.clone(); let t = std::thread::spawn(move || { let cmd = this.execute(ast, stdin, stdout); this.exec_loop(cmd, &mut [c1, c2]) }); 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) } type AliasAge = AliasAge; fn expand_alias( &mut self, cmd: &bstr, older_than: Option, ) -> Result)>, Self::Error> { Ok(self .se .lock() .unwrap() .aliases .get(cmd, older_than.unwrap_or(AliasAge::MAX)) .map(|(age, body)| (age, body.parsed))) } } type AliasAge = u32; #[derive(Clone)] pub struct AliasBody { pub unparsed: BString, pub parsed: Vec, } type AliasSet = Vec<(AliasAge, AliasBody)>; pub struct Aliases { age: u32, aliases: HashMap, } impl Aliases { pub fn new() -> Self { Self { age: 0, aliases: HashMap::new(), } } fn insert(&mut self, name: BString, body: AliasBody) { let age = self.age; self.age += 1; let new = (age, body); if let Some(entry) = self.aliases.get_mut(&name) { entry.push(new); } else { self.aliases.insert(name, vec![new]); } } fn get(&self, name: &bstr, older_than: AliasAge) -> Option<(AliasAge, AliasBody)> { let alias_set = self.aliases.get(name)?; alias_set.iter().rev().find(|e| e.0 < older_than).cloned() } fn remove(&mut self, name: &bstr) { self.aliases.remove(name); } } impl Default for Aliases { fn default() -> Self { Self::new() } } fn exec(se: Arc>, ast: Ast) -> Result<(), ExecError> { let mut exec = Executor::new(se.clone()); let ast = ast.expand(&mut exec)?; let (stdin, c1) = InputReader::new(Input::Stdin); let (stdout, c2) = OutputWriter::new(Output::Stdout); let cmd = exec.execute(ast, stdin, stdout); exec.exec_loop(cmd, &mut [c1, c2]) } pub fn run_quiet( se: Arc>, cmd: parse::Command, ) -> Result<(), ExecError> { let mut exec = Executor { se: se.clone(), args: None, expand_commands: true, }; let (i, c1) = InputReader::new(Input::Null); let (o, c2) = OutputWriter::new(Output::Null); let cmd = exec.execute_pipeline(parse::Pipes { cmds: vec![cmd] }, i, o); exec.exec_loop(cmd, &mut [c1, c2]) } pub fn run(se: Arc>, parsed: Ast) -> String { se.lock().unwrap().raw_disable(); let result = exec(se.clone(), parsed); se.lock().unwrap().raw_enable(); match result { Ok(_) => String::new(), Err(e) => e.error_message(), } } pub fn run_script(se: Arc>, script: crate::parse::Script) { for stmt in script.stmts { run(se.clone(), stmt); } } pub fn source(se: Arc>, file: &bstr) -> Result<(), ExecError> { let mut f = File::open(OsStr::from_bytes(file)).map_err(ExecError::IO)?; let mut buf = Vec::new(); f.read_to_end(&mut buf).map_err(ExecError::IO)?; let script = parse::Script::parse_from_bytes(&buf).map_err(ExecError::Parse)?; // TODO: error propagation run_script(se, script); Ok(()) } #[derive(Debug)] pub enum BuiltinError { IO(std::io::Error), Exit(i32), Break, Continue, } 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(&mut self, session: Arc>, args: &[BString]) {} fn io( &self, session: Arc>, args: &[BString], stdin: &mut dyn Read, stdout: &mut dyn Write, ) -> BuiltinResult; } pub trait BuiltinClone: Builtin { fn clone_box(&self) -> Box; } impl BuiltinClone for T { fn clone_box(&self) -> Box { Box::new(self.clone()) } } const BUILTINS: &[&'static dyn BuiltinClone] = &[ &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, &builtin::var, &builtin::completion, &builtin::alias, &builtin::unalias, #[cfg(debug_assertions)] &builtin::debug, &builtin::terminfo, &builtin::bind::new(), &builtin::exit, &builtin::ct, &builtin::source, &builtin::Break, &builtin::Continue, &builtin::Here, &builtin::logo, &builtin::export, &builtin::pish_theme, #[cfg(debug_assertions)] &builtin::case_match, ]; 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 BuiltinClone), Fun(Block), 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 } }