diff --git a/.gitignore b/.gitignore index 98b3eed..d986b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,4 @@ template/flake.lock ideas.md roadmap.md README.md -file.* +file* diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index 58a764d..89125f7 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -9,7 +9,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?; let stdout = borrow_fd(STDOUT_FILENO); - flog!(DEBUG, argv); let mut echo_output = argv.into_iter() .map(|a| a.0) // Extract the String from the tuple of (String,Span) @@ -17,7 +16,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( .join(" "); echo_output.push('\n'); - flog!(DEBUG, echo_output); write(stdout, echo_output.as_bytes())?; diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index c5f8578..9cecd69 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -29,7 +29,7 @@ pub const BUILTINS: [&str;9] = [ /// # Parameters /// * argv - The vector of raw argument tokens /// * job - A mutable reference to a `JobBldr` -/// * io_info - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs` +/// * io_mode - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs` /// /// # Behavior /// * Cleans, expands, and word splits the arg vector @@ -47,7 +47,7 @@ pub const BUILTINS: [&str;9] = [ pub fn setup_builtin<'t>( argv: Vec>, job: &'t mut JobBldr, - io_info: Option<(&mut IoStack,Vec)>, + io_mode: Option<(&mut IoStack,Vec)>, ) -> ShResult<(Vec<(String,Span<'t>)>, Option)> { let mut argv: Vec<(String,Span)> = prepare_argv(argv); @@ -61,7 +61,7 @@ pub fn setup_builtin<'t>( let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?; job.push_child(child); - let io_frame = if let Some((io_stack,redirs)) = io_info { + let io_frame = if let Some((io_stack,redirs)) = io_mode { io_stack.append_to_frame(redirs); let mut io_frame = io_stack.pop_frame(); io_frame.redirect()?; diff --git a/src/fern.rs b/src/fern.rs index 9c76b3e..39e38ee 100644 --- a/src/fern.rs +++ b/src/fern.rs @@ -47,6 +47,7 @@ fn set_termios() { } pub fn exec_input(input: &str) -> ShResult<()> { + let parse_start = Instant::now(); let mut tokens = vec![]; for token in LexStream::new(&input, LexFlags::empty()) { tokens.push(token?); @@ -56,9 +57,14 @@ pub fn exec_input(input: &str) -> ShResult<()> { for result in ParseStream::new(tokens) { nodes.push(result?); } + flog!(INFO, "parse duration: {:?}", parse_start.elapsed()); + let exec_start = Instant::now(); let mut dispatcher = Dispatcher::new(nodes); dispatcher.begin_dispatch()?; + flog!(INFO, "cmd duration: {:?}", exec_start.elapsed()); + + flog!(INFO, "total duration: {:?}", parse_start.elapsed()); Ok(()) } @@ -66,13 +72,13 @@ fn main() { save_termios(); set_termios(); sig_setup(); + + loop { let input = prompt::read_line().unwrap(); - let start = Instant::now(); if let Err(e) = exec_input(&input) { eprintln!("{e}"); } - flog!(INFO, "cmd duration: {:?}", start.elapsed()); } } diff --git a/src/jobs.rs b/src/jobs.rs index b49a218..b0f658f 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -318,6 +318,7 @@ impl JobTab { } } +#[derive(Debug)] pub struct JobBldr { table_id: Option, pgid: Option, @@ -373,6 +374,25 @@ impl JobBldr { } } +/// A wrapper around Vec with some job-specific methods +pub struct JobStack(Vec); + +impl JobStack { + pub fn new() -> Self { + Self(vec![]) + } + pub fn new_job(&mut self) { + self.0.push(JobBldr::new()) + } + pub fn curr_job_mut(&mut self) -> Option<&mut JobBldr> { + self.0.last_mut() + } + pub fn finalize_job(&mut self) -> Option { + let job = self.0.pop().map(|bldr| bldr.build()); + job + } +} + #[derive(Debug,Clone)] pub struct Job { table_id: Option, diff --git a/src/libsh/error.rs b/src/libsh/error.rs index 5bc98c2..6671a6e 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -44,6 +44,12 @@ impl<'s> ShErr { let span = span.into(); Self::Full { kind, msg, span } } + pub fn kind(&self) -> &ShErrKind { + match self { + ShErr::Simple { kind, msg: _ } | + ShErr::Full { kind, msg: _, span: _ } => kind + } + } } impl Display for ShErr { diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 949e58a..0e8ee1c 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -1,6 +1,7 @@ use std::collections::VecDeque; use crate::parse::lex::{Span, Tk}; +use crate::parse::{Redir, RedirType}; use crate::prelude::*; pub trait VecDequeExt { @@ -12,6 +13,13 @@ pub trait TkVecUtils { fn debug_tokens(&self); } +pub trait RedirVecUtils { + /// Splits the vector of redirections into two vectors + /// + /// One vector contains input redirs, the other contains output redirs + fn split_by_channel(self) -> (Vec,Vec); +} + impl VecDequeExt for VecDeque { fn to_vec(self) -> Vec { self.into_iter().collect::>() @@ -41,3 +49,25 @@ impl<'t> TkVecUtils> for Vec> { } } } + +impl RedirVecUtils for Vec { + fn split_by_channel(self) -> (Vec,Vec) { + let mut input = vec![]; + let mut output = vec![]; + for redir in self { + match redir.class { + RedirType::Input => input.push(redir), + RedirType::Pipe => { + match redir.io_mode.tgt_fd() { + STDIN_FILENO => input.push(redir), + STDOUT_FILENO | + STDERR_FILENO => output.push(redir), + _ => unreachable!() + } + } + _ => output.push(redir) + } + } + (input,output) + } +} diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 14f4e41..876e926 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -1,9 +1,9 @@ use std::collections::VecDeque; -use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, Job, JobBldr}, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}}; +use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, write_vars}}; -use super::{lex::{Span, Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdFlags, NdRule, Node, Redir, RedirType}; +use super::{lex::{Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, Redir, RedirType}; pub enum AssignBehavior { Export, @@ -40,13 +40,13 @@ impl ExecArgs { pub struct Dispatcher<'t> { nodes: VecDeque>, pub io_stack: IoStack, - pub curr_job: Option + pub job_stack: JobStack } impl<'t> Dispatcher<'t> { pub fn new(nodes: Vec>) -> Self { let nodes = VecDeque::from(nodes); - Self { nodes, io_stack: IoStack::new(), curr_job: None } + Self { nodes, io_stack: IoStack::new(), job_stack: JobStack::new() } } pub fn begin_dispatch(&mut self) -> ShResult<()> { flog!(TRACE, "beginning dispatch"); @@ -57,8 +57,10 @@ impl<'t> Dispatcher<'t> { } pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> { match node.class { - NdRule::CmdList {..} => self.exec_conjunction(node)?, + NdRule::Conjunction {..} => self.exec_conjunction(node)?, NdRule::Pipeline {..} => self.exec_pipeline(node)?, + NdRule::IfNode {..} => self.exec_if(node)?, + NdRule::LoopNode {..} => self.exec_loop(node)?, NdRule::Command {..} => self.dispatch_cmd(node)?, _ => unreachable!() } @@ -75,7 +77,7 @@ impl<'t> Dispatcher<'t> { } } pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> { - let NdRule::CmdList { elements } = conjunction.class else { + let NdRule::Conjunction { elements } = conjunction.class else { unreachable!() }; @@ -93,11 +95,96 @@ impl<'t> Dispatcher<'t> { } Ok(()) } + pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> { + let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else { + unreachable!(); + }; + let keep_going = |kind: LoopKind, status: i32| -> bool { + match kind { + LoopKind::While => status == 0, + LoopKind::Until => status != 0 + } + }; + + let io_frame = self.io_stack.pop_frame(); + let (mut cond_frame,mut body_frame) = io_frame.split_frame(); + let (in_redirs,out_redirs) = loop_stmt.redirs.split_by_channel(); + cond_frame.extend(in_redirs); + body_frame.extend(out_redirs); + + let CondNode { cond, body } = cond_node; + loop { + self.io_stack.push(cond_frame.clone()); + + if let Err(e) = self.dispatch_node(*cond.clone()) { + state::set_status(1); + return Err(e.into()); + } + + let status = state::get_status(); + if keep_going(kind,status) { + self.io_stack.push(body_frame.clone()); + for node in &body { + if let Err(e) = self.dispatch_node(node.clone()) { + match e.kind() { + ShErrKind::LoopBreak => break, + ShErrKind::LoopContinue => continue, + _ => return Err(e.into()) + } + } + } + } else { + break + } + } + + Ok(()) + } + pub fn exec_if(&mut self, if_stmt: Node<'t>) -> ShResult<()> { + let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else { + unreachable!(); + }; + // Pop the current frame and split it + let io_frame = self.io_stack.pop_frame(); + let (mut cond_frame,mut body_frame) = io_frame.split_frame(); + let (in_redirs,out_redirs) = if_stmt.redirs.split_by_channel(); + cond_frame.extend(in_redirs); // Condition gets input redirs + body_frame.extend(out_redirs); // Body gets output redirs + + for node in cond_nodes { + let CondNode { cond, body } = node; + self.io_stack.push(cond_frame.clone()); + + if let Err(e) = self.dispatch_node(*cond) { + state::set_status(1); + return Err(e.into()); + } + + match state::get_status() { + 0 => { + for body_node in body { + self.io_stack.push(body_frame.clone()); + self.dispatch_node(body_node)?; + } + } + _ => continue + } + } + + if !else_block.is_empty() { + for node in else_block { + self.io_stack.push(body_frame.clone()); + self.dispatch_node(node)?; + } + } + + Ok(()) + } pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> { let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else { unreachable!() }; - self.curr_job = Some(JobBldr::new()); + self.job_stack.new_job(); // Zip the commands and their respective pipes into an iterator let pipes_and_cmds = get_pipe_stack(cmds.len()) .into_iter() @@ -112,21 +199,18 @@ impl<'t> Dispatcher<'t> { } self.dispatch_node(cmd)?; } - let job = self.finalize_job(); + let job = self.job_stack.finalize_job().unwrap(); let is_bg = pipeline.flags.contains(NdFlags::BACKGROUND); dispatch_job(job, is_bg)?; Ok(()) } - pub fn finalize_job(&mut self) -> Job { - self.curr_job.take().unwrap().build() - } pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> { let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else { unreachable!() }; let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export); let cmd_raw = cmd.get_command().unwrap(); - let curr_job_mut = self.curr_job.as_mut().unwrap(); + let curr_job_mut = self.job_stack.curr_job_mut().unwrap(); let io_stack_mut = &mut self.io_stack; flog!(TRACE, "doing builtin"); @@ -178,7 +262,7 @@ impl<'t> Dispatcher<'t> { run_fork( io_frame, Some(exec_args), - self.curr_job.as_mut().unwrap(), + self.job_stack.curr_job_mut().unwrap(), def_child_action, def_parent_action )?; @@ -318,9 +402,9 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option,Option)> { if i == num_cmds - 1 { stack.push((prev_read.take(), None)); } else { - let (rpipe,wpipe) = IoPipe::get_pipes(); - let r_redir = Redir::new(Box::new(rpipe), RedirType::Input); - let w_redir = Redir::new(Box::new(wpipe), RedirType::Output); + let (rpipe,wpipe) = IoMode::get_pipes(); + let r_redir = Redir::new(rpipe, RedirType::Input); + let w_redir = Redir::new(wpipe, RedirType::Output); // Push (prev_read, Some(w_redir)) and set prev_read to r_redir stack.push((prev_read.take(), Some(w_redir))); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a9ac098..9a51c54 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -4,13 +4,13 @@ use bitflags::bitflags; use fmt::Display; use lex::{Span, Tk, TkFlags, TkRule}; -use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::{IoFd, IoFile, IoInfo}}; +use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode}; pub mod lex; pub mod execute; -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct Node<'t> { pub class: NdRule<'t>, pub flags: NdFlags, @@ -39,27 +39,27 @@ impl<'t> Node<'t> { } bitflags! { -#[derive(Debug)] +#[derive(Clone,Copy,Debug)] pub struct NdFlags: u32 { const BACKGROUND = 0b000001; } } -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct Redir { - pub io_info: Box, + pub io_mode: IoMode, pub class: RedirType } impl Redir { - pub fn new(io_info: Box, class: RedirType) -> Self { - Self { io_info, class } + pub fn new(io_mode: IoMode, class: RedirType) -> Self { + Self { io_mode, class } } } #[derive(Default,Debug)] pub struct RedirBldr { - pub io_info: Option>, + pub io_mode: Option, pub class: Option, pub tgt_fd: Option, } @@ -68,20 +68,20 @@ impl RedirBldr { pub fn new() -> Self { Default::default() } - pub fn with_io_info(self, io_info: Box) -> Self { - let Self { io_info: _, class, tgt_fd } = self; - Self { io_info: Some(io_info), class, tgt_fd } + pub fn with_io_mode(self, io_mode: IoMode) -> Self { + let Self { io_mode: _, class, tgt_fd } = self; + Self { io_mode: Some(io_mode), class, tgt_fd } } pub fn with_class(self, class: RedirType) -> Self { - let Self { io_info, class: _, tgt_fd } = self; - Self { io_info, class: Some(class), tgt_fd } + let Self { io_mode, class: _, tgt_fd } = self; + Self { io_mode, class: Some(class), tgt_fd } } pub fn with_tgt(self, tgt_fd: RawFd) -> Self { - let Self { io_info, class, tgt_fd: _ } = self; - Self { io_info, class, tgt_fd: Some(tgt_fd) } + let Self { io_mode, class, tgt_fd: _ } = self; + Self { io_mode, class, tgt_fd: Some(tgt_fd) } } pub fn build(self) -> Redir { - Redir::new(self.io_info.unwrap(), self.class.unwrap()) + Redir::new(self.io_mode.unwrap(), self.class.unwrap()) } } @@ -155,8 +155,8 @@ impl FromStr for RedirBldr { }); redir = redir.with_tgt(tgt_fd); if let Ok(src_fd) = src_fd.parse::() { - let io_info = IoFd::new(tgt_fd, src_fd); - redir = redir.with_io_info(Box::new(io_info)); + let io_mode = IoMode::fd(tgt_fd, src_fd); + redir = redir.with_io_mode(io_mode); } Ok(redir) } @@ -174,13 +174,13 @@ pub enum RedirType { HereString, // <<< } -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct CondNode<'t> { pub cond: Box>, pub body: Vec> } -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct CaseNode<'t> { pub pattern: Tk<'t>, pub body: Vec> @@ -193,13 +193,13 @@ pub enum ConjunctOp { Null } -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct ConjunctNode<'t> { pub cmd: Box>, pub operator: ConjunctOp } -#[derive(Debug)] +#[derive(Clone,Copy,Debug)] pub enum LoopKind { While, Until @@ -225,7 +225,7 @@ impl Display for LoopKind { } } -#[derive(Debug)] +#[derive(Clone,Debug)] pub enum AssignKind { Eq, PlusEq, @@ -234,7 +234,7 @@ pub enum AssignKind { DivEq, } -#[derive(Debug)] +#[derive(Clone,Debug)] pub enum NdRule<'t> { IfNode { cond_nodes: Vec>, else_block: Vec> }, LoopNode { kind: LoopKind, cond_node: CondNode<'t> }, @@ -242,7 +242,7 @@ pub enum NdRule<'t> { CaseNode { pattern: Tk<'t>, case_blocks: Vec> }, Command { assignments: Vec>, argv: Vec> }, Pipeline { cmds: Vec>, pipe_err: bool }, - CmdList { elements: Vec> }, + Conjunction { elements: Vec> }, Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> }, } @@ -294,6 +294,28 @@ impl<'t> ParseStream<'t> { node_tks.push(self.next_tk().unwrap()); } } + fn assert_separator(&mut self, node_tks: &mut Vec>) -> ShResult<()> { + let next_class = self.next_tk_class(); + match next_class { + TkRule::EOI | + TkRule::Or | + TkRule::Bg | + TkRule::And | + TkRule::Pipe => Ok(()), + + TkRule::Sep => { + if let Some(tk) = self.next_tk() { + node_tks.push(tk); + } + Ok(()) + } + _ => { + Err( + ShErr::simple(ShErrKind::ParseErr, "Expected a semicolon or newline here") + ) + } + } + } fn next_tk_is_some(&self) -> bool { self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI) } @@ -302,6 +324,11 @@ impl<'t> ParseStream<'t> { tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw }) } + fn check_redir(&self) -> bool { + self.tokens.first().is_some_and(|tk| { + tk.class == TkRule::Redir + }) + } /// Slice off consumed tokens fn commit(&mut self, num_consumed: usize) { assert!(num_consumed <= self.tokens.len()); @@ -334,7 +361,7 @@ impl<'t> ParseStream<'t> { Ok(None) } else { Ok(Some(Node { - class: NdRule::CmdList { elements }, + class: NdRule::Conjunction { elements }, flags: NdFlags::empty(), redirs: vec![], tokens: node_tks @@ -370,6 +397,7 @@ impl<'t> ParseStream<'t> { let mut node_tks: Vec = vec![]; let mut cond_nodes: Vec = vec![]; let mut else_block: Vec = vec![]; + let mut redirs = vec![]; if !self.check_keyword("if") || !self.next_tk_is_some() { return Ok(None) @@ -442,12 +470,51 @@ impl<'t> ParseStream<'t> { )); } node_tks.push(self.next_tk().unwrap()); - self.catch_separator(&mut node_tks); + + while self.check_redir() { + let tk = self.next_tk().unwrap(); + node_tks.push(tk.clone()); + let redir_bldr = tk.span.as_str().parse::().unwrap(); + if redir_bldr.io_mode.is_none() { + let path_tk = self.next_tk(); + + if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) { + self.flags |= ParseFlags::ERROR; + return Err( + ShErr::full( + ShErrKind::ParseErr, + "Expected a filename after this redirection", + tk.span.clone().into() + ) + ) + }; + + let path_tk = path_tk.unwrap(); + node_tks.push(path_tk.clone()); + let redir_class = redir_bldr.class.unwrap(); + let pathbuf = PathBuf::from(path_tk.span.as_str()); + + let Ok(file) = get_redir_file(redir_class, pathbuf) else { + self.flags |= ParseFlags::ERROR; + return Err(parse_err_full( + "Error opening file for redirection", + &path_tk.span + )); + }; + + let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file); + let redir_bldr = redir_bldr.with_io_mode(io_mode); + let redir = redir_bldr.build(); + redirs.push(redir); + } + } + + self.assert_separator(&mut node_tks)?; let node = Node { class: NdRule::IfNode { cond_nodes, else_block }, flags: NdFlags::empty(), - redirs: vec![], + redirs, tokens: node_tks }; Ok(Some(node)) @@ -505,7 +572,7 @@ impl<'t> ParseStream<'t> { )) } node_tks.push(self.next_tk().unwrap()); - self.catch_separator(&mut node_tks); + self.assert_separator(&mut node_tks)?; cond_node = CondNode { cond: Box::new(cond), body }; let loop_node = Node { @@ -594,7 +661,7 @@ impl<'t> ParseStream<'t> { TkRule::Redir => { node_tks.push(tk.clone()); let redir_bldr = tk.span.as_str().parse::().unwrap(); - if redir_bldr.io_info.is_none() { + if redir_bldr.io_mode.is_none() { let path_tk = tk_iter.next(); if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) { @@ -610,29 +677,10 @@ impl<'t> ParseStream<'t> { let path_tk = path_tk.unwrap(); node_tks.push(path_tk.clone()); + let redir_class = redir_bldr.class.unwrap(); + let pathbuf = PathBuf::from(path_tk.span.as_str()); - let Ok(file) = (match redir_bldr.class.unwrap() { - RedirType::Input => { - OpenOptions::new() - .read(true) - .open(Path::new(path_tk.span.as_str())) - } - RedirType::Output => { - OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(Path::new(path_tk.span.as_str())) - } - RedirType::Append => { - OpenOptions::new() - .write(true) - .create(true) - .append(true) - .open(Path::new(path_tk.span.as_str())) - } - _ => unreachable!() - }) else { + let Ok(file) = get_redir_file(redir_class, pathbuf) else { self.flags |= ParseFlags::ERROR; return Err(parse_err_full( "Error opening file for redirection", @@ -640,8 +688,8 @@ impl<'t> ParseStream<'t> { )); }; - let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file); - let redir_bldr = redir_bldr.with_io_info(Box::new(io_info)); + let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file); + let redir_bldr = redir_bldr.with_io_mode(io_mode); let redir = redir_bldr.build(); redirs.push(redir); } @@ -797,6 +845,32 @@ fn node_is_punctuated<'t>(tokens: &Vec) -> bool { }) } +fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult { + let result = match class { + RedirType::Input => { + OpenOptions::new() + .read(true) + .open(Path::new(&path)) + } + RedirType::Output => { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(Path::new(&path)) + } + RedirType::Append => { + OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(Path::new(&path)) + } + _ => unimplemented!() + }; + Ok(result?) +} + fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr { ShErr::full( ShErrKind::ParseErr, diff --git a/src/prelude.rs b/src/prelude.rs index 182d734..98a1486 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,6 +16,7 @@ pub use std::path::{ Path, PathBuf }; pub use std::ffi::{ CStr, CString }; pub use std::process::exit; pub use std::time::Instant; +pub use std::rc::Rc; pub use std::mem; pub use std::env; pub use std::fmt; diff --git a/src/procio.rs b/src/procio.rs index 52c82f0..51a9971 100644 --- a/src/procio.rs +++ b/src/procio.rs @@ -1,188 +1,67 @@ use std::{fmt::Debug, ops::{Deref, DerefMut}}; -use crate::{libsh::error::ShResult, parse::{Redir, RedirType}, prelude::*}; +use crate::{libsh::{error::ShResult, utils::RedirVecUtils}, parse::{Redir, RedirType}, prelude::*}; // Credit to fish-shell for many of the implementation ideas present in this module // https://fishshell.com/ +#[derive(Clone,Debug)] pub enum IoMode { - Fd, - File, - Pipe, + Fd { tgt_fd: RawFd, src_fd: Rc }, + File { tgt_fd: RawFd, file: Rc }, + Pipe { tgt_fd: RawFd, pipe: Rc }, } -pub trait IoInfo: Read { - fn mode(&self) -> IoMode; - /// The fildesc that is replaced by src_fd in dup2() - /// e.g. `dup2(src_fd, tgt_fd)` - fn tgt_fd(&self) -> RawFd; - /// The fildesc that replaces tgt_fd in dup2() - /// e.g. `dup2(src_fd, tgt_fd)` - fn src_fd(&self) -> RawFd; - fn print(&self) -> String; - fn close(&mut self) -> ShResult<()>; -} - -macro_rules! read_impl { - ($type:path) => { - impl Read for $type { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - let src_fd = self.src_fd(); - - Ok(read(src_fd, buf)?) - } +impl IoMode { + pub fn fd(tgt_fd: RawFd, src_fd: RawFd) -> Self { + let src_fd = unsafe { OwnedFd::from_raw_fd(src_fd).into() }; + Self::Fd { tgt_fd, src_fd } + } + pub fn file(tgt_fd: RawFd, file: File) -> Self { + let file = file.into(); + Self::File { tgt_fd, file } + } + pub fn pipe(tgt_fd: RawFd, pipe: OwnedFd) -> Self { + let pipe = pipe.into(); + Self::Pipe { tgt_fd, pipe } + } + pub fn tgt_fd(&self) -> RawFd { + match self { + IoMode::Fd { tgt_fd, src_fd: _ } | + IoMode::File { tgt_fd, file: _ } | + IoMode::Pipe { tgt_fd, pipe: _ } => *tgt_fd } - }; -} -read_impl!(IoPipe); -read_impl!(IoFile); -read_impl!(IoFd); - - -// TODO: implement this -impl Debug for Box { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f,"{}",self.print()) } -} - -/// A redirection to a raw fildesc -/// e.g. `2>&1` -#[derive(Debug)] -pub struct IoFd { - tgt_fd: RawFd, - src_fd: RawFd -} - -impl IoFd { - pub fn new(tgt_fd: RawFd, src_fd: RawFd) -> Self { - Self { tgt_fd, src_fd } - } -} - -impl IoInfo for IoFd { - fn mode(&self) -> IoMode { - IoMode::Fd - } - fn tgt_fd(&self) -> RawFd { - self.tgt_fd - } - fn src_fd(&self) -> RawFd { - self.src_fd - } - fn close(&mut self) -> ShResult<()> { - if self.src_fd == -1 { - return Ok(()) + pub fn src_fd(&self) -> RawFd { + match self { + IoMode::Fd { tgt_fd: _, src_fd } => src_fd.as_raw_fd(), + IoMode::File { tgt_fd: _, file } => file.as_raw_fd(), + IoMode::Pipe { tgt_fd: _, pipe } => pipe.as_raw_fd() } - close(self.src_fd)?; - self.src_fd = -1; - Ok(()) } - fn print(&self) -> String { - format!("{:?}",self) - } -} - -/// A redirection to a file -/// e.g. `> file.txt` -#[derive(Debug)] -pub struct IoFile { - tgt_fd: RawFd, - file: File -} - -impl IoFile { - pub fn new(tgt_fd: RawFd, file: File) -> Self { - Self { tgt_fd, file } - } -} - -impl IoInfo for IoFile { - fn mode(&self) -> IoMode { - IoMode::File - } - fn tgt_fd(&self) -> RawFd { - self.tgt_fd - } - fn src_fd(&self) -> RawFd { - self.file.as_raw_fd() - } - fn close(&mut self) -> ShResult<()> { - // Closes on it's own when it's dropped - Ok(()) - } - fn print(&self) -> String { - format!("{:?}",self) - } -} - -/// A redirection to a pipe -/// e.g. `echo foo | sed s/foo/bar/` -#[derive(Debug)] -pub struct IoPipe { - tgt_fd: RawFd, - pipe_fd: OwnedFd -} - -impl IoPipe { - pub fn new(tgt_fd: RawFd, pipe_fd: OwnedFd) -> Self { - Self { tgt_fd, pipe_fd } - } - pub fn get_pipes() -> (Self, Self) { + pub fn get_pipes() -> (Self,Self) { let (rpipe,wpipe) = pipe().unwrap(); - let r_iopipe = Self::new(STDIN_FILENO, rpipe); - let w_iopipe = Self::new(STDOUT_FILENO, wpipe); - - (r_iopipe,w_iopipe) + ( + Self::Pipe { tgt_fd: STDIN_FILENO, pipe: rpipe.into() }, + Self::Pipe { tgt_fd: STDOUT_FILENO, pipe: wpipe.into() } + ) } } -impl IoInfo for IoPipe { - fn mode(&self) -> IoMode { - IoMode::Pipe - } - fn tgt_fd(&self) -> RawFd { - self.tgt_fd - } - fn src_fd(&self) -> RawFd { - self.pipe_fd.as_raw_fd() - } - fn close(&mut self) -> ShResult<()> { - // Closes on it's own - Ok(()) - } - fn print(&self) -> String { - format!("{:?}",self) - } -} - -pub struct FdWriter { - tgt: OwnedFd -} - -impl FdWriter { - pub fn new(fd: i32) -> Self { - let tgt = unsafe { OwnedFd::from_raw_fd(fd) }; - Self { tgt } - } -} - -impl Write for FdWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - Ok(write(&self.tgt, buf)?) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) +impl Read for IoMode { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let src_fd = self.src_fd(); + Ok(read(src_fd, buf)?) } } /// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively -#[derive(Debug)] -pub struct IoGroup(OwnedFd,OwnedFd,OwnedFd); +#[derive(Debug,Clone)] +pub struct IoGroup(RawFd,RawFd,RawFd); /// A single stack frame used with the IoStack /// Each stack frame represents the redirections of a single command -#[derive(Default,Debug)] +#[derive(Default,Clone,Debug)] pub struct IoFrame { redirs: Vec, saved_io: Option, @@ -196,65 +75,44 @@ impl<'e> IoFrame { Self { redirs, saved_io: None } } - /// This method returns a 2-tuple of `IoFrames`. - /// This is to be used in the case of shell structures such as `if-then` and `while-do`. - /// # Params - /// * redirs: a vector of redirections + /// Splits the frame into two frames /// - /// # Returns - /// * An `IoFrame` containing all of the redirections which target stdin - /// * An `IoFrame` containing all of the redirections which target stdout/stderr - /// - /// # Purpose - /// In the case of something like `if cat; then echo foo; fi < input.txt > output.txt` - /// This will cleanly separate the redirections such that `cat` can receive the input from input.txt - /// and `echo foo` can redirect it's output to output.txt - pub fn cond_and_body(redirs: Vec) -> (Self, Self) { - let mut output_redirs = vec![]; - let mut input_redirs = vec![]; - for redir in redirs { - match redir.class { - RedirType::Input => input_redirs.push(redir), - RedirType::Pipe => { - match redir.io_info.tgt_fd() { - STDIN_FILENO => input_redirs.push(redir), - STDOUT_FILENO | - STDERR_FILENO => output_redirs.push(redir), - _ => unreachable!() - } - } - _ => output_redirs.push(redir) - } - } - (Self::from_redirs(input_redirs),Self::from_redirs(output_redirs)) + /// One frame contains input redirections, the other contains output redirections + /// This is used in shell structures to route redirections either *to* the condition, or *from* the body + /// The first field of the tuple contains input redirections (used for the condition) + /// The second field contains output redirections (used for the body) + pub fn split_frame(self) -> (Self,Self) { + let Self { redirs, saved_io: _ } = self; + let (input_redirs,output_redirs) = redirs.split_by_channel(); + ( + Self::from_redirs(input_redirs), + Self::from_redirs(output_redirs) + ) } pub fn save(&'e mut self) { - unsafe { - let saved_in = OwnedFd::from_raw_fd(dup(STDIN_FILENO).unwrap()); - let saved_out = OwnedFd::from_raw_fd(dup(STDOUT_FILENO).unwrap()); - let saved_err = OwnedFd::from_raw_fd(dup(STDERR_FILENO).unwrap()); - self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err)); - } + let saved_in = dup(STDIN_FILENO).unwrap(); + let saved_out = dup(STDOUT_FILENO).unwrap(); + let saved_err = dup(STDERR_FILENO).unwrap(); + self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err)); } pub fn redirect(&mut self) -> ShResult<()> { self.save(); for redir in &mut self.redirs { - let io_info = &mut redir.io_info; - let tgt_fd = io_info.tgt_fd(); - let src_fd = io_info.src_fd(); + let io_mode = &mut redir.io_mode; + let tgt_fd = io_mode.tgt_fd(); + let src_fd = io_mode.src_fd(); dup2(src_fd, tgt_fd)?; - io_info.close()?; } Ok(()) } pub fn restore(&mut self) -> ShResult<()> { - while let Some(mut redir) = self.pop() { - redir.io_info.close()?; - } if let Some(saved) = self.saved_io.take() { - dup2(saved.0.as_raw_fd(), STDIN_FILENO)?; - dup2(saved.1.as_raw_fd(), STDOUT_FILENO)?; - dup2(saved.2.as_raw_fd(), STDERR_FILENO)?; + dup2(saved.0, STDIN_FILENO)?; + close(saved.0)?; + dup2(saved.1, STDOUT_FILENO)?; + close(saved.1)?; + dup2(saved.2, STDERR_FILENO)?; + close(saved.2)?; } Ok(()) } diff --git a/src/tests/snapshots/fern__tests__parser__parse_conjunction.snap b/src/tests/snapshots/fern__tests__parser__parse_conjunction.snap index 51e261b..e36d989 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_conjunction.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_conjunction.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_conjunction_and_pipeline.snap b/src/tests/snapshots/fern__tests__parser__parse_conjunction_and_pipeline.snap index b99bcc0..6b69cee 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_conjunction_and_pipeline.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_conjunction_and_pipeline.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_if_multiline.snap b/src/tests/snapshots/fern__tests__parser__parse_if_multiline.snap index 5f8100f..1aa2098 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_if_multiline.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_if_multiline.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_if_multiple_elif.snap b/src/tests/snapshots/fern__tests__parser__parse_if_multiple_elif.snap index a2b0138..d08d577 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_if_multiple_elif.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_if_multiple_elif.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_if_redirs.snap b/src/tests/snapshots/fern__tests__parser__parse_if_redirs.snap new file mode 100644 index 0000000..4421ab3 --- /dev/null +++ b/src/tests/snapshots/fern__tests__parser__parse_if_redirs.snap @@ -0,0 +1,435 @@ +--- +source: src/tests/parser.rs +expression: nodes +--- +[ + Ok( + Node { + class: Conjunction { + elements: [ + ConjunctNode { + cmd: Node { + class: IfNode { + cond_nodes: [ + CondNode { + cond: Node { + class: Pipeline { + cmds: [ + Node { + class: Command { + assignments: [], + argv: [ + Tk { + class: Str, + span: Span { + range: 3..6, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD, + ), + }, + ], + }, + flags: NdFlags( + 0x0, + ), + redirs: [], + tokens: [ + Tk { + class: Str, + span: Span { + range: 3..6, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD, + ), + }, + Tk { + class: Sep, + span: Span { + range: 6..8, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + ], + pipe_err: false, + }, + flags: NdFlags( + 0x0, + ), + redirs: [], + tokens: [ + Tk { + class: Str, + span: Span { + range: 3..6, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD, + ), + }, + Tk { + class: Sep, + span: Span { + range: 6..8, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + body: [ + Node { + class: Pipeline { + cmds: [ + Node { + class: Command { + assignments: [], + argv: [ + Tk { + class: Str, + span: Span { + range: 13..17, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD | BUILTIN, + ), + }, + Tk { + class: Str, + span: Span { + range: 18..21, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + flags: NdFlags( + 0x0, + ), + redirs: [], + tokens: [ + Tk { + class: Str, + span: Span { + range: 13..17, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD | BUILTIN, + ), + }, + Tk { + class: Str, + span: Span { + range: 18..21, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Sep, + span: Span { + range: 21..23, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + ], + pipe_err: false, + }, + flags: NdFlags( + 0x0, + ), + redirs: [], + tokens: [ + Tk { + class: Str, + span: Span { + range: 13..17, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD | BUILTIN, + ), + }, + Tk { + class: Str, + span: Span { + range: 18..21, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Sep, + span: Span { + range: 21..23, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + ], + }, + ], + else_block: [], + }, + flags: NdFlags( + 0x0, + ), + redirs: [ + Redir { + io_mode: File { + tgt_fd: 1, + file: File { + fd: 3, + path: "/home/pagedmov/Coding/projects/rust/fern/file.txt", + read: false, + write: true, + }, + }, + class: Output, + }, + ], + tokens: [ + Tk { + class: Str, + span: Span { + range: 0..2, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Str, + span: Span { + range: 3..6, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD, + ), + }, + Tk { + class: Sep, + span: Span { + range: 6..8, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 8..12, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Str, + span: Span { + range: 13..17, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD | BUILTIN, + ), + }, + Tk { + class: Str, + span: Span { + range: 18..21, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Sep, + span: Span { + range: 21..23, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 23..25, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Redir, + span: Span { + range: 26..27, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 28..36, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + operator: Null, + }, + ], + }, + flags: NdFlags( + 0x0, + ), + redirs: [], + tokens: [ + Tk { + class: Str, + span: Span { + range: 0..2, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Str, + span: Span { + range: 3..6, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD, + ), + }, + Tk { + class: Sep, + span: Span { + range: 6..8, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 8..12, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Str, + span: Span { + range: 13..17, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + IS_CMD | BUILTIN, + ), + }, + Tk { + class: Str, + span: Span { + range: 18..21, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Sep, + span: Span { + range: 21..23, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 23..25, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + KEYWORD, + ), + }, + Tk { + class: Redir, + span: Span { + range: 26..27, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + Tk { + class: Str, + span: Span { + range: 28..36, + source: "if foo; then echo bar; fi > file.txt", + }, + flags: TkFlags( + 0x0, + ), + }, + ], + }, + ), +] diff --git a/src/tests/snapshots/fern__tests__parser__parse_if_simple.snap b/src/tests/snapshots/fern__tests__parser__parse_if_simple.snap index 8927e8c..07f6c26 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_if_simple.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_if_simple.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_if_with_elif.snap b/src/tests/snapshots/fern__tests__parser__parse_if_with_elif.snap index 4c623db..73c47e6 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_if_with_elif.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_if_with_elif.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_loop_multiline.snap b/src/tests/snapshots/fern__tests__parser__parse_loop_multiline.snap index dcc1ffd..a1b4507 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_loop_multiline.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_loop_multiline.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_loop_simple.snap b/src/tests/snapshots/fern__tests__parser__parse_loop_simple.snap index 54f9fa9..7685408 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_loop_simple.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_loop_simple.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_loop_until.snap b/src/tests/snapshots/fern__tests__parser__parse_loop_until.snap index 607c9eb..1f1df75 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_loop_until.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_loop_until.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_multiline.snap b/src/tests/snapshots/fern__tests__parser__parse_multiline.snap index 29f2e8e..827de79 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_multiline.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_multiline.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { @@ -198,7 +198,7 @@ expression: nodes ), Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { @@ -391,7 +391,7 @@ expression: nodes ), Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_pipeline.snap b/src/tests/snapshots/fern__tests__parser__parse_pipeline.snap index a0916ae..903ec77 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_pipeline.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_pipeline.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node { diff --git a/src/tests/snapshots/fern__tests__parser__parse_simple.snap b/src/tests/snapshots/fern__tests__parser__parse_simple.snap index 30b1c39..b25f498 100644 --- a/src/tests/snapshots/fern__tests__parser__parse_simple.snap +++ b/src/tests/snapshots/fern__tests__parser__parse_simple.snap @@ -5,7 +5,7 @@ expression: nodes [ Ok( Node { - class: CmdList { + class: Conjunction { elements: [ ConjunctNode { cmd: Node {