From ac8733ec1a47bb6bffeb3b1db6adf0a10a7b87a6 Mon Sep 17 00:00:00 2001 From: Jonas Maier Date: Fri, 8 May 2026 09:45:32 +0200 Subject: if statement parsing --- src/main.rs | 7 ++-- src/parse/mod.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/run/mod.rs | 28 ++++++++++++++- 3 files changed, 134 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index e10a8e3..a82eea6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,6 +142,7 @@ impl Session { self.pretty_cwd_res().unwrap_or_else(|_| String::new()) } + // TODO: prompt should be BString as well fn prompt(&self) -> String { #[cfg(debug_assertions)] let dev = "dev "; @@ -346,15 +347,11 @@ impl Session { if !line.is_empty() { let parsed = match parse::do_parse(&line) { Ok(p) => p, - Err((crate::parse::ParseError::Eof, _)) => { + Err(_) => { se.line.add(b'\n'); print!("\r\n> "); return; } - Err(e) => { - println!("{e:?}\n{}", se.prompt()); - return; - } }; print!("\r\n"); let entry = HistoryEntry::new(line.clone()); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b908441..8cc6a4f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -79,6 +79,35 @@ pub enum Ast { FunDecl(FunDecl), VarAssign(VarAssign), Pipes(Pipes), + If(If), +} + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq)] +enum IfParseProgress { + Condition, + TrueBlock, + FalseBlock, + Done, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct If { + pub condition: Pipes, + pub true_block: Block, + pub false_block: Block, + parse_progress: IfParseProgress, +} + +impl If { + fn expand(self, e: &mut E) -> Res, E::Error> { + Ok(If { + condition: self.condition.expand(e)?, + true_block: self.true_block, + false_block: self.false_block, + parse_progress: self.parse_progress, + }) + } } pub fn decl(name: ExpString, body: Block) -> Ast { @@ -189,6 +218,15 @@ impl CmdDisplay for Ast { } write!(w, "])")?; } + Ast::If(i) => { + write!(w, "cond(")?; + Ast::Pipes(i.condition.clone()).cdisplay(w)?; + write!(w, ", ")?; + i.true_block.cdisplay(w)?; + write!(w, ", ")?; + i.false_block.cdisplay(w)?; + write!(w, ")")?; + } } Ok(()) } @@ -273,6 +311,7 @@ impl Ast { Ast::VarAssign(va) => Ok(Ast::VarAssign(va.expand(e)?)), Ast::Pipes(pipes) => Ok(Ast::Pipes(pipes.expand(e)?)), Ast::FunDecl(fd) => Ok(Ast::FunDecl(fd.expand(e)?)), + Ast::If(i) => Ok(Ast::If(i.expand(e)?)), } } } @@ -348,6 +387,7 @@ impl Parse for Block { if b.peek() != b'}' { return Err(ParseError::Expected('}')); } + b.adv(); } else if !b.is_completion() { return Err(ParseError::Expected('}')); } @@ -1059,6 +1099,8 @@ pub enum ParseError { NotHexDigit, NotABlock, + + NotAnIf, } type Result = std::result::Result; @@ -1104,6 +1146,12 @@ impl Block { CompletionContext::none() } } + + fn empty() -> Self { + Self { + commands: Vec::with_capacity(0), + } + } } impl Ast { @@ -1112,6 +1160,18 @@ impl Ast { Ast::FunDecl(fd) => fd.body.completion(e), Ast::VarAssign(va) => va.val.completion(e, CompletionKind::Argument), Ast::Pipes(p) => p.completion(e), + Ast::If(i) => i.completion(e), + } + } +} + +impl If { + fn completion(&self, e: &mut E) -> CompletionContext { + match self.parse_progress { + IfParseProgress::Condition => self.condition.completion(e), + IfParseProgress::TrueBlock => self.true_block.completion(e), + IfParseProgress::FalseBlock => self.false_block.completion(e), + IfParseProgress::Done => CompletionContext::none(), } } } @@ -1309,10 +1369,55 @@ impl<'a> Cursor<'a> { } } +impl Parse for If { + fn parse(b: &mut Cursor<'_>) -> Result { + b.spaces(); + if !b.buf.starts_with(b"if ") || b.buf.starts_with(b"if\t") { + return Err(ParseError::NotAnIf); + } + b.advance(3); + b.spaces(); + let condition = Pipes::parse(b)?; + let true_block = Block::parse(b)?; + + b.spaces(); + + let false_block = if b.buf.starts_with(b"else") { + b.advance(4); + Block::parse(b)? + } else if b.buf.starts_with(b"elif") { + b.advance(2); + let elif = If::parse(b)?; + Block { + commands: vec![Ast::If(elif)], + } + } else { + Block::empty() + }; + + Ok(If { + condition, + true_block, + false_block, + + // TODO: for completion one should allow more lenient parsing and set this appropriately then + parse_progress: IfParseProgress::Done, + }) + } +} + impl Parse for Ast { fn parse(b: &mut Cursor<'_>) -> Result { b.spaces(); + let orig_len = b.buf.len(); + let x = If::parse(b); + if let Ok(cond) = x { + return Ok(Self::If(cond)); + } else if b.buf.len() != orig_len { + x?; + } + let orig_len = b.buf.len(); let x = VarAssign::parse(b); if let Ok(va) = x { diff --git a/src/run/mod.rs b/src/run/mod.rs index 459c898..106e22c 100644 --- a/src/run/mod.rs +++ b/src/run/mod.rs @@ -319,6 +319,7 @@ impl Executor { Ast::FunDecl(fd) => self.execute_fun_decl(fd), Ast::VarAssign(va) => self.execute_var_assign(va), Ast::Pipes(pipes) => self.execute_pipeline(pipes, stdin, stdout), + Ast::If(cond) => self.execute_if(cond, stdin, stdout), } } @@ -345,6 +346,31 @@ impl Executor { let cmd = this.execute_pipeline(parse::Pipes { cmds: vec![cmd] }, stdin, stdout); this.exec_loop(cmd, &mut [c1, c2]) } + + fn execute_if( + &mut self, + cond: parse::If, + stdin: InputReader, + stdout: OutputWriter, + ) -> SpawnedCmd { + let mut this = self.clone(); + let handle = wait::spawn(move || -> Result<(), ExecError> { + let res = this + .execute_pipeline(cond.condition, stdin.try_clone()?, stdout.try_clone()?) + .join(); + let block = match res { + Ok(_) | Err(ExecError::ExecError(0)) => cond.true_block, + Err(_) => cond.false_block, + }; + for cmd in block.commands { + let cmd = cmd.expand(&mut this)?; + this.execute(cmd, stdin.try_clone()?, stdout.try_clone()?) + .join()?; + } + Ok(()) + }); + SpawnedCmd::Fun(handle) + } } impl parse::Expander for Executor { @@ -556,7 +582,7 @@ pub trait Builtin: Send + Sync { ) -> BuiltinResult; } -pub trait BuiltinClone : Builtin { +pub trait BuiltinClone: Builtin { fn clone_box(&self) -> Box; } -- cgit v1.2.3