diff options
| author | Jonas Maier <> | 2026-03-07 13:04:22 +0100 |
|---|---|---|
| committer | Jonas Maier <> | 2026-03-07 13:04:22 +0100 |
| commit | 4bfeec396b4f9e727587064de6c12942f8c6afff (patch) | |
| tree | 40116c5ef277360017dfd83268dda5d92e87ae67 | |
| parent | 6504d8c244af90fad76d101406a47a1ac4b22e22 (diff) | |
| download | pish-4bfeec396b4f9e727587064de6c12942f8c6afff.tar.gz | |
can parse concatenated quotes
| -rw-r--r-- | src/parse/mod.rs | 163 | ||||
| -rw-r--r-- | src/parse/test.rs | 8 |
2 files changed, 97 insertions, 74 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index f7aa364..7a514e5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -3,7 +3,7 @@ use crate::BString; #[cfg(test)] mod test; -pub trait Stage : PartialEq { +pub trait Stage: PartialEq { type Str: std::fmt::Debug + Clone + PartialEq; } @@ -154,7 +154,7 @@ impl CmdDisplay for StringPart { write!(w, "var(")?; var_name.name.as_slice().cdisplay(w)?; write!(w, ")") - }, + } StringPart::Cmd(ast) => { write!(w, "cmdp(")?; ast.cdisplay(w)?; @@ -402,115 +402,130 @@ impl Parse for ExpString { return Err(ParseError::NotAString); } - let mut delim = b.peek(); - if delim == b'\'' || delim == b'"' { - b.adv(); - } else if is_symbol(delim) && delim != b'$' { - return Err(ParseError::NotAString); - } else { - delim = b' '; - } - let mut parts = Vec::new(); let p = &mut parts; let mut escaping = false; - let add_char = |p: &mut Vec<StringPart>, x: u8| match p.last_mut() { Some(StringPart::Boring(v)) => v.push(x), _ => p.push(StringPart::Boring(vec![x])), }; + let mut already_parsed = false; - while b.has() { - let x = b.peek(); - - if escaping { - add_char(p, x); - escaping = false; + 'cont: while b.has() { + let mut delim = b.peek(); + if delim == b'\'' || delim == b'"' { b.adv(); - continue; + } else if is_symbol(delim) && delim != b'$' { + return if already_parsed { + Ok(Self { parts }) + } else { + Err(ParseError::NotAString) + }; + } else { + delim = b' '; } - if x == delim || (b.peek_space() && delim == b' ') { - if delim != b' ' { + while b.has() { + let x = b.peek(); + + if escaping { + add_char(p, x); + escaping = false; b.adv(); + continue; } - return Ok(Self { parts }); - } - - if delim == b' ' && is_symbol(x) && x != b'$' { - return Ok(Self { parts }); - } - b.adv(); + if delim == b' ' && (x.is_ascii_whitespace() || (is_symbol(x) && x != b'$')) { + if x == b'\'' || x == b'"' { + break; + } else { + return Ok(Self { parts }); + } + } - if delim == b'\'' { - // no fancy stuff here - add_char(p, x); - continue; - } + if x == delim { + b.adv(); + already_parsed = true; + continue 'cont; + } - if x == b'\\' { - escaping = true; - continue; - } + b.adv(); - if x == b'$' { - if !b.has() { + if delim == b'\'' { + // no fancy stuff here add_char(p, x); continue; } - let x = b.peek(); - - if x == b'?' || x == b'!' { - b.adv(); - p.push(StringPart::Var(VarName { name: vec![x] })) - } else if is_var_begin(x) { - let v = VarName::parse(b)?; - p.push(StringPart::Var(v)); - } else if x == b'{' { - b.adv(); - let v = VarName::parse(b)?; + if x == b'\\' { + escaping = true; + continue; + } + if x == b'$' { if !b.has() { - return Err(ParseError::Eof); - } else if b.peek() == b':' { - todo!(": in var expansion") + add_char(p, x); + continue; } - if !b.has() { - return Err(ParseError::Eof); - } else if b.peek() != b'}' { - return Err(ParseError::Incomplete); - } + let x = b.peek(); + + if x == b'?' || x == b'!' { + b.adv(); + p.push(StringPart::Var(VarName { name: vec![x] })) + } else if is_var_begin(x) { + let v = VarName::parse(b)?; + p.push(StringPart::Var(v)); + } else if x == b'{' { + b.adv(); + let v = VarName::parse(b)?; + + if !b.has() { + return Err(ParseError::Eof); + } else if b.peek() == b':' { + todo!(": in var expansion") + } + + if !b.has() { + return Err(ParseError::Eof); + } else if b.peek() != b'}' { + return Err(ParseError::Incomplete); + } - b.adv(); - p.push(StringPart::Var(v)); - } else if x == b'(' { - b.adv(); - let cmd = Ast::parse(b)?; - b.spaces(); - if b.is_empty() { - return Err(ParseError::Eof); - } else if b.peek() == b')' { b.adv(); - p.push(StringPart::Cmd(cmd)); + p.push(StringPart::Var(v)); + } else if x == b'(' { + b.adv(); + let cmd = Ast::parse(b)?; + b.spaces(); + if b.is_empty() { + return Err(ParseError::Eof); + } else if b.peek() == b')' { + b.adv(); + p.push(StringPart::Cmd(cmd)); + } else { + return Err(ParseError::Expected(')')); + } } else { - return Err(ParseError::Expected(')')); + // doesn't seem to be a variable or expansion, just add $ back into the string + add_char(p, b'$'); + continue; } - } else { - // doesn't seem to be a variable or expansion, just add $ back into the string - add_char(p, b'$'); + continue; } + add_char(p, x); + } + + if b.has() && b"\"'".contains(&b.peek()) { continue; } - add_char(p, x); + break; } - if b.is_completion() || delim == b' ' { + if b.is_completion() || already_parsed { Ok(Self { parts }) } else { Err(ParseError::Eof) diff --git a/src/parse/test.rs b/src/parse/test.rs index 9352cfd..9a7e359 100644 --- a/src/parse/test.rs +++ b/src/parse/test.rs @@ -26,3 +26,11 @@ fn command_interp() { ])]))])])]), ) } + +#[test] +fn string_concat() { + parse_test( + parse(br#" foo'bar'"baz" "#), + pipes([cmd([estr(b"foobarbaz")])]), + ); +} |
