aboutsummaryrefslogtreecommitdiffstats
path: root/src/parse/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse/mod.rs')
-rw-r--r--src/parse/mod.rs99
1 files changed, 90 insertions, 9 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index e061d67..02012dc 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -98,14 +98,14 @@ pub fn pipes<const N: usize>(cmds: [Command<PreExpansion>; N]) -> Ast<PreExpansi
pub fn estr(x: &[u8]) -> ExpString {
ExpString {
parts: vec![StringPart::Boring(x.to_vec())],
- delim: b' ',
+ delim: StringDelimiter::None,
}
}
pub fn str<const N: usize>(parts: [StringPart; N]) -> ExpString {
ExpString {
parts: parts.to_vec(),
- delim: b' ',
+ delim: StringDelimiter::None,
}
}
@@ -480,7 +480,7 @@ impl StringPart {
/// `"hi ${var} $(cmd) "` gets mapped to `[Boring("hi "), Var("var"), String(" "), Cmd(...), Boring(" ")]`
pub struct ExpString {
parts: Vec<StringPart>,
- delim: u8,
+ delim: StringDelimiter,
}
impl ExpString {
@@ -559,8 +559,8 @@ impl Parse for VarName {
}
}
-#[derive(Clone, Debug)]
-enum StringDelimiter {
+#[derive(Clone, Debug, PartialEq)]
+pub enum StringDelimiter {
/// no delimiter, i.e. when parsing a simple command like `echo foo`
None,
@@ -593,6 +593,18 @@ enum StringDelimiter {
StrictCustom(BString),
}
+trait PushAll {
+ fn push_all(&mut self, other: &bstr);
+}
+
+impl PushAll for BString {
+ fn push_all(&mut self, other: &bstr) {
+ for &c in other {
+ self.push(c);
+ }
+ }
+}
+
/// gets the largest ident this slice starts with, might be empty
fn peek_ident(b: &[u8]) -> &[u8] {
if b.is_empty() || !b[0].is_ascii_alphabetic() {
@@ -698,6 +710,61 @@ impl StringDelimiter {
fn is_none(&self) -> bool {
matches!(self, Self::None)
}
+
+ /// assuming that `s` will be placed in the middle of some specifically delimited string
+ pub fn escape(&self, mut s: &bstr, out: &mut BString) {
+ while !s.is_empty() {
+ let first = s[0];
+ match self {
+ StringDelimiter::None => {
+ if matches!(first, b' ' | b'$' | b'\\' | b'\'' | b'"') {
+ out.push(b'\\');
+ }
+ }
+ StringDelimiter::Interp | StringDelimiter::InterpCustom(_) => {
+ if matches!(first, b'$' | b'\\' | b'"') {
+ out.push(b'\\');
+ }
+ }
+ StringDelimiter::Strict => {
+ if first == b'\'' {
+ out.push_all(b"'\\'");
+ }
+ }
+ StringDelimiter::StrictCustom(delim) => {
+ if s.starts_with(b"'''") && s[3..].starts_with(delim) {
+ out.push_all(b"'''");
+ out.push_all(delim);
+ out.push_all(b"\\'\\'\\'");
+ out.push_all(delim);
+ out.push_all(b"''");
+ out.push_all(delim);
+ out.push_all(b"'''");
+ s = &s[3 + delim.len()..];
+ continue;
+ }
+ }
+ }
+ out.push(first);
+ s = &s[1..];
+ }
+ }
+
+ pub fn write_closing_delimiter(&self, out: &mut BString) {
+ match self {
+ StringDelimiter::None => (),
+ StringDelimiter::Interp => out.push(b'"'),
+ StringDelimiter::Strict => out.push(b'\''),
+ StringDelimiter::InterpCustom(delim) => {
+ out.push_all(b"\"\"\"");
+ out.push_all(&delim);
+ }
+ StringDelimiter::StrictCustom(delim) => {
+ out.push_all(b"'''");
+ out.push_all(&delim);
+ }
+ }
+ }
}
fn parse_escape_code(b: &mut Cursor<'_>) -> Result<Option<u8>> {
@@ -752,11 +819,15 @@ impl Parse for ExpString {
let mut already_parsed = false;
+ let mut last_delim = StringDelimiter::None;
+
'outer: loop {
let Some(delim) = StringDelimiter::try_begin(b) else {
break;
};
+ last_delim = delim.clone();
+
already_parsed = true;
while !delim.try_end(b) {
@@ -879,8 +950,11 @@ impl Parse for ExpString {
}
}
- if b.is_completion() || already_parsed {
- Ok(Self { parts, delim: b' ' })
+ if already_parsed {
+ Ok(Self {
+ parts,
+ delim: last_delim,
+ })
} else {
Err(ParseError::NotAString)
}
@@ -978,7 +1052,7 @@ pub fn do_parse(x: &[u8]) -> Res<Ast<PreExpansion>, (ParseError, &[u8])> {
}
}
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
pub enum CompletionKind {
Command,
PathCommand,
@@ -990,6 +1064,7 @@ pub enum CompletionKind {
pub struct CompletionContext {
pub kind: CompletionKind,
pub partial: BString,
+ pub delim: StringDelimiter,
}
impl CompletionContext {
@@ -997,6 +1072,7 @@ impl CompletionContext {
Self {
kind: CompletionKind::None,
partial: BString::new(),
+ delim: StringDelimiter::None,
}
}
}
@@ -1029,6 +1105,7 @@ impl ExpString {
CompletionContext {
kind: CompletionKind::Variable,
partial: var.name.name.clone(),
+ delim: self.delim.clone(),
}
} else if let Some(StringPart::Cmd(cmd)) = self.parts.last()
&& !cmd.already_complete
@@ -1038,7 +1115,11 @@ impl ExpString {
if s.contains(&b'/') && kind == CompletionKind::Command {
kind = CompletionKind::PathCommand;
}
- CompletionContext { kind, partial: s }
+ CompletionContext {
+ kind,
+ partial: s,
+ delim: self.delim.clone(),
+ }
} else {
CompletionContext::none()
}