aboutsummaryrefslogtreecommitdiffstats
path: root/src/run/mod.rs
diff options
context:
space:
mode:
authorJonas Maier <>2026-03-06 17:38:32 +0100
committerJonas Maier <>2026-03-06 17:38:32 +0100
commit44628890b64befde0a1bb147d82d9bfe48b7e1af (patch)
treec22b372c9674397e6acbdce2a2721433e9fcc813 /src/run/mod.rs
parent0e73fe1d5edfe01789efa43c1c97a2c448cd25be (diff)
downloadpish-44628890b64befde0a1bb147d82d9bfe48b7e1af.tar.gz
shell expansion
Diffstat (limited to 'src/run/mod.rs')
-rw-r--r--src/run/mod.rs296
1 files changed, 188 insertions, 108 deletions
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();
}