aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJonas Maier <>2026-05-22 17:44:24 +0200
committerJonas Maier <>2026-05-22 17:44:24 +0200
commit07daff9331dbdc607584edbf1a8fb3e415c338ea (patch)
tree4742c87aba7748652bd8ea9da7be01607d4d1555 /src
parent9a727206f1c94f7580e79a5355adc4b932ae6345 (diff)
downloadpish-07daff9331dbdc607584edbf1a8fb3e415c338ea.tar.gz
lenient block parsing for completion
Diffstat (limited to 'src')
-rw-r--r--src/parse/mod.rs85
-rw-r--r--src/run/builtin.rs34
2 files changed, 80 insertions, 39 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index ffbdfe5..102b334 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -403,13 +403,8 @@ pub struct FunDecl<S: Stage> {
impl Parse for Block {
fn parse(b: &mut Cursor<'_>) -> Result<Self> {
let mut commands = Vec::new();
- b.spaces();
- if b.has() && b.peek() == b'{' {
- b.adv();
- } else {
- return Err(ParseError::NotABlock);
- }
+ b.consume_keyword(Keyword::OpenBrace)?;
loop {
while {
@@ -419,7 +414,7 @@ impl Parse for Block {
b.adv();
}
- if b.has() && b"})".contains(&b.peek()) {
+ if b.has() && b"})".contains(&b.peek()) || b.is_completion() && b.is_empty() {
break;
}
@@ -427,16 +422,10 @@ impl Parse for Block {
commands.push(cmd);
}
- let finished_parsing = if b.has() {
- if b.peek() != b'}' {
- return Err(ParseError::Expected('}'));
- }
- b.adv();
- true
- } else if !b.is_completion() {
- return Err(ParseError::Expected('}'));
- } else {
- false
+ let finished_parsing = match b.consume_keyword(Keyword::CloseBrace) {
+ Ok(_) => true,
+ Err(_) if b.is_completion() => false,
+ Err(e) => Err(e)?,
};
Ok(Self {
@@ -1290,7 +1279,8 @@ pub trait Parse: Sized {
}
}
-enum ParseMode {
+#[derive(Copy, Clone)]
+pub enum ParseMode {
Command,
Completion,
}
@@ -1302,7 +1292,7 @@ pub struct Cursor<'a> {
/// if the last byte that was consumed was whitespace or part of a word
spaced: bool,
- backtrace: bool,
+ pub backtrace: bool,
}
#[derive(Default)]
@@ -1314,7 +1304,7 @@ struct SpaceStats {
}
impl<'a> Cursor<'a> {
- fn new(buf: &'a [u8], mode: ParseMode) -> Self {
+ pub fn new(buf: &'a [u8], mode: ParseMode) -> Self {
Self {
buf,
mode,
@@ -1323,6 +1313,10 @@ impl<'a> Cursor<'a> {
}
}
+ pub fn remaining(&self) -> &[u8] {
+ self.buf
+ }
+
// non empty
fn has(&self) -> bool {
!self.buf.is_empty()
@@ -1336,8 +1330,12 @@ impl<'a> Cursor<'a> {
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) {
+ if self.buf.is_empty() {
+ println!("{word} <EOF>\r");
+ } else {
+ println!("{word} {}\r", self.buf[0] as char);
+ }
+ for l in bt.lines().skip(4).take(8) {
println!("{l}\r");
}
println!("\r");
@@ -1413,20 +1411,30 @@ impl<'a> Cursor<'a> {
}
fn consume_keyword(&mut self, kw: Keyword) -> Result<()> {
- self.spaces();
let bytes = kw.as_bytes();
+
+ if self.backtrace {
+ self.bt(&format!("keyword {kw:?}"));
+ }
+
+ self.spaces();
if self.buf.starts_with(bytes) {
- if kw.requires_space() && self.buf.len() > bytes.len() {
- if self.buf[bytes.len()].is_ascii_whitespace() {
+ if kw.requires_space() {
+ if self.buf.len() > bytes.len() && self.buf[bytes.len()].is_ascii_whitespace() {
self.buf = &self.buf[bytes.len() + 1..];
self.spaces();
Ok(())
} else {
- self.buf = &self.buf[bytes.len()..];
- self.spaces();
- Err(ParseError::ExpectedKeyword(kw))
+ if self.is_completion() {
+ self.buf = &self.buf[bytes.len()..];
+ Ok(())
+ } else {
+ Err(ParseError::ExpectedKeyword(kw))
+ }
}
} else {
+ self.buf = &self.buf[bytes.len()..];
+ self.spaces();
Ok(())
}
} else {
@@ -1441,6 +1449,8 @@ pub enum Keyword {
While,
Else,
Elif,
+ OpenBrace,
+ CloseBrace,
}
impl Keyword {
@@ -1450,6 +1460,8 @@ impl Keyword {
Keyword::While => b"while",
Keyword::Elif => b"elif",
Keyword::Else => b"else",
+ Keyword::OpenBrace => b"{",
+ Keyword::CloseBrace => b"}",
}
}
@@ -1459,13 +1471,15 @@ impl Keyword {
Keyword::While => true,
Keyword::Elif => true,
Keyword::Else => false,
+ Keyword::OpenBrace => false,
+ Keyword::CloseBrace => false,
}
}
}
-impl Parse for If<PreExpansion> {
- fn parse(b: &mut Cursor<'_>) -> Result<Self> {
- b.consume_keyword(Keyword::If)?;
+impl If<PreExpansion> {
+ fn parse_internal(b: &mut Cursor<'_>, first_keyword: Keyword) -> Result<Self> {
+ b.consume_keyword(first_keyword)?;
let mut res = If {
condition: Pipes::parse(b)?,
@@ -1493,8 +1507,7 @@ impl Parse for If<PreExpansion> {
res.false_block = if b.consume_keyword(Keyword::Else).is_ok() {
Block::parse(b)?
- } else if b.consume_keyword(Keyword::Elif).is_ok() {
- let elif = If::parse(b)?;
+ } else if let Ok(elif) = Self::parse_internal(b, Keyword::Elif) {
Block {
finished_parsing: elif.parse_progress.is_done(),
commands: vec![Ast::If(elif)],
@@ -1512,6 +1525,12 @@ impl Parse for If<PreExpansion> {
}
}
+impl Parse for If<PreExpansion> {
+ fn parse(b: &mut Cursor<'_>) -> Result<Self> {
+ Self::parse_internal(b, Keyword::If)
+ }
+}
+
impl Parse for While {
fn parse(b: &mut Cursor<'_>) -> Result<Self> {
b.spaces();
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
index 488ee04..638cfcd 100644
--- a/src/run/builtin.rs
+++ b/src/run/builtin.rs
@@ -6,7 +6,6 @@ use std::{env::*, fs::OpenOptions, path::PathBuf};
use pish_derive::FromArgs;
use super::{Builtin, BuiltinError as Error, BuiltinResult as Result};
-use crate::parse::CmdDisplay;
use crate::run::{AliasAge, AliasBody};
use crate::*;
@@ -354,16 +353,39 @@ impl Builtin for parse {
_stdin: &mut dyn Read,
stdout: &mut dyn Write,
) -> Result {
+ use crate::parse::*;
+
+ let mut parse_mode = ParseMode::Command;
+ let mut backtrace = false;
+
for arg in args {
- match crate::parse::do_parse(arg) {
+ match &arg[..] {
+ b"-c" | b"--completion" => {
+ parse_mode = ParseMode::Completion;
+ continue;
+ }
+ b"-e" | b"--execution" => {
+ parse_mode = ParseMode::Command;
+ continue;
+ }
+ b"--bt" | b"--backtrace" => {
+ backtrace = !backtrace;
+ }
+ _ => {}
+ }
+
+ let mut c = crate::parse::Cursor::new(arg, parse_mode);
+ c.backtrace = backtrace;
+
+ match Ast::parse(&mut c) {
Ok(parsed) => {
write!(stdout, "ok ")?;
parsed.cdisplay(stdout)?;
- writeln!(stdout)?;
+ writeln!(stdout, "\n{parsed:?}")?;
}
- Err(err) => {
- writeln!(stdout, "err {:?} ", err.0)?;
- stdout.write_all(&err.1)?;
+ Err(e) => {
+ writeln!(stdout, "err {e:?} ")?;
+ stdout.write_all(&c.remaining())?;
}
}
}