aboutsummaryrefslogtreecommitdiffstats
path: root/src/parse.rs
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-03-05 23:54:56 +0100
committerJonas Maier <jonas@x77.dev>2026-03-05 23:54:56 +0100
commitfb80e9c1cd4c2dcbb2d2ba1e2be8c7e19b9f0ce1 (patch)
treee6980a3ae362de33f1521c647e18dad16b02363e /src/parse.rs
parentf03a0863ba3da7cf34e938a7de1cf92675b09c41 (diff)
downloadpish-fb80e9c1cd4c2dcbb2d2ba1e2be8c7e19b9f0ce1.tar.gz
very buggy beginning of tab completion
Diffstat (limited to 'src/parse.rs')
-rw-r--r--src/parse.rs169
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))?;
}