From 40387917c8ba4703aee111e1cf37dcd793e39ba2 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Fri, 6 Mar 2026 16:01:15 +0100 Subject: partial shell string parser --- src/parse.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) (limited to 'src/parse.rs') diff --git a/src/parse.rs b/src/parse.rs index 2da97c7..a685f5d 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -17,10 +17,94 @@ pub struct Pipes { pub cmds: Vec, } +pub enum StringPart { + Boring(BString), + Var(BString), + Cmd(Ast), +} + +/// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]` +pub struct ShellString { + parts: Vec, +} + +fn is_symbol(x: u8) -> bool { + match x { + b'|' => true, + _ => false, + } +} + +impl Parse for ShellString { + fn parse(b: &mut Cursor<'_>) -> Result { + b.spaces(); + if b.is_empty() { + return Err(ParseError::Eof); + } + + let mut delim = b.peek(); + if delim != b'\'' && delim != b'"' { + delim = b' '; + } else { + b.adv(); + } + + let mut parts = Vec::new(); + let p = &mut parts; + let mut escaping = false; + + let add_char = |p: &mut Vec, x: u8| match p.last_mut() { + Some(StringPart::Boring(v)) => v.push(x), + _ => p.push(StringPart::Boring(vec![x])), + }; + + while b.has() { + let x = b.peek(); + + if escaping { + add_char(p, x); + escaping = false; + b.adv(); + continue; + } + + if x == delim { + if delim != b' ' { + b.adv(); + } + return Ok(Self { parts }); + } + + b.adv(); + + if x == b'\\' { + escaping = true; + continue; + } + + if x == b'$' { + todo!() + } + + if delim == b' ' && is_symbol(x) { + return Ok(Self { parts }); + } + + add_char(p, x); + } + + if b.is_completion() { + Ok(Self { parts }) + } else { + Err(ParseError::Eof) + } + } +} + #[derive(Debug)] pub struct Command { - pub cmd: Vec, - pub args: Vec>, + pub cmd: BString, + pub args: Vec, } #[derive(Debug)] -- cgit v1.2.3