From 9dd1d992bd2344d8824e69d8f47c9009a6caf021 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Fri, 7 Jan 2022 23:42:32 -0500 Subject: large refactor --- src/parse.rs | 335 +++++----------------- src/parse/ast.rs | 588 +++++++++++++++++++++++++++++++++++++++ src/pipeline/builtins/command.rs | 16 +- src/pipeline/builtins/mod.rs | 6 +- src/pipeline/command.rs | 26 +- src/pipeline/mod.rs | 6 +- src/shell/history/mod.rs | 7 +- src/shell/mod.rs | 4 +- src/test_parse.rs | 285 ------------------- 9 files changed, 687 insertions(+), 586 deletions(-) create mode 100644 src/parse/ast.rs delete mode 100644 src/test_parse.rs diff --git a/src/parse.rs b/src/parse.rs index 1dfb4f9..f147892 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,10 +1,65 @@ -use pest::Parser as _; +pub mod ast; -#[derive(pest_derive::Parser)] -#[grammar = "shell.pest"] -struct Shell; +#[derive(Debug)] +pub struct Commands { + pipelines: Vec, +} + +impl Commands { + pub fn pipelines(&self) -> &[Pipeline] { + &self.pipelines + } +} -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] +pub struct Pipeline { + exes: Vec, + input_string: String, +} + +impl Pipeline { + pub fn into_exes(self) -> impl Iterator { + self.exes.into_iter() + } + + pub fn input_string(&self) -> &str { + &self.input_string + } +} + +#[derive(Debug)] +pub struct Exe { + exe: std::path::PathBuf, + args: Vec, + redirects: Vec, +} + +impl Exe { + pub fn exe(&self) -> &std::path::Path { + &self.exe + } + + pub fn args(&self) -> &[String] { + &self.args + } + + pub fn redirects(&self) -> &[Redirect] { + &self.redirects + } + + pub fn shift(&mut self) { + self.exe = std::path::PathBuf::from(self.args.remove(0)); + } +} + +#[derive(Debug, Clone)] +pub struct Redirect { + pub from: std::os::unix::io::RawFd, + pub to: RedirectTarget, + pub dir: Direction, +} + +#[derive(Debug, Clone)] pub enum RedirectTarget { Fd(std::os::unix::io::RawFd), File(std::path::PathBuf), @@ -25,12 +80,12 @@ impl Direction { use nix::fcntl::OFlag; use nix::sys::stat::Mode; Ok(match self { - crate::parse::Direction::In => nix::fcntl::open( + Self::In => nix::fcntl::open( path, OFlag::O_NOCTTY | OFlag::O_RDONLY, Mode::empty(), )?, - crate::parse::Direction::Out => nix::fcntl::open( + Self::Out => nix::fcntl::open( path, OFlag::O_CREAT | OFlag::O_NOCTTY @@ -43,7 +98,7 @@ impl Direction { | Mode::S_IROTH | Mode::S_IWOTH, )?, - crate::parse::Direction::Append => nix::fcntl::open( + Self::Append => nix::fcntl::open( path, OFlag::O_APPEND | OFlag::O_CREAT @@ -60,266 +115,6 @@ impl Direction { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Redirect { - pub from: std::os::unix::io::RawFd, - pub to: RedirectTarget, - pub dir: Direction, -} - -impl Redirect { - fn parse(prefix: &str, to: &str) -> Self { - let (from, dir) = if let Some(from) = prefix.strip_suffix(">>") { - (from, Direction::Append) - } else if let Some(from) = prefix.strip_suffix('>') { - (from, Direction::Out) - } else if let Some(from) = prefix.strip_suffix('<') { - (from, Direction::In) - } else { - unreachable!() - }; - let from = if from.is_empty() { - match dir { - Direction::In => 0, - Direction::Out | Direction::Append => 1, - } - } else { - from.parse().unwrap() - }; - let to = to.strip_prefix('&').map_or_else( - || RedirectTarget::File(to.into()), - |fd| RedirectTarget::Fd(fd.parse().unwrap()), - ); - Self { from, to, dir } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Word { - word: String, - interpolate: bool, - quoted: bool, -} - -impl Word { - fn parse(s: &str, interpolate: bool, quoted: bool) -> Self { - let mut word_str = s.to_string(); - if interpolate { - word_str = strip_escape(&word_str); - } else { - word_str = strip_basic_escape(&word_str); - } - Self { - word: word_str, - interpolate, - quoted, - } - } -} - -enum WordOrRedirect { - Word(Word), - Redirect(Redirect), -} - -impl WordOrRedirect { - fn build_ast(pair: pest::iterators::Pair) -> Self { - assert!(matches!(pair.as_rule(), Rule::word)); - let mut inner = pair.into_inner(); - let mut word = inner.next().unwrap(); - let mut prefix = None; - if matches!(word.as_rule(), Rule::redir_prefix) { - prefix = Some(word.as_str().trim().to_string()); - word = inner.next().unwrap(); - } - assert!(matches!( - word.as_rule(), - Rule::bareword | Rule::single_string | Rule::double_string - )); - let word = Word::parse( - word.as_str(), - matches!(word.as_rule(), Rule::bareword | Rule::double_string), - matches!( - word.as_rule(), - Rule::single_string | Rule::double_string - ), - ); - if let Some(prefix) = prefix { - Self::Redirect(Redirect::parse(&prefix, &word.word)) - } else { - Self::Word(word) - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Exe { - exe: Word, - args: Vec, - redirects: Vec, -} - -impl Exe { - fn build_ast(pair: pest::iterators::Pair) -> Self { - assert!(matches!(pair.as_rule(), Rule::exe)); - let mut iter = pair.into_inner(); - let exe = match WordOrRedirect::build_ast(iter.next().unwrap()) { - WordOrRedirect::Word(word) => word, - WordOrRedirect::Redirect(_) => todo!(), - }; - let (args, redirects): (_, Vec<_>) = iter - .map(WordOrRedirect::build_ast) - .partition(|word| matches!(word, WordOrRedirect::Word(_))); - let args = args - .into_iter() - .map(|word| match word { - WordOrRedirect::Word(word) => word, - WordOrRedirect::Redirect(_) => unreachable!(), - }) - .collect(); - let redirects = redirects - .into_iter() - .map(|word| match word { - WordOrRedirect::Word(_) => unreachable!(), - WordOrRedirect::Redirect(redirect) => redirect, - }) - .collect(); - Self { - exe, - args, - redirects, - } - } - - pub fn exe(&self) -> &str { - &self.exe.word - } - - pub fn args(&self) -> impl Iterator { - self.args.iter().map(|arg| arg.word.as_ref()) - } - - pub fn redirects(&self) -> &[Redirect] { - &self.redirects - } - - pub fn shift(&mut self) { - self.exe = self.args.remove(0); - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Pipeline { - exes: Vec, - input_string: String, -} - -impl Pipeline { - pub fn parse(pipeline: &str) -> Result { - Ok(Self::build_ast( - Shell::parse(Rule::pipeline, pipeline) - .map_err(|e| Error::new(pipeline, anyhow::anyhow!(e)))? - .next() - .unwrap(), - )) - } - - pub fn into_exes(self) -> impl Iterator { - self.exes.into_iter() - } - - pub fn input_string(&self) -> &str { - &self.input_string - } - - fn build_ast(pipeline: pest::iterators::Pair) -> Self { - assert!(matches!(pipeline.as_rule(), Rule::pipeline)); - let input_string = pipeline.as_str().to_string(); - Self { - exes: pipeline.into_inner().map(Exe::build_ast).collect(), - input_string, - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct Commands { - pipelines: Vec, - input_string: String, -} - -impl Commands { - pub fn parse(full_cmd: &str) -> Result { - Ok(Self::build_ast( - Shell::parse(Rule::line, full_cmd) - .map_err(|e| Error::new(full_cmd, anyhow::anyhow!(e)))? - .next() - .unwrap() - .into_inner() - .next() - .unwrap(), - )) - } - - pub fn pipelines(&self) -> &[Pipeline] { - &self.pipelines - } - - pub fn input_string(&self) -> &str { - &self.input_string - } - - fn build_ast(commands: pest::iterators::Pair) -> Self { - assert!(matches!(commands.as_rule(), Rule::commands)); - let input_string = commands.as_str().to_string(); - Self { - pipelines: commands - .into_inner() - .map(Pipeline::build_ast) - .collect(), - input_string, - } - } -} - -fn strip_escape(s: &str) -> String { - let mut new = String::new(); - let mut escape = false; - for c in s.chars() { - if escape { - new.push(c); - escape = false; - } else { - match c { - '\\' => escape = true, - _ => new.push(c), - } - } - } - new -} - -fn strip_basic_escape(s: &str) -> String { - let mut new = String::new(); - let mut escape = false; - for c in s.chars() { - if escape { - match c { - '\\' | '\'' => {} - _ => new.push('\\'), - } - new.push(c); - escape = false; - } else { - match c { - '\\' => escape = true, - _ => new.push(c), - } - } - } - new -} - #[derive(Debug)] pub struct Error { input: String, @@ -350,7 +145,3 @@ impl std::error::Error for Error { Some(&*self.e) } } - -#[cfg(test)] -#[path = "test_parse.rs"] -mod test; diff --git a/src/parse/ast.rs b/src/parse/ast.rs new file mode 100644 index 0000000..28593bb --- /dev/null +++ b/src/parse/ast.rs @@ -0,0 +1,588 @@ +use crate::prelude::*; + +use pest::Parser as _; + +#[derive(pest_derive::Parser)] +#[grammar = "shell.pest"] +struct Shell; + +#[derive(Debug, PartialEq, Eq)] +pub struct Commands { + pipelines: Vec, + input_string: String, +} + +impl Commands { + pub fn parse(full_cmd: &str) -> Result { + Ok(Self::build_ast( + Shell::parse(Rule::line, full_cmd) + .map_err(|e| super::Error::new(full_cmd, anyhow::anyhow!(e)))? + .next() + .unwrap() + .into_inner() + .next() + .unwrap(), + )) + } + + pub fn eval(self, env: &Env) -> super::Commands { + super::Commands { + pipelines: self + .pipelines + .into_iter() + .map(|pipeline| pipeline.eval(env)) + .collect(), + } + } + + pub fn input_string(&self) -> &str { + &self.input_string + } + + fn build_ast(commands: pest::iterators::Pair) -> Self { + assert!(matches!(commands.as_rule(), Rule::commands)); + let input_string = commands.as_str().to_string(); + Self { + pipelines: commands + .into_inner() + .map(Pipeline::build_ast) + .collect(), + input_string, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Pipeline { + exes: Vec, + input_string: String, +} + +impl Pipeline { + pub fn parse(pipeline: &str) -> Result { + Ok(Self::build_ast( + Shell::parse(Rule::pipeline, pipeline) + .map_err(|e| super::Error::new(pipeline, anyhow::anyhow!(e)))? + .next() + .unwrap(), + )) + } + + pub fn eval(self, env: &Env) -> super::Pipeline { + super::Pipeline { + exes: self.exes.into_iter().map(|exe| exe.eval(env)).collect(), + input_string: self.input_string, + } + } + + fn build_ast(pipeline: pest::iterators::Pair) -> Self { + assert!(matches!(pipeline.as_rule(), Rule::pipeline)); + let input_string = pipeline.as_str().to_string(); + Self { + exes: pipeline.into_inner().map(Exe::build_ast).collect(), + input_string, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +struct Exe { + exe: Word, + args: Vec, + redirects: Vec, +} + +impl Exe { + fn eval(self, env: &Env) -> super::Exe { + super::Exe { + exe: std::path::PathBuf::from(self.exe.eval(env)), + args: self.args.into_iter().map(|arg| arg.eval(env)).collect(), + redirects: self + .redirects + .into_iter() + .map(|redirect| redirect.eval(env)) + .collect(), + } + } + + fn build_ast(pair: pest::iterators::Pair) -> Self { + assert!(matches!(pair.as_rule(), Rule::exe)); + let mut iter = pair.into_inner(); + let exe = match WordOrRedirect::build_ast(iter.next().unwrap()) { + WordOrRedirect::Word(word) => word, + WordOrRedirect::Redirect(_) => todo!(), + }; + let (args, redirects): (_, Vec<_>) = iter + .map(WordOrRedirect::build_ast) + .partition(|word| matches!(word, WordOrRedirect::Word(_))); + let args = args + .into_iter() + .map(|word| match word { + WordOrRedirect::Word(word) => word, + WordOrRedirect::Redirect(_) => unreachable!(), + }) + .collect(); + let redirects = redirects + .into_iter() + .map(|word| match word { + WordOrRedirect::Word(_) => unreachable!(), + WordOrRedirect::Redirect(redirect) => redirect, + }) + .collect(); + Self { + exe, + args, + redirects, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Word { + word: String, + interpolate: bool, + quoted: bool, +} + +impl Word { + fn parse(s: &str, rule: Rule) -> Self { + let interpolate = + matches!(rule, Rule::bareword | Rule::double_string); + let quoted = + matches!(rule, Rule::single_string | Rule::double_string); + let mut word_str = s.to_string(); + if interpolate { + word_str = strip_escape(&word_str); + } else { + word_str = strip_basic_escape(&word_str); + } + Self { + word: word_str, + interpolate, + quoted, + } + } + + fn eval(self, _env: &Env) -> String { + // TODO + self.word + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Redirect { + from: std::os::unix::io::RawFd, + to: RedirectTarget, + dir: super::Direction, +} + +impl Redirect { + fn parse(prefix: &str, to: Word) -> Self { + let (from, dir) = if let Some(from) = prefix.strip_suffix(">>") { + (from, super::Direction::Append) + } else if let Some(from) = prefix.strip_suffix('>') { + (from, super::Direction::Out) + } else if let Some(from) = prefix.strip_suffix('<') { + (from, super::Direction::In) + } else { + unreachable!() + }; + let from = if from.is_empty() { + match dir { + super::Direction::In => 0, + super::Direction::Out | super::Direction::Append => 1, + } + } else { + from.parse().unwrap() + }; + #[allow(clippy::option_if_let_else)] + let to = if let Some(fd) = to.word.strip_prefix('&') { + RedirectTarget::Fd(fd.parse().unwrap()) + } else { + RedirectTarget::File(to) + }; + Self { from, to, dir } + } + + fn eval(self, env: &Env) -> super::Redirect { + super::Redirect { + from: self.from, + to: self.to.eval(env), + dir: self.dir, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum RedirectTarget { + Fd(std::os::unix::io::RawFd), + File(Word), +} + +impl RedirectTarget { + fn eval(self, env: &Env) -> super::RedirectTarget { + match self { + Self::Fd(fd) => super::RedirectTarget::Fd(fd), + Self::File(path) => super::RedirectTarget::File( + std::path::PathBuf::from(path.eval(env)), + ), + } + } +} + +enum WordOrRedirect { + Word(Word), + Redirect(Redirect), +} + +impl WordOrRedirect { + fn build_ast(pair: pest::iterators::Pair) -> Self { + assert!(matches!(pair.as_rule(), Rule::word)); + let mut inner = pair.into_inner(); + let mut word = inner.next().unwrap(); + let mut prefix = None; + if matches!(word.as_rule(), Rule::redir_prefix) { + prefix = Some(word.as_str().trim().to_string()); + word = inner.next().unwrap(); + } + assert!(matches!( + word.as_rule(), + Rule::bareword | Rule::single_string | Rule::double_string + )); + let word = Word::parse(word.as_str(), word.as_rule()); + if let Some(prefix) = prefix { + Self::Redirect(Redirect::parse(&prefix, word)) + } else { + Self::Word(word) + } + } +} + +fn strip_escape(s: &str) -> String { + let mut new = String::new(); + let mut escape = false; + for c in s.chars() { + if escape { + new.push(c); + escape = false; + } else { + match c { + '\\' => escape = true, + _ => new.push(c), + } + } + } + new +} + +fn strip_basic_escape(s: &str) -> String { + let mut new = String::new(); + let mut escape = false; + for c in s.chars() { + if escape { + match c { + '\\' | '\'' => {} + _ => new.push('\\'), + } + new.push(c); + escape = false; + } else { + match c { + '\\' => escape = true, + _ => new.push(c), + } + } + } + new +} + +#[cfg(test)] +impl From for RedirectTarget { + fn from(fd: std::os::unix::io::RawFd) -> Self { + Self::Fd(fd) + } +} + +#[allow(clippy::fallible_impl_from)] +#[cfg(test)] +impl From for RedirectTarget { + fn from(word: Word) -> Self { + Self::File(word) + } +} + +#[cfg(test)] +macro_rules! c { + ($input_string:expr, $($pipelines:expr),*) => { + Commands { + pipelines: vec![$($pipelines),*], + input_string: $input_string.to_string(), + } + }; + } + +#[cfg(test)] +macro_rules! p { + ($input_string:expr, $($exes:expr),*) => { + Pipeline { + exes: vec![$($exes),*], + input_string: $input_string.to_string(), + } + }; + } + +#[cfg(test)] +macro_rules! e { + ($word:expr) => { + Exe { + exe: $word, + args: vec![], + redirects: vec![], + } + }; + ($word:expr, $($args:expr),*) => { + Exe { + exe: $word, + args: vec![$($args),*], + redirects: vec![], + } + }; + ($word:expr ; $($redirects:expr),*) => { + Exe { + exe: $word, + args: vec![], + redirects: vec![$($redirects),*], + } + }; + ($word:expr, $($args:expr),* ; $($redirects:expr),*) => { + Exe { + exe: $word, + args: vec![$($args),*], + redirects: vec![$($redirects),*], + } + }; + } + +#[cfg(test)] +macro_rules! r { + ($from:literal, $to:expr, $dir:ident) => { + Redirect { + from: $from, + to: $to.into(), + dir: super::Direction::$dir, + } + }; +} + +#[cfg(test)] +macro_rules! w { + ($word:expr) => { + Word { + word: $word.to_string(), + interpolate: true, + quoted: false, + } + }; + ($word:expr, $interpolate:expr) => { + Word { + word: $word.to_string(), + interpolate: $interpolate, + quoted: false, + } + }; + ($word:expr, $interpolate:expr, $quoted:expr) => { + Word { + word: $word.to_string(), + interpolate: $interpolate, + quoted: $quoted, + } + }; +} + +#[cfg(test)] +macro_rules! parse_eq { + ($line:literal, $parsed:expr) => { + assert_eq!(&Commands::parse($line).unwrap(), &$parsed) + }; +} + +#[test] +fn test_basic() { + parse_eq!("foo", c!("foo", p!("foo", e!(w!("foo"))))); + parse_eq!( + "foo bar", + c!("foo bar", p!("foo bar", e!(w!("foo"), w!("bar")))) + ); + parse_eq!( + "foo bar baz", + c!( + "foo bar baz", + p!("foo bar baz", e!(w!("foo"), w!("bar"), w!("baz"))) + ) + ); + parse_eq!( + "foo | bar", + c!("foo | bar", p!("foo | bar", e!(w!("foo")), e!(w!("bar")))) + ); + parse_eq!( + "command ls; perl -E 'say foo' | tr a-z A-Z; builtin echo bar", + c!( + "command ls; perl -E 'say foo' | tr a-z A-Z; builtin echo bar", + p!("command ls", e!(w!("command"), w!("ls"))), + p!( + "perl -E 'say foo' | tr a-z A-Z", + e!(w!("perl"), w!("-E"), w!("say foo", false, true)), + e!(w!("tr"), w!("a-z"), w!("A-Z")) + ), + p!("builtin echo bar", e!(w!("builtin"), w!("echo"), w!("bar"))) + ) + ); +} + +#[test] +fn test_whitespace() { + parse_eq!(" foo ", c!("foo", p!("foo", e!(w!("foo"))))); + parse_eq!( + " foo # this is a comment", + c!("foo", p!("foo", e!(w!("foo")))) + ); + parse_eq!("foo#comment", c!("foo", p!("foo", e!(w!("foo"))))); + parse_eq!( + "foo;bar|baz;quux#comment", + c!( + "foo;bar|baz;quux", + p!("foo", e!(w!("foo"))), + p!("bar|baz", e!(w!("bar")), e!(w!("baz"))), + p!("quux", e!(w!("quux"))) + ) + ); + parse_eq!( + "foo | bar ", + c!( + "foo | bar", + p!("foo | bar", e!(w!("foo")), e!(w!("bar"))) + ) + ); + parse_eq!( + " abc def ghi |jkl mno| pqr stu; vwxyz # comment", + c!( + "abc def ghi |jkl mno| pqr stu; vwxyz", + p!( + "abc def ghi |jkl mno| pqr stu", + e!(w!("abc"), w!("def"), w!("ghi")), + e!(w!("jkl"), w!("mno")), + e!(w!("pqr"), w!("stu")) + ), + p!("vwxyz", e!(w!("vwxyz"))) + ) + ); + parse_eq!( + "foo 'bar # baz' \"quux # not a comment\" # comment", + c!( + "foo 'bar # baz' \"quux # not a comment\"", + p!( + "foo 'bar # baz' \"quux # not a comment\"", + e!( + w!("foo"), + w!("bar # baz", false, true), + w!("quux # not a comment", true, true) + ) + ) + ) + ); +} + +#[test] +fn test_redirect() { + parse_eq!( + "foo > bar", + c!( + "foo > bar", + p!("foo > bar", e!(w!("foo") ; r!(1, w!("bar"), Out))) + ) + ); + parse_eq!( + "foo /dev/null 2>&1", + c!( + "foo > /dev/null 2>&1", + p!( + "foo > /dev/null 2>&1", + e!(w!("foo") ; r!(1, w!("/dev/null"), Out), r!(2, 1, Out)) + ) + ) + ); + parse_eq!( + "foo >>bar", + c!( + "foo >>bar", + p!("foo >>bar", e!(w!("foo") ; r!(1, w!("bar"), Append))) + ) + ); + parse_eq!( + "foo >> bar", + c!( + "foo >> bar", + p!("foo >> bar", e!(w!("foo") ; r!(1, w!("bar"), Append))) + ) + ); + parse_eq!( + "foo > 'bar baz'", + c!( + "foo > 'bar baz'", + p!( + "foo > 'bar baz'", + e!(w!("foo") ; r!(1, w!("bar baz", false, true), Out)) + ) + ) + ); +} + +#[test] +fn test_escape() { + parse_eq!( + "foo\\ bar", + c!("foo\\ bar", p!("foo\\ bar", e!(w!("foo bar")))) + ); + parse_eq!( + "'foo\\ bar'", + c!( + "'foo\\ bar'", + p!("'foo\\ bar'", e!(w!("foo\\ bar", false, true))) + ) + ); + parse_eq!( + "\"foo\\ bar\"", + c!( + "\"foo\\ bar\"", + p!("\"foo\\ bar\"", e!(w!("foo bar", true, true))) + ) + ); + parse_eq!( + "\"foo\\\"bar\"", + c!( + "\"foo\\\"bar\"", + p!("\"foo\\\"bar\"", e!(w!("foo\"bar", true, true))) + ) + ); + parse_eq!( + "'foo\\'bar\\\\'", + c!( + "'foo\\'bar\\\\'", + p!("'foo\\'bar\\\\'", e!(w!("foo'bar\\", false, true))) + ) + ); + parse_eq!( + "foo > bar\\ baz", + c!( + "foo > bar\\ baz", + p!("foo > bar\\ baz", e!(w!("foo") ; r!(1, w!("bar baz"), Out))) + ) + ); +} diff --git a/src/pipeline/builtins/command.rs b/src/pipeline/builtins/command.rs index c0fa86d..03fe8a2 100644 --- a/src/pipeline/builtins/command.rs +++ b/src/pipeline/builtins/command.rs @@ -8,12 +8,16 @@ pub struct Command { impl Command { pub fn new(exe: crate::parse::Exe) -> Result { - if let Some(f) = super::BUILTINS.get(exe.exe()) { - Ok(Self { - exe, - f, - io: Io::new(), - }) + if let Some(s) = exe.exe().to_str() { + if let Some(f) = super::BUILTINS.get(s) { + Ok(Self { + exe, + f, + io: Io::new(), + }) + } else { + Err(exe) + } } else { Err(exe) } diff --git a/src/pipeline/builtins/mod.rs b/src/pipeline/builtins/mod.rs index 003892d..e608ae7 100644 --- a/src/pipeline/builtins/mod.rs +++ b/src/pipeline/builtins/mod.rs @@ -62,7 +62,7 @@ fn cd( let dir = exe .args() - .into_iter() + .iter() .map(std::convert::AsRef::as_ref) .next() .unwrap_or(""); @@ -137,8 +137,8 @@ fn echo( } }; } - let count = exe.args().count(); - for (i, arg) in exe.args().enumerate() { + let count = exe.args().len(); + for (i, arg) in exe.args().iter().enumerate() { write_stdout!(arg.as_bytes()); if i == count - 1 { write_stdout!(b"\n"); diff --git a/src/pipeline/command.rs b/src/pipeline/command.rs index 746a340..f9dda06 100644 --- a/src/pipeline/command.rs +++ b/src/pipeline/command.rs @@ -2,27 +2,22 @@ use crate::pipeline::prelude::*; pub struct Command { inner: Inner, - exe: String, + exe: std::path::PathBuf, redirects: Vec, pre_exec: Option< Box std::io::Result<()> + Send + Sync + 'static>, >, } -pub enum Inner { - Binary(async_std::process::Command), - Builtin(super::builtins::Command), -} - impl Command { pub fn new(exe: crate::parse::Exe) -> Self { - let exe_str = exe.exe().to_string(); + let exe_path = exe.exe().to_path_buf(); let redirects = exe.redirects().to_vec(); Self { inner: super::builtins::Command::new(exe).map_or_else( |exe| Self::new_binary(exe).inner, Inner::Builtin, ), - exe: exe_str, + exe: exe_path, redirects, pre_exec: None, } @@ -30,25 +25,25 @@ impl Command { #[allow(clippy::needless_pass_by_value)] pub fn new_binary(exe: crate::parse::Exe) -> Self { - let exe_str = exe.exe().to_string(); + let exe_path = exe.exe().to_path_buf(); let redirects = exe.redirects().to_vec(); let mut cmd = async_std::process::Command::new(exe.exe()); cmd.args(exe.args()); Self { inner: Inner::Binary(cmd), - exe: exe_str, + exe: exe_path, redirects, pre_exec: None, } } pub fn new_builtin(exe: crate::parse::Exe) -> Self { - let exe_str = exe.exe().to_string(); + let exe_path = exe.exe().to_path_buf(); let redirects = exe.redirects().to_vec(); Self { inner: super::builtins::Command::new(exe) .map_or_else(|_| todo!(), Inner::Builtin), - exe: exe_str, + exe: exe_path, redirects, pre_exec: None, } @@ -132,7 +127,7 @@ impl Command { anyhow::anyhow!( "{}: {}", crate::format::io_error(&e), - exe + exe.display() ) })?)) } @@ -147,6 +142,11 @@ impl Command { } } +pub enum Inner { + Binary(async_std::process::Command), + Builtin(super::builtins::Command), +} + pub enum Child<'a> { Binary(async_std::process::Child), Builtin(super::builtins::Child<'a>), diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 015eeef..a54951c 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -37,7 +37,8 @@ async fn run_with_env( env: &mut Env, shell_write: &async_std::fs::File, ) -> anyhow::Result<()> { - let pipeline = crate::parse::Pipeline::parse(env.pipeline().unwrap())?; + let pipeline = + crate::parse::ast::Pipeline::parse(env.pipeline().unwrap())?; let (children, pg) = spawn_children(pipeline, env)?; let status = wait_children(children, pg, env, shell_write).await; env.set_status(status); @@ -61,9 +62,10 @@ async fn write_event( } fn spawn_children( - pipeline: crate::parse::Pipeline, + pipeline: crate::parse::ast::Pipeline, env: &Env, ) -> anyhow::Result<(Vec, Option)> { + let pipeline = pipeline.eval(env); let mut cmds: Vec<_> = pipeline.into_exes().map(Command::new).collect(); for i in 0..(cmds.len() - 1) { let (r, w) = pipe()?; diff --git a/src/shell/history/mod.rs b/src/shell/history/mod.rs index 7cd37fc..8ff9ccc 100644 --- a/src/shell/history/mod.rs +++ b/src/shell/history/mod.rs @@ -88,7 +88,7 @@ impl History { pub async fn run( &mut self, - ast: crate::parse::Commands, + ast: crate::parse::ast::Commands, env: &Env, event_w: async_std::channel::Sender, ) -> anyhow::Result { @@ -269,7 +269,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries { } fn run_commands( - ast: crate::parse::Commands, + ast: crate::parse::ast::Commands, entry: async_std::sync::Arc>, mut env: Env, input_r: async_std::channel::Receiver>, @@ -299,7 +299,8 @@ fn run_commands( } }; - for pipeline in ast.pipelines() { + let commands = ast.eval(&env); + for pipeline in commands.pipelines() { env.set_pipeline(pipeline.input_string().to_string()); match run_pipeline(&pty, &mut env, event_w.clone()).await { Ok((pipeline_status, done)) => { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 2b8c849..2e28ddb 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -582,8 +582,8 @@ impl Shell { fn parse( &self, cmd: &str, - ) -> Result { - let ast = crate::parse::Commands::parse(cmd)?; + ) -> Result { + let ast = crate::parse::ast::Commands::parse(cmd)?; // todo: interpolate Ok(ast) } diff --git a/src/test_parse.rs b/src/test_parse.rs deleted file mode 100644 index a43d359..0000000 --- a/src/test_parse.rs +++ /dev/null @@ -1,285 +0,0 @@ -#[allow(clippy::wildcard_imports)] -use crate::parse::*; - -impl From for RedirectTarget { - fn from(fd: std::os::unix::io::RawFd) -> Self { - Self::Fd(fd) - } -} - -impl From for RedirectTarget { - fn from(path: std::path::PathBuf) -> Self { - Self::File(path) - } -} - -#[allow(clippy::fallible_impl_from)] -impl From<&str> for RedirectTarget { - fn from(path: &str) -> Self { - Self::File(path.try_into().unwrap()) - } -} - -macro_rules! c { - ($input_string:expr, $($pipelines:expr),*) => { - Commands { - pipelines: vec![$($pipelines),*], - input_string: $input_string.to_string(), - } - }; - } - -macro_rules! p { - ($input_string:expr, $($exes:expr),*) => { - Pipeline { - exes: vec![$($exes),*], - input_string: $input_string.to_string(), - } - }; - } - -macro_rules! e { - ($word:expr) => { - Exe { - exe: $word, - args: vec![], - redirects: vec![], - } - }; - ($word:expr, $($args:expr),*) => { - Exe { - exe: $word, - args: vec![$($args),*], - redirects: vec![], - } - }; - ($word:expr ; $($redirects:expr),*) => { - Exe { - exe: $word, - args: vec![], - redirects: vec![$($redirects),*], - } - }; - ($word:expr, $($args:expr),* ; $($redirects:expr),*) => { - Exe { - exe: $word, - args: vec![$($args),*], - redirects: vec![$($redirects),*], - } - }; - } - -macro_rules! r { - ($from:literal, $to:literal, $dir:ident) => { - Redirect { - from: $from, - to: $to.into(), - dir: Direction::$dir, - } - }; -} - -macro_rules! w { - ($word:expr) => { - Word { - word: $word.to_string(), - interpolate: true, - quoted: false, - } - }; - ($word:expr, $interpolate:expr) => { - Word { - word: $word.to_string(), - interpolate: $interpolate, - quoted: false, - } - }; - ($word:expr, $interpolate:expr, $quoted:expr) => { - Word { - word: $word.to_string(), - interpolate: $interpolate, - quoted: $quoted, - } - }; -} - -macro_rules! parse_eq { - ($line:literal, $parsed:expr) => { - assert_eq!(&Commands::parse($line).unwrap(), &$parsed) - }; -} - -#[test] -fn test_basic() { - parse_eq!("foo", c!("foo", p!("foo", e!(w!("foo"))))); - parse_eq!( - "foo bar", - c!("foo bar", p!("foo bar", e!(w!("foo"), w!("bar")))) - ); - parse_eq!( - "foo bar baz", - c!( - "foo bar baz", - p!("foo bar baz", e!(w!("foo"), w!("bar"), w!("baz"))) - ) - ); - parse_eq!( - "foo | bar", - c!("foo | bar", p!("foo | bar", e!(w!("foo")), e!(w!("bar")))) - ); - parse_eq!( - "command ls; perl -E 'say foo' | tr a-z A-Z; builtin echo bar", - c!( - "command ls; perl -E 'say foo' | tr a-z A-Z; builtin echo bar", - p!("command ls", e!(w!("command"), w!("ls"))), - p!( - "perl -E 'say foo' | tr a-z A-Z", - e!(w!("perl"), w!("-E"), w!("say foo", false, true)), - e!(w!("tr"), w!("a-z"), w!("A-Z")) - ), - p!("builtin echo bar", e!(w!("builtin"), w!("echo"), w!("bar"))) - ) - ); -} - -#[test] -fn test_whitespace() { - parse_eq!(" foo ", c!("foo", p!("foo", e!(w!("foo"))))); - parse_eq!( - " foo # this is a comment", - c!("foo", p!("foo", e!(w!("foo")))) - ); - parse_eq!("foo#comment", c!("foo", p!("foo", e!(w!("foo"))))); - parse_eq!( - "foo;bar|baz;quux#comment", - c!( - "foo;bar|baz;quux", - p!("foo", e!(w!("foo"))), - p!("bar|baz", e!(w!("bar")), e!(w!("baz"))), - p!("quux", e!(w!("quux"))) - ) - ); - parse_eq!( - "foo | bar ", - c!( - "foo | bar", - p!("foo | bar", e!(w!("foo")), e!(w!("bar"))) - ) - ); - parse_eq!( - " abc def ghi |jkl mno| pqr stu; vwxyz # comment", - c!( - "abc def ghi |jkl mno| pqr stu; vwxyz", - p!( - "abc def ghi |jkl mno| pqr stu", - e!(w!("abc"), w!("def"), w!("ghi")), - e!(w!("jkl"), w!("mno")), - e!(w!("pqr"), w!("stu")) - ), - p!("vwxyz", e!(w!("vwxyz"))) - ) - ); - parse_eq!( - "foo 'bar # baz' \"quux # not a comment\" # comment", - c!( - "foo 'bar # baz' \"quux # not a comment\"", - p!( - "foo 'bar # baz' \"quux # not a comment\"", - e!( - w!("foo"), - w!("bar # baz", false, true), - w!("quux # not a comment", true, true) - ) - ) - ) - ); -} - -#[test] -fn test_redirect() { - parse_eq!( - "foo > bar", - c!( - "foo > bar", - p!("foo > bar", e!(w!("foo") ; r!(1, "bar", Out))) - ) - ); - parse_eq!( - "foo /dev/null 2>&1", - c!( - "foo > /dev/null 2>&1", - p!( - "foo > /dev/null 2>&1", - e!(w!("foo") ; r!(1, "/dev/null", Out), r!(2, 1, Out)) - ) - ) - ); - parse_eq!( - "foo >>bar", - c!( - "foo >>bar", - p!("foo >>bar", e!(w!("foo") ; r!(1, "bar", Append))) - ) - ); - parse_eq!( - "foo >> bar", - c!( - "foo >> bar", - p!("foo >> bar", e!(w!("foo") ; r!(1, "bar", Append))) - ) - ); - parse_eq!( - "foo > 'bar baz'", - c!( - "foo > 'bar baz'", - p!("foo > 'bar baz'", e!(w!("foo") ; r!(1, "bar baz", Out))) - ) - ); -} - -#[test] -fn test_escape() { - parse_eq!( - "foo\\ bar", - c!("foo\\ bar", p!("foo\\ bar", e!(w!("foo bar")))) - ); - parse_eq!( - "'foo\\ bar'", - c!( - "'foo\\ bar'", - p!("'foo\\ bar'", e!(w!("foo\\ bar", false, true))) - ) - ); - parse_eq!( - "\"foo\\ bar\"", - c!( - "\"foo\\ bar\"", - p!("\"foo\\ bar\"", e!(w!("foo bar", true, true))) - ) - ); - parse_eq!( - "\"foo\\\"bar\"", - c!( - "\"foo\\\"bar\"", - p!("\"foo\\\"bar\"", e!(w!("foo\"bar", true, true))) - ) - ); - parse_eq!( - "'foo\\'bar\\\\'", - c!( - "'foo\\'bar\\\\'", - p!("'foo\\'bar\\\\'", e!(w!("foo'bar\\", false, true))) - ) - ); - parse_eq!( - "foo > bar\\ baz", - c!( - "foo > bar\\ baz", - p!("foo > bar\\ baz", e!(w!("foo") ; r!(1, "bar baz", Out))) - ) - ); -} -- cgit v1.2.3-54-g00ecf