diff options
| -rw-r--r-- | src/parse/mod.rs | 95 | ||||
| -rw-r--r-- | src/run/mod.rs | 18 |
2 files changed, 113 insertions, 0 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6ce3133..68a5e56 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -87,6 +87,7 @@ pub enum Ast<T: Stage> { Pipes(Pipes<T>), If(If<T>), While(While), + Case(Case<T>), } #[derive(Debug, Clone, PartialEq)] @@ -276,6 +277,20 @@ impl CmdDisplay for Ast<PreExpansion> { l.block.cdisplay(w)?; write!(w, ")")?; } + Ast::Case(c) => { + write!(w, "case(")?; + c.discriminant.cdisplay(w)?; + write!(w, ", [")?; + let mut first = true; + for case in c.branches.iter() { + if !first { + write!(w, ", ")?; + } + first = false; + case.cdisplay(w)?; + } + write!(w, "])")?; + } } Ok(()) } @@ -360,6 +375,7 @@ impl Ast<PreExpansion> { Ast::FunDecl(fd) => Ok(Ast::FunDecl(fd.expand(e)?)), Ast::If(i) => Ok(Ast::If(i.expand(e)?)), Ast::While(w) => Ok(Ast::While(w)), + Ast::Case(c) => Ok(Ast::Case(c.expand(e)?)), } } } @@ -1231,6 +1247,7 @@ impl Ast<PreExpansion> { Ast::Pipes(p) => p.completion(e), Ast::If(i) => i.completion(e), Ast::While(_) => todo!(), + Ast::Case(_) => todo!(), } } } @@ -1621,6 +1638,7 @@ pub enum Keyword { Elif, OpenBrace, CloseBrace, + Case, } impl Keyword { @@ -1632,6 +1650,7 @@ impl Keyword { Keyword::Else => b"else", Keyword::OpenBrace => b"{", Keyword::CloseBrace => b"}", + Keyword::Case => b"case", } } @@ -1643,6 +1662,7 @@ impl Keyword { Keyword::Else => false, Keyword::OpenBrace => false, Keyword::CloseBrace => false, + Keyword::Case => true, } } @@ -1750,6 +1770,14 @@ impl Ast<PreExpansion> { x?; } + let orig_len = b.buf.len(); + let x = Case::parse(b); + if let Ok(c) = x { + return Ok(Self::Case(c)); + } else if b.buf.len() != orig_len { + x?; + } + Ok(Self::Pipes(b.parse()?)) } } @@ -1813,3 +1841,70 @@ impl Parse for Pipes<PreExpansion> { } } } + +#[derive(Debug, Clone, PartialEq)] +pub struct CaseBranch { + pub pattern: BString, + pub block: Block, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Case<T: Stage> { + pub discriminant: T::Str, + pub branches: Vec<CaseBranch>, +} + +impl CmdDisplay for CaseBranch { + fn cdisplay(&self, w: &mut dyn std::io::Write) -> std::io::Result<()> { + write!(w, "cbranch(b\"{}\", ", self.pattern.escape_ascii())?; + self.block.cdisplay(w)?; + write!(w, ")") + } +} + +impl Case<PreExpansion> { + fn expand<E: Expander>(self, e: &mut E) -> Res<Case<PostExpansion>, E::Error> { + Ok(Case { + discriminant: self.discriminant.expand(e)?, + branches: self.branches, + }) + } +} + +impl Parse for CaseBranch { + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + b.spaces(); + + let mut pattern = Vec::new(); + while b.has() && b.peek() != b'{' { + pattern.push(b.adv()); + } + while let Some(b' ' | b'\n' | b'\t' | b'\r') = pattern.last() { + pattern.pop(); + } + + let block = Block::parse(b)?; + + Ok(Self { pattern, block }) + } +} + +impl Parse for Case<PreExpansion> { + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + b.consume_keyword(Keyword::Case)?; + let discriminant = ExpString::parse(b)?; + b.consume_keyword(Keyword::OpenBrace)?; + let mut branches = Vec::new(); + loop { + b.spaces(); + if let Ok(_) = b.consume_keyword(Keyword::CloseBrace) { + break; + } + branches.push(CaseBranch::parse(b)?); + } + Ok(Self { + discriminant, + branches, + }) + } +} diff --git a/src/run/mod.rs b/src/run/mod.rs index a2a6cb1..f86278d 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -26,6 +26,7 @@ pub enum ExecError { Parse(crate::parse::ParseError), Break, Continue, + NoCaseMatch, } impl ExecError { @@ -71,6 +72,7 @@ impl ExecError { ExecError::Parse(pe) => format!("parse error: {pe:?}"), ExecError::Break => "break: only useful in loops".to_string(), ExecError::Continue => "continue: only useful in loops".to_string(), + ExecError::NoCaseMatch => "case: no pattern matched".to_string(), } } } @@ -356,6 +358,7 @@ impl Executor { Ast::Pipes(pipes) => self.execute_pipeline(pipes, stdin, stdout), Ast::If(cond) => self.execute_if(cond, stdin, stdout), Ast::While(w) => self.execute_while(w, stdin, stdout), + Ast::Case(c) => self.execute_case(c, stdin, stdout), } } @@ -446,6 +449,21 @@ impl Executor { SpawnedCmd::Fun(handle) } + fn execute_case( + &mut self, + c: parse::Case<PostExpansion>, + stdin: InputReader, + stdout: OutputWriter, + ) -> SpawnedCmd { + for branch in c.branches.into_iter() { + // TODO: regex case patterns + if branch.pattern == c.discriminant { + return self.execute_block(branch.block, stdin, stdout); + } + } + SpawnedCmd::Joined(Err(ExecError::NoCaseMatch)) + } + pub fn execute_script( &mut self, s: parse::Script, |
