diff options
| author | Jonas Maier <> | 2026-03-06 17:38:32 +0100 |
|---|---|---|
| committer | Jonas Maier <> | 2026-03-06 17:38:32 +0100 |
| commit | 44628890b64befde0a1bb147d82d9bfe48b7e1af (patch) | |
| tree | c22b372c9674397e6acbdce2a2721433e9fcc813 /src | |
| parent | 0e73fe1d5edfe01789efa43c1c97a2c448cd25be (diff) | |
| download | pish-44628890b64befde0a1bb147d82d9bfe48b7e1af.tar.gz | |
shell expansion
Diffstat (limited to 'src')
| -rw-r--r-- | src/parse.rs | 15 | ||||
| -rw-r--r-- | src/run/mod.rs | 296 |
2 files changed, 197 insertions, 114 deletions
diff --git a/src/parse.rs b/src/parse.rs index c30206a..2ed0dbf 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -19,10 +19,10 @@ impl Stage for PostExpansion { type Res<T, E> = std::result::Result<T, E>; -trait Expander { +pub trait Expander { type Error; - fn expand_var(&mut self, v: VarName) -> Res<BString, Self::Error>; - fn expand_cmd(&mut self, c: Ast<PreExpansion>) -> Res<BString, Self::Error>; + fn expand_var(&mut self, v: BString) -> Res<BString, Self::Error>; + fn expand_cmd(&mut self, c: Ast<PostExpansion>) -> Res<BString, Self::Error>; } #[derive(Debug, Clone)] @@ -80,7 +80,7 @@ impl StringPart { } } -#[derive(Debug)] +#[derive(Debug, Clone)] /// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]` pub struct ExpString { parts: Vec<StringPart>, @@ -92,8 +92,11 @@ impl ExpString { for part in self.parts.into_iter() { let mut x = match part { StringPart::Boring(items) => items, - StringPart::Var(v) => e.expand_var(v)?, - StringPart::Cmd(ast) => e.expand_cmd(ast)?, + StringPart::Var(v) => e.expand_var(v.name)?, + StringPart::Cmd(ast) => { + let exp = ast.expand(e)?; + e.expand_cmd(exp)? + } }; out.append(&mut x); } diff --git a/src/run/mod.rs b/src/run/mod.rs index 5cc8149..40f4927 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -1,150 +1,230 @@ use std::collections::HashMap; use std::path::PathBuf; +use std::sync::{Arc, Mutex}; -use crate::parse::Ast; +use crate::parse::{Ast, PreExpansion}; use crate::*; mod builtin; -pub fn run(se: &mut Session, cmd: Vec<u8>) { - let parsed = parse::do_parse(&cmd); +enum ExecError { + UnknownVariable, + ExecError(i32), +} - let parsed = match parsed { - Ok(p) => p, - Err(err) => { - println!("{cmd:?}"); - print!("{err:?}\r\n{}", se.prompt()); - return; - } - }; +struct Executor<'a> { + se: &'a mut Session, +} - let Ast::Pipes(pipes) = parsed else { - todo!("can only handle pipes"); - }; +#[derive(Clone)] +struct ArcWriter { + inner: Arc<Mutex<Vec<u8>>>, +} - let mut children = Vec::new(); - let mut threads = Vec::new(); - let mut prev_reader = None; - let mut spawn_error = false; +impl ArcWriter { + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(Vec::new())), + } + } + pub fn into_inner(self) -> Vec<u8> { + self.inner.lock().unwrap().clone() + } +} - se.raw.disable(); +impl std::io::Write for ArcWriter { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.inner.lock().unwrap().extend_from_slice(buf); + Ok(buf.len()) + } - let pipelen = pipes.cmds.len(); - for (i, cmd) in pipes.cmds.into_iter().enumerate() { - let last = i == pipelen - 1; + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} - let (reader, writer) = if !last { - let (r, w) = io::pipe().unwrap(); - (Some(r), Some(w)) - } else { - (None, None) +impl<'a> Executor<'a> { + fn execute( + &mut self, + ast: Ast<parse::PostExpansion>, + stdin: Box<dyn io::Read + Send>, + stdout: Box<dyn io::Write + Send>, + ) -> Result<(), ExecError> { + let Ast::Pipes(pipes) = ast else { + todo!("can only handle pipes"); }; - let dc = se.dispatch.get(&cmd.cmd[..]); + let mut stdin = Some(stdin); + let mut stdout = Some(stdout); - match dc { - CommandKind::Path(path) => { - let mut command = Command::new(&path); - for arg in cmd.args.iter() { - command.arg(OsStr::from_bytes(arg)); - } + let mut children = Vec::new(); + let mut threads = Vec::new(); + let mut prev_reader = None; + let mut spawn_error = false; - if let Some(r) = prev_reader.take() { - command.stdin(Stdio::from(r)); - } + let pipelen = pipes.cmds.len(); + for (i, cmd) in pipes.cmds.into_iter().enumerate() { + let last = i == pipelen - 1; - if let Some(w) = writer { - command.stdout(Stdio::from(w)); - } + let (reader, writer) = if !last { + let (r, w) = io::pipe().unwrap(); + (Some(r), Some(w)) + } else { + (None, None) + }; - let child = match command.spawn() { - Ok(c) => c, - Err(e) => { - let cmd = path.to_string_lossy(); - let msg = match e.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:?}"), - }; - println!("pish: {msg}"); - spawn_error = true; - break; + let dc = self.se.dispatch.get(&cmd.cmd[..]); + + match dc { + CommandKind::Path(path) => { + let mut command = Command::new(&path); + for arg in cmd.args.iter() { + command.arg(OsStr::from_bytes(arg)); } - }; - children.push(child); - } + if let Some(r) = prev_reader.take() { + command.stdin(Stdio::from(r)); + } - CommandKind::Builtin(builtin) => { - builtin.mod_session(se, &cmd.args); + if let Some(w) = writer { + command.stdout(Stdio::from(w)); + } - let mut input: Box<dyn io::Read + Send> = match prev_reader.take() { - Some(r) => Box::new(r), - None => Box::new(io::stdin()), - }; + let child = match command.spawn() { + Ok(c) => c, + Err(e) => { + let cmd = path.to_string_lossy(); + let msg = match e.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:?}"), + }; + println!("pish: {msg}"); + spawn_error = true; + break; + } + }; + + children.push(child); + } - let mut output: Box<dyn io::Write + Send> = match writer { - Some(w) => Box::new(w), - None => Box::new(io::stdout()), - }; + CommandKind::Builtin(builtin) => { + builtin.mod_session(&mut self.se, &cmd.args); - let handle = - std::thread::spawn(move || builtin.io(&cmd.args, &mut input, &mut output)); + let mut input: Box<dyn io::Read + Send> = match prev_reader.take() { + Some(r) => Box::new(r), + None => stdin.take().unwrap(), + }; - threads.push(handle); - } - } + let mut output: Box<dyn io::Write + Send> = match writer { + Some(w) => Box::new(w), + None => stdout.take().unwrap(), + }; - prev_reader = reader; - } + let handle = + std::thread::spawn(move || builtin.io(&cmd.args, &mut input, &mut output)); - let status_string; - - if spawn_error { - for child in children.iter_mut() { - if let Err(e) = child.kill() { - println!("failed to kill child - {e:?}"); + threads.push(handle); + } } + + prev_reader = reader; } - status_string = "".into(); - } else { - let mut code = 0; - for jh in threads { - // TODO do not ignore panics - let _ = jh.join(); - } - for mut child in children { - match child.wait() { - Ok(ec) => { - if let Some(c) = ec.code() { - code = c; - } + + if spawn_error { + for child in children.iter_mut() { + if let Err(e) = child.kill() { + println!("failed to kill child - {e:?}"); } - Err(e) => { - println!("failed to wait for child - {e:?}") + } + Err(ExecError::ExecError(127)) + } else { + let mut code = 0; + for jh in threads { + // TODO do not ignore panics + let _ = jh.join(); + } + for mut child in children { + match child.wait() { + Ok(ec) => { + if let Some(c) = ec.code() { + code = c; + } + } + Err(e) => { + println!("failed to wait for child - {e:?}") + } } } + if code == 0 { + Ok(()) + } else { + Err(ExecError::ExecError(code)) + } } - if code == 0 { - status_string = String::new(); - } else { - status_string = format!("{code}"); + } +} + +impl<'a> parse::Expander for Executor<'a> { + type Error = ExecError; + + fn expand_var(&mut self, var: BString) -> Result<BString, Self::Error> { + match std::env::var_os(OsStr::from_bytes(&var)) { + Some(val) => Ok(val.as_bytes().to_vec()), + None => Err(ExecError::UnknownVariable), } } + fn expand_cmd(&mut self, ast: Ast<parse::PostExpansion>) -> Result<BString, Self::Error> { + let stdout = Box::new(ArcWriter::new()); + let stdin = Box::new(std::io::empty()); + self.execute(ast, stdin, stdout.clone())?; + Ok(stdout.into_inner()) + } +} + +fn exec(se: &mut Session, ast: Ast<PreExpansion>) -> Result<(), ExecError> { + let mut exec = Executor { se }; + let ast = ast.expand(&mut exec)?; + exec.execute(ast, Box::new(io::stdin()), Box::new(io::stdout())) +} + +pub fn run(se: &mut Session, cmd: Vec<u8>) { + let parsed = parse::do_parse(&cmd); + let parsed = match parsed { + Ok(p) => p, + Err(err) => { + println!("{cmd:?}"); + print!("{err:?}\r\n{}", se.prompt()); + return; + } + }; + + se.raw.disable(); + let result = exec(se, parsed); se.raw.enable(); + let status_string = match result { + Ok(_) => String::new(), + Err(ExecError::UnknownVariable) => String::from("unbound variable"), + Err(ExecError::ExecError(i)) => i.to_string(), + }; + print!("\r{status_string}\r\n{}", se.prompt()); let _ = std::io::stdout().lock().flush(); } |
