From 92d776676515e036f834c611955c4b6e8020f0be Mon Sep 17 00:00:00 2001 From: pagedmov Date: Sun, 16 Mar 2025 02:56:29 -0400 Subject: [PATCH] Early work on scripting features --- src/builtin/cd.rs | 15 +-- src/builtin/echo.rs | 25 ++--- src/builtin/export.rs | 52 +++++----- src/builtin/jobctl.rs | 40 ++------ src/builtin/mod.rs | 56 +++++++++++ src/builtin/pwd.rs | 21 ++-- src/builtin/shift.rs | 17 ++-- src/builtin/source.rs | 16 +-- src/fern.rs | 2 + src/libsh/utils.rs | 32 ++++++ src/parse/execute.rs | 8 +- src/parse/mod.rs | 218 ++++++++++++++++++++++++++++++++++++++--- src/prompt/mod.rs | 5 +- src/prompt/readline.rs | 1 + 14 files changed, 376 insertions(+), 132 deletions(-) diff --git a/src/builtin/cd.rs b/src/builtin/cd.rs index 0eb548a..35cffac 100644 --- a/src/builtin/cd.rs +++ b/src/builtin/cd.rs @@ -1,20 +1,14 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}}; + +use super::setup_builtin; pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("cd"), Some(child_pgid))?; - job.push_child(child); + let (argv,_) = setup_builtin(argv,job,None)?; - let argv = prepare_argv(argv); let new_dir = if let Some((arg,_)) = argv.into_iter().skip(1).next() { PathBuf::from(arg) } else { @@ -25,5 +19,6 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> { let new_dir = env::current_dir().unwrap(); env::set_var("PWD", new_dir); + state::set_status(0); Ok(()) } diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index b237c4c..58a764d 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -1,4 +1,4 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}}; +use crate::{builtin::setup_builtin, jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments: _, argv } = node.class else { @@ -6,33 +6,22 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( }; assert!(!argv.is_empty()); - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("echo"), Some(child_pgid))?; - job.push_child(child); - - io_stack.append_to_frame(node.redirs); - let mut io_frame = io_stack.pop_frame(); - - io_frame.redirect()?; + let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?; let stdout = borrow_fd(STDOUT_FILENO); + flog!(DEBUG, argv); - let mut echo_output = prepare_argv(argv) - .into_iter() + let mut echo_output = argv.into_iter() .map(|a| a.0) // Extract the String from the tuple of (String,Span) - .skip(1) // Skip 'echo' .collect::>() .join(" "); echo_output.push('\n'); + flog!(DEBUG, echo_output); write(stdout, echo_output.as_bytes())?; - io_frame.restore()?; + io_frame.unwrap().restore()?; + state::set_status(0); Ok(()) } diff --git a/src/builtin/export.rs b/src/builtin/export.rs index 8c7085c..cbe4973 100644 --- a/src/builtin/export.rs +++ b/src/builtin/export.rs @@ -1,32 +1,40 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; -pub fn export(node: Node, job: &mut JobBldr) -> ShResult<()> { +use super::setup_builtin; + +pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid + let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?; + + if argv.is_empty() { + // Display the environment variables + let mut env_output = env::vars() + .map(|var| format!("{}={}",var.0,var.1)) // Get all of them, zip them into one string + .collect::>(); + env_output.sort(); // Sort them alphabetically + let mut env_output = env_output.join("\n"); // Join them with newlines + env_output.push('\n'); // Push a final newline + + let stdout = borrow_fd(STDOUT_FILENO); + write(stdout, env_output.as_bytes())?; // Write it } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("export"), Some(child_pgid))?; - job.push_child(child); - - let argv = prepare_argv(argv); - - for (arg,span) in argv { - let Some((var,val)) = arg.split_once('=') else { - return Err( - ShErr::full( - ShErrKind::ExecFail, - "Expected an assignment in export args", - span.into() + for (arg,span) in argv { + let Some((var,val)) = arg.split_once('=') else { + return Err( + ShErr::full( + ShErrKind::SyntaxErr, + "export: Expected an assignment in export args", + span.into() + ) ) - ) - }; - env::set_var(var, val); + }; + env::set_var(var, val); + } } + io_frame.unwrap().restore()?; + state::set_status(0); Ok(()) } diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs index 712e005..c91853b 100644 --- a/src/builtin/jobctl.rs +++ b/src/builtin/jobctl.rs @@ -1,5 +1,7 @@ use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}}; +use super::setup_builtin; + pub enum JobBehavior { Foregound, Background @@ -11,22 +13,12 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR JobBehavior::Foregound => "fg", JobBehavior::Background => "bg" }; - let NdRule::Command { assignments, argv } = node.class else { + let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some(cmd), Some(child_pgid))?; - job.push_child(child); - - let mut argv = prepare_argv(argv) - .into_iter() - .skip(1); + let (argv,_) = setup_builtin(argv, job, None)?; + let mut argv = argv.into_iter(); if read_jobs(|j| j.get_fg().is_some()) { return Err( @@ -145,28 +137,14 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult { } pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { - let NdRule::Command { assignments, argv } = node.class else { + let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("jobs"), Some(child_pgid))?; - job.push_child(child); - - let mut argv = prepare_argv(argv) - .into_iter() - .skip(1); - - let mut io_frame = io_stack.pop_frame(); - io_frame.redirect()?; + let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?; let mut flags = JobCmdFlags::empty(); - while let Some((arg,span)) = argv.next() { + for (arg,span) in argv { let mut chars = arg.chars().peekable(); if chars.peek().is_none_or(|ch| *ch != '-') { return Err( @@ -198,7 +176,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( } } write_jobs(|j| j.print_jobs(flags))?; - io_frame.restore()?; + io_frame.unwrap().restore()?; state::set_status(0); Ok(()) diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 5ef9fc9..c5f8578 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,3 +1,7 @@ +use nix::unistd::Pid; + +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, lex::{Span, Tk}, Redir}, procio::{IoFrame, IoStack}}; + pub mod echo; pub mod cd; pub mod export; @@ -17,3 +21,55 @@ pub const BUILTINS: [&str;9] = [ "fg", "bg" ]; + +/// Sets up a builtin command +/// +/// Prepares a builtin for execution by processing arguments, setting up redirections, and registering the command as a child process in the given `JobBldr` +/// +/// # Parameters +/// * argv - The vector of raw argument tokens +/// * job - A mutable reference to a `JobBldr` +/// * io_info - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs` +/// +/// # Behavior +/// * Cleans, expands, and word splits the arg vector +/// * Adds a new `ChildProc` to the job builder +/// * Performs redirections, if any. +/// +/// # Returns +/// * The processed arg vector +/// * The popped `IoFrame`, if any +/// +/// # Notes +/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame` +/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()` +/// * If no redirections are given, the second field will *always* be `None` +pub fn setup_builtin<'t>( + argv: Vec>, + job: &'t mut JobBldr, + io_info: Option<(&mut IoStack,Vec)>, +) -> ShResult<(Vec<(String,Span<'t>)>, Option)> { + let mut argv: Vec<(String,Span)> = prepare_argv(argv); + + let child_pgid = if let Some(pgid) = job.pgid() { + pgid + } else { + job.set_pgid(Pid::this()); + Pid::this() + }; + let cmd_name = argv.remove(0).0; + let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?; + job.push_child(child); + + let io_frame = if let Some((io_stack,redirs)) = io_info { + io_stack.append_to_frame(redirs); + let mut io_frame = io_stack.pop_frame(); + io_frame.redirect()?; + Some(io_frame) + } else { + None + }; + + // We return the io_frame because the caller needs to also call io_frame.restore() + Ok((argv,io_frame)) +} diff --git a/src/builtin/pwd.rs b/src/builtin/pwd.rs index 81b194e..f76fa4a 100644 --- a/src/builtin/pwd.rs +++ b/src/builtin/pwd.rs @@ -1,30 +1,21 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; + +use super::setup_builtin; pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("pwd"), Some(child_pgid))?; - job.push_child(child); - - io_stack.append_to_frame(node.redirs); - let mut io_frame = io_stack.pop_frame(); - - io_frame.redirect()?; + let (_,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?; let stdout = borrow_fd(STDOUT_FILENO); let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string(); curr_dir.push('\n'); write(stdout, curr_dir.as_bytes())?; - io_frame.restore().unwrap(); + io_frame.unwrap().restore().unwrap(); + state::set_status(0); Ok(()) } diff --git a/src/builtin/shift.rs b/src/builtin/shift.rs index 6ea8481..7ceb52b 100644 --- a/src/builtin/shift.rs +++ b/src/builtin/shift.rs @@ -1,20 +1,14 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}}; + +use super::setup_builtin; pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("shift"), Some(child_pgid))?; - job.push_child(child); - - let mut argv = prepare_argv(argv).into_iter().skip(1); + let (argv,_) = setup_builtin(argv, job, None)?; + let mut argv = argv.into_iter(); if let Some((arg,span)) = argv.next() { let Ok(count) = arg.parse::() else { @@ -31,5 +25,6 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> { } } + state::set_status(0); Ok(()) } diff --git a/src/builtin/source.rs b/src/builtin/source.rs index 989a585..ae8cca8 100644 --- a/src/builtin/source.rs +++ b/src/builtin/source.rs @@ -1,20 +1,13 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::source_file}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, source_file}}; + +use super::setup_builtin; pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> { let NdRule::Command { assignments: _, argv } = node.class else { unreachable!() }; - let child_pgid = if let Some(pgid) = job.pgid() { - pgid - } else { - job.set_pgid(Pid::this()); - Pid::this() - }; - let child = ChildProc::new(Pid::this(), Some("source"), Some(child_pgid))?; - job.push_child(child); - - let argv = prepare_argv(argv).into_iter().skip(1); + let (argv,_) = setup_builtin(argv, job, None)?; for (arg,span) in argv { let path = PathBuf::from(arg); @@ -39,5 +32,6 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> { source_file(path)?; } + state::set_status(0); Ok(()) } diff --git a/src/fern.rs b/src/fern.rs index 587dab2..9c76b3e 100644 --- a/src/fern.rs +++ b/src/fern.rs @@ -13,6 +13,7 @@ pub mod tests; use libsh::error::ShResult; use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream}; +use signal::sig_setup; use termios::{LocalFlags, Termios}; use crate::prelude::*; @@ -64,6 +65,7 @@ pub fn exec_input(input: &str) -> ShResult<()> { fn main() { save_termios(); set_termios(); + sig_setup(); loop { let input = prompt::read_line().unwrap(); let start = Instant::now(); diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 76cd17b..949e58a 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -1,11 +1,43 @@ use std::collections::VecDeque; +use crate::parse::lex::{Span, Tk}; +use crate::prelude::*; + pub trait VecDequeExt { fn to_vec(self) -> Vec; } +pub trait TkVecUtils { + fn get_span(&self) -> Option; + fn debug_tokens(&self); +} + impl VecDequeExt for VecDeque { fn to_vec(self) -> Vec { self.into_iter().collect::>() } } + +impl<'t> TkVecUtils> for Vec> { + fn get_span(&self) -> Option> { + if let Some(first_tk) = self.first() { + if let Some(last_tk) = self.last() { + Some( + Span::new( + first_tk.span.start..last_tk.span.end, + first_tk.source() + ) + ) + } else { + None + } + } else { + None + } + } + fn debug_tokens(&self) { + for token in self { + flog!(DEBUG, "token: {}",token) + } + } +} diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 77f6344..14f4e41 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -133,7 +133,7 @@ impl<'t> Dispatcher<'t> { let result = match cmd_raw.span.as_str() { "echo" => echo(cmd, io_stack_mut, curr_job_mut), "cd" => cd(cmd, curr_job_mut), - "export" => export(cmd, curr_job_mut), + "export" => export(cmd, io_stack_mut, curr_job_mut), "pwd" => pwd(cmd, io_stack_mut, curr_job_mut), "source" => source(cmd, curr_job_mut), "shift" => shift(cmd, curr_job_mut), @@ -147,7 +147,11 @@ impl<'t> Dispatcher<'t> { env::set_var(&var, ""); } - Ok(result?) + if let Err(e) = result { + state::set_status(1); + return Err(e.into()) + } + Ok(()) } pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> { let NdRule::Command { assignments, argv } = cmd.class else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6a2be86..1fbab39 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,9 +1,10 @@ use std::str::FromStr; use bitflags::bitflags; +use fmt::Display; use lex::{Span, Tk, TkFlags, TkRule}; -use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}}; +use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::{IoFd, IoFile, IoInfo}}; pub mod lex; pub mod execute; @@ -175,7 +176,7 @@ pub enum RedirType { #[derive(Debug)] pub struct CondNode<'t> { - pub cond: Vec>, + pub cond: Box>, pub body: Vec> } @@ -204,6 +205,26 @@ pub enum LoopKind { Until } +impl FromStr for LoopKind { + type Err = ShErr; + fn from_str(s: &str) -> Result { + match s { + "while" => Ok(Self::While), + "until" => Ok(Self::Until), + _ => Err(ShErr::simple(ShErrKind::ParseErr, format!("Invalid loop kind: {s}"))) + } + } +} + +impl Display for LoopKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LoopKind::While => write!(f,"while"), + LoopKind::Until => write!(f,"until") + } + } +} + #[derive(Debug)] pub enum AssignKind { Eq, @@ -215,8 +236,8 @@ pub enum AssignKind { #[derive(Debug)] pub enum NdRule<'t> { - IfNode { cond_blocks: Vec>, else_block: Vec> }, - LoopNode { kind: LoopKind, cond_block: CondNode<'t> }, + IfNode { cond_nodes: Vec>, else_block: Vec> }, + LoopNode { kind: LoopKind, cond_node: CondNode<'t> }, ForNode { vars: Vec>, arr: Vec>, body: Vec> }, CaseNode { pattern: Tk<'t>, case_blocks: Vec> }, Command { assignments: Vec>, argv: Vec> }, @@ -259,6 +280,14 @@ impl<'t> ParseStream<'t> { None } } + fn next_tk_is_some(&self) -> bool { + self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI) + } + fn check_keyword(&self, kw: &str) -> bool { + self.tokens.first().is_some_and(|tk| { + tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw + }) + } /// Slice off consumed tokens fn commit(&mut self, num_consumed: usize) { assert!(num_consumed <= self.tokens.len()); @@ -303,6 +332,12 @@ impl<'t> ParseStream<'t> { /// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom /// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline fn parse_block(&mut self, check_pipelines: bool) -> ShResult>> { + if let Some(node) = self.parse_loop()? { + return Ok(Some(node)) + } + if let Some(node) = self.parse_if()? { + return Ok(Some(node)) + } if check_pipelines { if let Some(node) = self.parse_pipeline()? { return Ok(Some(node)) @@ -314,6 +349,158 @@ impl<'t> ParseStream<'t> { } Ok(None) } + fn parse_if(&mut self) -> ShResult>> { + // Needs at last one 'if-then', + // Any number of 'elif-then', + // Zero or one 'else' + let mut node_tks: Vec = vec![]; + let mut cond_nodes: Vec = vec![]; + let mut else_block: Vec = vec![]; + + if !self.check_keyword("if") || !self.next_tk_is_some() { + return Ok(None) + } + node_tks.push(self.next_tk().unwrap()); + + loop { + let prefix_keywrd = if cond_nodes.is_empty() { + "if" + } else { + "elif" + }; + let Some(cond) = self.parse_block(true)? else { + return Err(parse_err_full( + &format!("Expected an expression after '{prefix_keywrd}'"), + &node_tks.get_span().unwrap() + )); + }; + cond.tokens.debug_tokens(); + node_tks.extend(cond.tokens.clone()); + + if !self.check_keyword("then") || !self.next_tk_is_some() { + return Err(parse_err_full( + &format!("Expected 'then' after '{prefix_keywrd}' condition"), + &node_tks.get_span().unwrap() + )); + } + node_tks.push(self.next_tk().unwrap()); + + let mut body_blocks = vec![]; + while let Some(body_block) = self.parse_block(true)? { + body_block.tokens.debug_tokens(); + node_tks.extend(body_block.tokens.clone()); + body_blocks.push(body_block); + } + if body_blocks.is_empty() { + return Err(parse_err_full( + "Expected an expression after 'then'", + &node_tks.get_span().unwrap() + )); + }; + let cond_node = CondNode { cond: Box::new(cond), body: body_blocks }; + cond_nodes.push(cond_node); + flog!(DEBUG, cond_nodes.len()); + flog!(DEBUG, !self.check_keyword("elif") || !self.next_tk_is_some()); + + if !self.check_keyword("elif") || !self.next_tk_is_some() { + break + } else { + node_tks.push(self.next_tk().unwrap()); + } + } + + if self.check_keyword("else") { + node_tks.push(self.next_tk().unwrap()); + while let Some(block) = self.parse_block(true)? { + else_block.push(block) + } + if else_block.is_empty() { + return Err(parse_err_full( + "Expected an expression after 'else'", + &node_tks.get_span().unwrap() + )); + } + } + + if !self.check_keyword("fi") || !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected 'fi' after if statement", + &node_tks.get_span().unwrap() + )); + } + node_tks.push(self.next_tk().unwrap()); + + let node = Node { + class: NdRule::IfNode { cond_nodes, else_block }, + flags: NdFlags::empty(), + redirs: vec![], + tokens: node_tks + }; + flog!(DEBUG, node); + Ok(Some(node)) + } + fn parse_loop(&mut self) -> ShResult>> { + // Requires a single CondNode and a LoopKind + let loop_kind: LoopKind; + let cond_node: CondNode<'t>; + let mut node_tks = vec![]; + + if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() { + return Ok(None) + } + let loop_tk = self.next_tk().unwrap(); + loop_kind = loop_tk.span + .as_str() + .parse() // LoopKind implements FromStr + .unwrap(); + node_tks.push(loop_tk); + + let Some(cond) = self.parse_block(true)? else { + return Err(parse_err_full( + &format!("Expected an expression after '{loop_kind}'"), // It also implements Display + &node_tks.get_span().unwrap() + )) + }; + node_tks.extend(cond.tokens.clone()); + + if !self.check_keyword("do") || !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected 'do' after loop condition", + &node_tks.get_span().unwrap() + )) + } + node_tks.push(self.next_tk().unwrap()); + + let mut body = vec![]; + while let Some(block) = self.parse_block(true)? { + node_tks.extend(block.tokens.clone()); + body.push(block); + } + if body.is_empty() { + return Err(parse_err_full( + "Expected an expression after 'do'", + &node_tks.get_span().unwrap() + )) + }; + + if !self.check_keyword("done") || !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected 'done' after loop body", + &node_tks.get_span().unwrap() + )) + } + node_tks.push(self.next_tk().unwrap()); + + cond_node = CondNode { cond: Box::new(cond), body }; + let loop_node = Node { + class: NdRule::LoopNode { kind: loop_kind, cond_node }, + flags: NdFlags::empty(), + redirs: vec![], + tokens: node_tks + }; + flog!(DEBUG, loop_node); + Ok(Some(loop_node)) + } fn parse_pipeline(&mut self) -> ShResult>> { let mut cmds = vec![]; let mut node_tks = vec![]; @@ -356,12 +543,16 @@ impl<'t> ParseStream<'t> { node_tks.push(prefix_tk.clone()); argv.push(prefix_tk.clone()); break + } else if prefix_tk.flags.contains(TkFlags::ASSIGN) { let Some(assign) = self.parse_assignment(&prefix_tk) else { break }; node_tks.push(prefix_tk.clone()); assignments.push(assign) + + } else if prefix_tk.flags.contains(TkFlags::KEYWORD) { + return Ok(None) } } @@ -428,13 +619,10 @@ impl<'t> ParseStream<'t> { _ => unreachable!() }) else { self.flags |= ParseFlags::ERROR; - return Err( - ShErr::full( - ShErrKind::InternalErr, - "Error opening file for redirection", - path_tk.span.clone().into() - ) - ) + return Err(parse_err_full( + "Error opening file for redirection", + &path_tk.span + )); }; let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file); @@ -591,3 +779,11 @@ fn node_is_punctuated<'t>(tokens: &Vec) -> bool { matches!(tk.class, TkRule::Sep) }) } + +fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr { + ShErr::full( + ShErrKind::ParseErr, + reason, + blame.clone().into() + ) +} diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index b8b82bb..5608d47 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -28,7 +28,10 @@ pub fn read_line<'s>() -> ShResult { } Ok(line) } - Err(ReadlineError::Eof) => std::process::exit(0), + Err(ReadlineError::Eof) => { + kill(Pid::this(), Signal::SIGQUIT)?; + Ok(String::new()) + } Err(ReadlineError::Interrupted) => Ok(String::new()), Err(e) => { return Err(e.into()) diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index eab13c8..e96b13d 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -65,6 +65,7 @@ impl Highlighter for FernReadline { impl Validator for FernReadline { fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result { + return Ok(ValidationResult::Valid(None)); let mut tokens = vec![]; let tk_stream = LexStream::new(ctx.input(), LexFlags::empty()); for tk in tk_stream {