aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-03-09 20:49:53 +0100
committerJonas Maier <jonas@x77.dev>2026-03-09 20:49:53 +0100
commit5d8d9f07669cdc8ac17c866edb6a8c07bbe4221c (patch)
treef37429eadff223b4381361b7994b6a9840f00f4e /src
parent93e5286946b5ed3e6c27a4ef6f1b457a1f6bebc7 (diff)
downloadpish-5d8d9f07669cdc8ac17c866edb6a8c07bbe4221c.tar.gz
variable default value
Diffstat (limited to 'src')
-rw-r--r--src/parse/mod.rs77
-rw-r--r--src/parse/test.rs8
-rw-r--r--src/run/mod.rs11
3 files changed, 80 insertions, 16 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index d25767d..4806421 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -28,7 +28,7 @@ type Res<T, E> = std::result::Result<T, E>;
pub trait Expander {
type Error;
- fn expand_var(&mut self, v: BString) -> Res<BString, Self::Error>;
+ fn expand_var(&mut self, v: BString, default: Option<BString>) -> Res<BString, Self::Error>;
fn expand_cmd(&mut self, c: Ast<PostExpansion>) -> Res<BString, Self::Error>;
}
@@ -75,7 +75,17 @@ pub fn plain(x: &[u8]) -> StringPart {
}
pub fn var(x: &[u8]) -> StringPart {
- StringPart::Var(VarName { name: x.to_vec() })
+ StringPart::Var(Var {
+ name: VarName { name: x.to_vec() },
+ default: None,
+ })
+}
+
+pub fn var_default(x: &[u8], default: ExpString) -> StringPart {
+ StringPart::Var(Var {
+ name: VarName { name: x.to_vec() },
+ default: Some(default),
+ })
}
pub fn cmdp(x: Ast<PreExpansion>) -> StringPart {
@@ -150,10 +160,18 @@ impl CmdDisplay for StringPart {
items.as_slice().cdisplay(w)?;
write!(w, ")")
}
- StringPart::Var(var_name) => {
- write!(w, "var(")?;
- var_name.name.as_slice().cdisplay(w)?;
- write!(w, ")")
+ StringPart::Var(var) => {
+ if let Some(default) = &var.default {
+ write!(w, "var_default(")?;
+ var.name.name.as_slice().cdisplay(w)?;
+ write!(w, ",")?;
+ default.cdisplay(w)?;
+ write!(w, ")")
+ } else {
+ write!(w, "var(")?;
+ var.name.name.as_slice().cdisplay(w)?;
+ write!(w, ")")
+ }
}
StringPart::Cmd(ast) => {
write!(w, "cmdp(")?;
@@ -312,10 +330,16 @@ impl Pipes<PreExpansion> {
#[derive(Debug, Clone, PartialEq)]
pub enum StringPart {
Boring(BString),
- Var(VarName),
+ Var(Var),
Cmd(Ast<PreExpansion>),
}
+#[derive(Debug, Clone, PartialEq)]
+pub struct Var {
+ name: VarName,
+ default: Option<ExpString>,
+}
+
impl StringPart {
pub fn is_boring(&self) -> bool {
matches!(self, StringPart::Boring(..))
@@ -340,7 +364,13 @@ impl ExpString {
for part in self.parts.into_iter() {
let mut x = match part {
StringPart::Boring(items) => items,
- StringPart::Var(v) => e.expand_var(v.name)?,
+ StringPart::Var(v) => {
+ let default = match v.default {
+ Some(default) => Some(default.expand(e)?),
+ None => None,
+ };
+ e.expand_var(v.name.name, default)?
+ }
StringPart::Cmd(ast) => {
let exp = ast.expand(e)?;
e.expand_cmd(exp)?
@@ -483,18 +513,34 @@ impl Parse for ExpString {
if x == b'?' || x == b'!' {
b.adv();
- p.push(StringPart::Var(VarName { name: vec![x] }))
+ p.push(StringPart::Var(Var {
+ name: VarName { name: vec![x] },
+ default: None,
+ }))
} else if is_var_begin(x) {
let v = VarName::parse(b)?;
- p.push(StringPart::Var(v));
+ p.push(StringPart::Var(Var {
+ name: v,
+ default: None,
+ }));
} else if x == b'{' {
b.adv();
let v = VarName::parse(b)?;
+ let mut default = None;
if !b.has() {
return Err(ParseError::Eof);
} else if b.peek() == b':' {
- todo!(": in var expansion")
+ b.adv();
+ if !b.has() {
+ return Err(ParseError::Eof);
+ }
+ if b.peek() == b'-' {
+ b.adv();
+ default = Some(ExpString::parse(b)?);
+ } else {
+ todo!(": in var expansion")
+ }
}
if !b.has() {
@@ -504,7 +550,7 @@ impl Parse for ExpString {
}
b.adv();
- p.push(StringPart::Var(v));
+ p.push(StringPart::Var(Var { name: v, default }));
} else if x == b'(' {
b.adv();
let cmd = Ast::parse(b)?;
@@ -530,8 +576,11 @@ impl Parse for ExpString {
}
if delim == b' ' && x == b'~' {
- p.push(StringPart::Var(VarName {
- name: b"HOME".to_vec(),
+ p.push(StringPart::Var(Var {
+ name: VarName {
+ name: b"HOME".to_vec(),
+ },
+ default: None,
}));
} else {
add_char(p, x);
diff --git a/src/parse/test.rs b/src/parse/test.rs
index 28cf4a2..ca57e75 100644
--- a/src/parse/test.rs
+++ b/src/parse/test.rs
@@ -86,3 +86,11 @@ fn set_variable_in_fun() {
decl(estr(b"setter"), assign(estr(b"x"), estr(b"1"))),
);
}
+
+#[test]
+fn variable_with_defaults() {
+ parse_test(
+ parse(b"${x:-y}"),
+ pipes([cmd([str([var_default(b"x", estr(b"y"))])])]),
+ );
+}
diff --git a/src/run/mod.rs b/src/run/mod.rs
index c7414a8..e041066 100644
--- a/src/run/mod.rs
+++ b/src/run/mod.rs
@@ -305,7 +305,11 @@ impl Executor {
impl parse::Expander for Executor {
type Error = ExecError;
- fn expand_var(&mut self, var: BString) -> Result<BString, Self::Error> {
+ fn expand_var(
+ &mut self,
+ var: BString,
+ default: Option<BString>,
+ ) -> Result<BString, Self::Error> {
if var.is_empty() {
return Err(ExecError::UnknownVariable(var));
}
@@ -331,7 +335,10 @@ impl parse::Expander for Executor {
match std::env::var_os(OsStr::from_bytes(&var)) {
Some(val) => Ok(val.as_bytes().to_vec()),
- None => Err(ExecError::UnknownVariable(var)),
+ None => match default {
+ Some(d) => Ok(d),
+ None => Err(ExecError::UnknownVariable(var)),
+ },
}
}