aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-05-31 17:12:12 +0200
committerJonas Maier <jonas@x77.dev>2026-05-31 17:12:12 +0200
commit08f3af622cc3e7b3f85a60c6ffe83d9d70e9dc02 (patch)
tree5b2b7d303fc88023c67e6693194b072ac40f9630
parent7679654c5710d08f626f164731b9b929b93957d8 (diff)
downloadpish-08f3af622cc3e7b3f85a60c6ffe83d9d70e9dc02.tar.gz
basic case statement
-rw-r--r--src/parse/mod.rs95
-rw-r--r--src/run/mod.rs18
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,