diff options
| author | Jonas Maier <jonas@x77.dev> | 2026-03-05 23:54:56 +0100 |
|---|---|---|
| committer | Jonas Maier <jonas@x77.dev> | 2026-03-05 23:54:56 +0100 |
| commit | fb80e9c1cd4c2dcbb2d2ba1e2be8c7e19b9f0ce1 (patch) | |
| tree | e6980a3ae362de33f1521c647e18dad16b02363e /src/parse.rs | |
| parent | f03a0863ba3da7cf34e938a7de1cf92675b09c41 (diff) | |
| download | pish-fb80e9c1cd4c2dcbb2d2ba1e2be8c7e19b9f0ce1.tar.gz | |
very buggy beginning of tab completion
Diffstat (limited to 'src/parse.rs')
| -rw-r--r-- | src/parse.rs | 169 |
1 files changed, 134 insertions, 35 deletions
diff --git a/src/parse.rs b/src/parse.rs index 39ede4b..2da97c7 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,3 +1,5 @@ +use crate::BString; + #[derive(Debug)] pub enum Ast { AssignVar(AssignVar), @@ -36,45 +38,142 @@ pub enum ParseError { type Result<T> = std::result::Result<T, ParseError>; -pub fn do_parse(mut x: &[u8]) -> Result<Ast> { - Ast::parse(&mut x) +pub fn do_parse(x: &[u8]) -> Result<Ast> { + Ast::parse(&mut Cursor::new(x, ParseMode::Command)) } -trait Parse: Sized { - fn parse(b: &mut &[u8]) -> Result<Self>; +pub enum CompletionKind { + Command, + Argument, + None, } -#[inline(always)] -fn parse<T: Parse>(b: &mut &[u8]) -> Result<T> { - T::parse(b) +pub struct CompletionContext { + pub kind: CompletionKind, + pub partial: BString, } -fn spaces(b: &mut &[u8]) { - while let Some(b' ' | b'\t') = b.get(0) { - *b = &b[1..]; +pub fn completion_context<'a>(x: &'a [u8]) -> CompletionContext { + let mut cursor = Cursor::new(x, ParseMode::Completion); + let ast = Ast::parse(&mut cursor); + match ast { + Ok(Ast::Pipes(pipes)) if cursor.spaced == false => { + if let Some(cmd) = pipes.cmds.last() { + if cmd.args.is_empty() { + CompletionContext { + kind: CompletionKind::Command, + partial: cmd.cmd.clone(), + } + } else { + CompletionContext { + kind: CompletionKind::Argument, + partial: cmd.args[cmd.args.len() - 1].clone(), + } + } + } else { + CompletionContext { + kind: CompletionKind::None, + partial: Vec::new(), + } + } + } + _ => CompletionContext { + kind: CompletionKind::None, + partial: Vec::new(), + }, } } -#[inline(always)] -fn adv(b: &mut &[u8]) { - *b = &b[1..] +trait Parse: Sized { + fn parse(b: &mut Cursor<'_>) -> Result<Self>; +} + +enum ParseMode { + Command, + Completion, +} + +struct Cursor<'a> { + buf: &'a [u8], + mode: ParseMode, + + /// if the last byte that was consumed was whitespace or part of a word + spaced: bool, } -fn parse_quoted_string(b: &mut &[u8], delim: u8) -> Result<Vec<u8>> { +impl<'a> Cursor<'a> { + fn new(buf: &'a [u8], mode: ParseMode) -> Self { + Self { + buf, + mode, + spaced: false, + } + } + + // non empty + fn has(&self) -> bool { + !self.buf.is_empty() + } + + fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + fn peek(&self) -> u8 { + self.buf[0] + } + + fn adv(&mut self) -> u8 { + let out = self.buf[0]; + self.buf = &self.buf[1..]; + self.spaced = false; + out + } + + fn spaces(&mut self) { + while let Some(b' ' | b'\t') = self.buf.first() { + self.adv(); + self.spaced = true; + } + } + + fn is_completion(&self) -> bool { + match self.mode { + ParseMode::Completion => true, + _ => false, + } + } + + fn parse<T: Parse>(&mut self) -> Result<T> { + T::parse(self) + } +} + +fn parse_quoted_string(b: &mut Cursor<'_>, delim: u8) -> Result<Vec<u8>> { // TODO: escape sequence stuff let mut s = Vec::new(); - while b.len() > 0 { - if b[0] == delim { - adv(b); + 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[0]); - adv(b); + s.push(b.adv()); } - if delim == b' ' { + if delim == b' ' || b.is_completion() { Ok(s) } else { Err(ParseError::Incomplete) @@ -82,16 +181,16 @@ fn parse_quoted_string(b: &mut &[u8], delim: u8) -> Result<Vec<u8>> { } impl Parse for Vec<u8> { - fn parse(b: &mut &[u8]) -> Result<Self> { - spaces(b); + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + b.spaces(); if b.is_empty() { return Err(ParseError::Eof); } - let c = b[0]; + let c = b.peek(); if c == b'|' { Err(ParseError::UnexpectedPipe) } else if c == b'\'' || c == b'"' { - adv(b); + b.adv(); parse_quoted_string(b, c) } else if c.is_ascii_graphic() { parse_quoted_string(b, b' ') @@ -102,17 +201,17 @@ impl Parse for Vec<u8> { } impl Parse for Ast { - fn parse(b: &mut &[u8]) -> Result<Self> { - Ok(Self::Pipes(parse(b)?)) + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + Ok(Self::Pipes(b.parse()?)) } } impl Parse for Command { - fn parse(b: &mut &[u8]) -> Result<Self> { - let path: Vec<u8> = parse(b)?; + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + let path: Vec<u8> = b.parse()?; let mut args = Vec::new(); loop { - let arg: Result<Vec<u8>> = parse(b); + let arg: Result<Vec<u8>> = b.parse(); match arg { Ok(arg) => args.push(arg), Err(ParseError::Eof | ParseError::UnexpectedPipe) => break, @@ -124,19 +223,19 @@ impl Parse for Command { } impl Parse for Pipes { - fn parse(b: &mut &[u8]) -> Result<Self> { - let mut cmds: Vec<Command> = vec![parse(b)?]; + fn parse(b: &mut Cursor<'_>) -> Result<Self> { + let mut cmds: Vec<Command> = vec![b.parse()?]; loop { - spaces(b); + b.spaces(); if b.is_empty() { return Ok(Pipes { cmds }); } - let c = b[0]; + let c = b.peek(); if c == b'|' { - adv(b); - cmds.push(parse(b)?); + b.adv(); + cmds.push(b.parse()?); } else { Err(ParseError::Unknown(c))?; } |
