From 2e55d27f565ec96c7238b35191ae5c65c2b6bc57 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Fri, 6 Mar 2026 18:49:33 +0100 Subject: shell expansion somewhat working (?) --- src/parse.rs | 132 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 63 insertions(+), 69 deletions(-) (limited to 'src/parse.rs') diff --git a/src/parse.rs b/src/parse.rs index 2ed0dbf..b2433a8 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -106,7 +106,7 @@ impl ExpString { fn is_symbol(x: u8) -> bool { match x { - b'|' | b'{' | b'}' | b'$' => true, + b'|' | b'{' | b'}' | b'$' | b'(' | b')' | b'\'' | b'"' => true, _ => false, } } @@ -152,14 +152,16 @@ impl Parse for ExpString { fn parse(b: &mut Cursor<'_>) -> Result { b.spaces(); if b.is_empty() { - return Err(ParseError::Eof); + return Err(ParseError::NotAString); } let mut delim = b.peek(); - if delim != b'\'' && delim != b'"' { - delim = b' '; - } else { + 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(); @@ -181,13 +183,17 @@ impl Parse for ExpString { continue; } - if x == delim { + if x == delim || (b.peek_space() && delim == b' ') { if delim != b' ' { b.adv(); } return Ok(Self { parts }); } + if delim == b' ' && is_symbol(x) && x != b'$' { + return Ok(Self { parts }); + } + b.adv(); if delim == b'\'' { @@ -231,7 +237,16 @@ impl Parse for ExpString { b.adv(); p.push(StringPart::Var(v)); } else if x == b'(' { - todo!() + b.adv(); + let cmd = Ast::parse(b)?; + b.spaces(); + if b.is_empty() { + return Err(ParseError::Eof); + } else if b.peek() == b')' { + p.push(StringPart::Cmd(cmd)); + } else { + return Err(ParseError::Expected(')')); + } } else { // doesn't seem to be a variable or expansion, just add $ back into the string add_char(p, b'$'); @@ -241,10 +256,6 @@ impl Parse for ExpString { continue; } - if delim == b' ' && is_symbol(x) { - return Ok(Self { parts }); - } - add_char(p, x); } @@ -286,12 +297,20 @@ pub enum ParseError { ExpectedAlphabetic, Unknown(u8), + + Expected(char), + + NotAString, } type Result = std::result::Result; -pub fn do_parse(x: &[u8]) -> Result> { - Ast::parse(&mut Cursor::new(x, ParseMode::Command)) +pub fn do_parse(x: &[u8]) -> Res, (ParseError, &[u8])> { + let mut c = Cursor::new(x, ParseMode::Command); + match Ast::parse(&mut c) { + Ok(ast) => Ok(ast), + Err(e) => Err((e, c.buf)), + } } pub enum CompletionKind { @@ -359,6 +378,8 @@ struct Cursor<'a> { /// if the last byte that was consumed was whitespace or part of a word spaced: bool, + + backtrace: bool, } impl<'a> Cursor<'a> { @@ -367,6 +388,7 @@ impl<'a> Cursor<'a> { buf, mode, spaced: false, + backtrace: false, } } @@ -379,19 +401,40 @@ impl<'a> Cursor<'a> { self.buf.is_empty() } + fn bt(&self, word: &str) { + if self.backtrace { + let bt = std::backtrace::Backtrace::capture(); + let bt = format!("{bt}"); + println!("{word} {}\r", self.buf[0] as char); + for l in bt.lines().skip(4).take(2) { + println!("{l}\r"); + } + println!("\r"); + } + } + fn peek(&self) -> u8 { + self.bt("peek"); self.buf[0] } fn adv(&mut self) -> u8 { + self.bt("adv"); let out = self.buf[0]; self.buf = &self.buf[1..]; self.spaced = false; out } + fn peek_space(&self) -> bool { + if self.buf.is_empty() { + return false; + } + matches!(self.buf[0], b' ' | b'\t' | b'\n' | b'\r') + } + fn spaces(&mut self) { - while let Some(b' ' | b'\t') = self.buf.first() { + while self.peek_space() { self.adv(); self.spaced = true; } @@ -409,57 +452,6 @@ impl<'a> Cursor<'a> { } } -fn parse_quoted_string(b: &mut Cursor<'_>, delim: u8) -> Result> { - // TODO: escape sequence stuff - - let mut s = Vec::new(); - while b.has() { - if delim == b' ' && b.peek() == b'|' { - return if s.len() == 0 { - Err(ParseError::UnexpectedPipe) - } else { - Ok(s) - }; - } - - if b.peek() == delim { - b.adv(); - if delim == b' ' { - b.spaced = true; - } - return Ok(s); - } - - s.push(b.adv()); - } - - if delim == b' ' || b.is_completion() { - Ok(s) - } else { - Err(ParseError::Incomplete) - } -} - -impl Parse for Vec { - fn parse(b: &mut Cursor<'_>) -> Result { - b.spaces(); - if b.is_empty() { - return Err(ParseError::Eof); - } - let c = b.peek(); - if c == b'|' { - Err(ParseError::UnexpectedPipe) - } else if c == b'\'' || c == b'"' { - b.adv(); - parse_quoted_string(b, c) - } else if c.is_ascii_graphic() { - parse_quoted_string(b, b' ') - } else { - Err(ParseError::Unknown(c)) - } - } -} - impl Parse for Ast { fn parse(b: &mut Cursor<'_>) -> Result { Ok(Self::Pipes(b.parse()?)) @@ -471,14 +463,14 @@ impl Parse for Command { let path: ExpString = b.parse()?; let mut args = Vec::new(); loop { - let arg: Result = b.parse(); - match arg { + match ExpString::parse(b) { Ok(arg) => args.push(arg), - Err(ParseError::Eof | ParseError::UnexpectedPipe) => break, + Err(ParseError::NotAString) => break, Err(e) => Err(e)?, } } - Ok(Self { cmd: path, args }) + let x = Ok(Self { cmd: path, args }); + x } } @@ -496,6 +488,8 @@ impl Parse for Pipes { if c == b'|' { b.adv(); cmds.push(b.parse()?); + } else if is_symbol(c) { + return Ok(Pipes { cmds }); } else { Err(ParseError::Unknown(c))?; } -- cgit v1.2.3