use std::str::FromStr; use bitflags::bitflags; use lex::{Span, Tk, TkFlags, TkRule}; use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}}; pub mod lex; pub mod execute; #[derive(Debug)] pub struct Node<'t> { pub class: NdRule<'t>, pub flags: NdFlags, pub redirs: Vec, pub tokens: Vec>, } bitflags! { #[derive(Debug)] pub struct NdFlags: u32 { const BACKGROUND = 0b000001; } } #[derive(Debug)] pub struct Redir { pub io_info: Box, pub class: RedirType } impl Redir { pub fn new(io_info: Box, class: RedirType) -> Self { Self { io_info, class } } } #[derive(Default,Debug)] pub struct RedirBldr { pub io_info: Option>, pub class: Option, pub tgt_fd: Option, } 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_class(self, class: RedirType) -> Self { let Self { io_info, class: _, tgt_fd } = self; Self { io_info, 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) } } pub fn build(self) -> Redir { Redir::new(self.io_info.unwrap(), self.class.unwrap()) } } impl FromStr for RedirBldr { type Err = (); fn from_str(s: &str) -> Result { let mut chars = s.chars().peekable(); let mut src_fd = String::new(); let mut tgt_fd = String::new(); let mut redir = RedirBldr::new(); while let Some(ch) = chars.next() { match ch { '>' => { redir = redir.with_class(RedirType::Output); if let Some('>') = chars.peek() { chars.next(); redir = redir.with_class(RedirType::Append); } } '<' => { redir = redir.with_class(RedirType::Input); let mut count = 0; while count < 2 && matches!(chars.peek(), Some('<')) { chars.next(); count += 1; } redir = match count { 1 => redir.with_class(RedirType::HereDoc), 2 => redir.with_class(RedirType::HereString), _ => redir, // Default case remains RedirType::Input }; } '&' => { while let Some(next_ch) = chars.next() { if next_ch.is_ascii_digit() { src_fd.push(next_ch) } else { break } } if src_fd.is_empty() { return Err(()) } } _ if ch.is_ascii_digit() && tgt_fd.is_empty() => { tgt_fd.push(ch); while let Some(next_ch) = chars.peek() { if next_ch.is_ascii_digit() { let next_ch = chars.next().unwrap(); tgt_fd.push(next_ch); } else { break } } } _ => return Err(()) } } // FIXME: I am 99.999999999% sure that tgt_fd and src_fd are backwards here let tgt_fd = tgt_fd.parse::().unwrap_or_else(|_| { match redir.class.unwrap() { RedirType::Input | RedirType::HereDoc | RedirType::HereString => 0, _ => 1 } }); 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)); } Ok(redir) } } #[derive(PartialEq,Clone,Copy,Debug)] pub enum RedirType { Null, // Default Pipe, // | PipeAnd, // |&, redirs stderr and stdout Input, // < Output, // > Append, // >> HereDoc, // << HereString, // <<< } #[derive(Debug)] pub struct CondNode<'t> { pub cond: Vec>, pub body: Vec> } #[derive(Debug)] pub struct CaseNode<'t> { pub pattern: Tk<'t>, pub body: Vec> } #[derive(Clone,Copy,PartialEq,Debug)] pub enum ConjunctOp { And, Or, Null } #[derive(Debug)] pub struct ConjunctNode<'t> { pub cmd: Box>, pub operator: ConjunctOp } #[derive(Debug)] pub enum LoopKind { While, Until } #[derive(Debug)] pub enum AssignKind { Eq, PlusEq, MinusEq, MultEq, DivEq, } #[derive(Debug)] pub enum NdRule<'t> { IfNode { cond_blocks: Vec>, else_block: Vec> }, LoopNode { kind: LoopKind, cond_block: CondNode<'t> }, ForNode { vars: Vec>, arr: Vec>, body: Vec> }, CaseNode { pattern: Tk<'t>, case_blocks: Vec> }, Command { assignments: Vec>, argv: Vec> }, Pipeline { cmds: Vec>, pipe_err: bool }, CmdList { elements: Vec> }, Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> }, } /// If the given expression returns Some(), then return it #[derive(Debug)] pub enum ParseResult<'t> { NoMatch, Error(ShErr<'t>), Match(Node<'t>) } use ParseResult::*; // muh ergonomics #[derive(Debug)] pub struct ParseStream<'t> { pub tokens: Vec>, pub flags: ParseFlags } bitflags! { #[derive(Debug)] pub struct ParseFlags: u32 { const ERROR = 0b0000001; } } impl<'t> ParseStream<'t> { pub fn new(tokens: Vec>) -> Self { Self { tokens, flags: ParseFlags::empty() } } fn next_tk_class(&self) -> &TkRule { if let Some(tk) = self.tokens.first() { &tk.class } else { &TkRule::Null } } fn next_tk(&mut self) -> Option> { if !self.tokens.is_empty() { if *self.next_tk_class() == TkRule::EOI { return None } Some(self.tokens.remove(0)) } else { None } } /// Slice off consumed tokens fn commit(&mut self, num_consumed: usize) { assert!(num_consumed <= self.tokens.len()); self.tokens = self.tokens[num_consumed..].to_vec(); } fn parse_cmd_list(&mut self) -> ParseResult<'t> { let mut elements = vec![]; let mut node_tks = vec![]; while let Match(block) = self.parse_block(true) { node_tks.append(&mut block.tokens.clone()); let conjunct_op = match self.next_tk_class() { TkRule::And => ConjunctOp::And, TkRule::Or => ConjunctOp::Or, _ => ConjunctOp::Null }; let conjunction = ConjunctNode { cmd: Box::new(block), operator: conjunct_op }; elements.push(conjunction); let Some(tk) = self.next_tk() else { break }; node_tks.push(tk); if conjunct_op == ConjunctOp::Null { break } } Match(Node { class: NdRule::CmdList { elements }, flags: NdFlags::empty(), redirs: vec![], tokens: node_tks }) } /// This tries to match on different stuff that can appear in a command position /// Matches shell commands like if-then-fi, pipelines, etc. /// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom /// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline fn parse_block(&mut self, check_pipelines: bool) -> ParseResult<'t> { if check_pipelines { match self.parse_pipeline() { NoMatch => { /* Continue */ } result => return result } } else { match self.parse_cmd() { NoMatch => { /* Continue */ } result => return result } } NoMatch } fn parse_pipeline(&mut self) -> ParseResult<'t> { let mut cmds = vec![]; let mut node_tks = vec![]; while let Match(cmd) = self.parse_block(false) { let is_punctuated = node_is_punctuated(&cmd.tokens); node_tks.append(&mut cmd.tokens.clone()); cmds.push(cmd); if *self.next_tk_class() != TkRule::Pipe || is_punctuated { break } else { if let Some(pipe) = self.next_tk() { node_tks.push(pipe) } else { break } } } if cmds.is_empty() { NoMatch } else { Match(Node { // TODO: implement pipe_err support class: NdRule::Pipeline { cmds, pipe_err: false }, flags: NdFlags::empty(), redirs: vec![], tokens: node_tks }) } } fn parse_cmd(&mut self) -> ParseResult<'t> { let tk_slice = self.tokens.as_slice(); let mut tk_iter = tk_slice.iter(); let mut node_tks = vec![]; let mut redirs = vec![]; let mut argv = vec![]; let mut assignments = vec![]; let Some(cmd_tk) = tk_iter.next() else { return NoMatch }; if !cmd_tk.flags.contains(TkFlags::IS_CMD) { return NoMatch } else { node_tks.push(cmd_tk.clone()); argv.push(cmd_tk.clone()); } while let Some(tk) = tk_iter.next() { match tk.class { TkRule::EOI | TkRule::Pipe | TkRule::And | TkRule::Or => { break } TkRule::Sep => { node_tks.push(tk.clone()); break } TkRule::Str => { argv.push(tk.clone()); node_tks.push(tk.clone()); } TkRule::Redir => { node_tks.push(tk.clone()); let redir_bldr = tk.span.as_str().parse::().unwrap(); if redir_bldr.io_info.is_none() { let Some(path_tk) = tk_iter.next() else { self.flags |= ParseFlags::ERROR; return Error( ShErr::full( ShErrKind::ParseErr, "Expected a filename after this redirection", tk.span.clone() ) ) }; node_tks.push(path_tk.clone()); 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 { self.flags |= ParseFlags::ERROR; return Error( ShErr::full( ShErrKind::InternalErr, "Error opening file for redirection", path_tk.span.clone() ) ) }; let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file); let redir_bldr = redir_bldr.with_io_info(Box::new(io_info)); let redir = redir_bldr.build(); redirs.push(redir); } } _ => unimplemented!("Unexpected token rule `{:?}` in parse_cmd()",tk.class) } } self.commit(node_tks.len()); Match(Node { class: NdRule::Command { assignments, argv }, tokens: node_tks, flags: NdFlags::empty(), redirs, }) } } impl<'t> Iterator for ParseStream<'t> { type Item = ParseResult<'t>; fn next(&mut self) -> Option { // Empty token vector or only SOI/EOI tokens, nothing to do if self.tokens.is_empty() || self.tokens.len() == 2 { return None } if self.flags.contains(ParseFlags::ERROR) { return None } if let Some(tk) = self.tokens.first() { if tk.class == TkRule::EOI { return None } if tk.class == TkRule::SOI { self.next_tk(); } } match self.parse_cmd_list() { NoMatch => None, Error(err) => Some(Error(err)), Match(cmdlist) => Some(Match(cmdlist)) } } } fn node_is_punctuated<'t>(tokens: &Vec) -> bool { tokens.last().is_some_and(|tk| { matches!(tk.class, TkRule::Sep) }) }