diff --git a/src/fern.rs b/src/fern.rs index 39e38ee..b07b63d 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 procio::IoFrame; use signal::sig_setup; use termios::{LocalFlags, Termios}; use crate::prelude::*; @@ -46,7 +47,7 @@ fn set_termios() { } } -pub fn exec_input(input: &str) -> ShResult<()> { +pub fn exec_input(input: &str, io_frame: Option) -> ShResult<()> { let parse_start = Instant::now(); let mut tokens = vec![]; for token in LexStream::new(&input, LexFlags::empty()) { @@ -61,6 +62,9 @@ pub fn exec_input(input: &str) -> ShResult<()> { let exec_start = Instant::now(); let mut dispatcher = Dispatcher::new(nodes); + if let Some(frame) = io_frame { + dispatcher.io_stack.push(frame) + } dispatcher.begin_dispatch()?; flog!(INFO, "cmd duration: {:?}", exec_start.elapsed()); @@ -77,7 +81,7 @@ fn main() { loop { let input = prompt::read_line().unwrap(); - if let Err(e) = exec_input(&input) { + if let Err(e) = exec_input(&input,None) { eprintln!("{e}"); } } diff --git a/src/jobs.rs b/src/jobs.rs index b0f658f..ddf22da 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -613,6 +613,7 @@ pub fn enable_reaping() -> ShResult<()> { pub fn wait_fg(job: Job) -> ShResult<()> { flog!(TRACE, "Waiting on foreground job"); let mut code = 0; + flog!(DEBUG,job.pgid()); attach_tty(job.pgid())?; disable_reaping()?; let statuses = write_jobs(|j| j.new_fg(job))?; diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 876e926..9927155 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -1,9 +1,9 @@ use std::collections::VecDeque; -use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, write_vars}}; +use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ErrSpan, ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars}}; -use super::{lex::{Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, Redir, RedirType}; +use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, Redir, RedirType}; pub enum AssignBehavior { Export, @@ -61,6 +61,8 @@ impl<'t> Dispatcher<'t> { NdRule::Pipeline {..} => self.exec_pipeline(node)?, NdRule::IfNode {..} => self.exec_if(node)?, NdRule::LoopNode {..} => self.exec_loop(node)?, + NdRule::BraceGrp {..} => self.exec_brc_grp(node)?, + NdRule::FuncDef {..} => self.exec_func_def(node)?, NdRule::Command {..} => self.dispatch_cmd(node)?, _ => unreachable!() } @@ -68,10 +70,12 @@ impl<'t> Dispatcher<'t> { } pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> { let Some(cmd) = node.get_command() else { - return self.exec_cmd(node) + return self.exec_cmd(node) // Argv is empty, probably an assignment }; if cmd.flags.contains(TkFlags::BUILTIN) { self.exec_builtin(node) + } else if is_func(node.get_command().cloned()) { + self.exec_func(node) } else { self.exec_cmd(node) } @@ -95,6 +99,66 @@ impl<'t> Dispatcher<'t> { } Ok(()) } + pub fn exec_func_def(&mut self, func_def: Node<'t>) -> ShResult<()> { + let NdRule::FuncDef { name, body } = func_def.class else { + unreachable!() + }; + let body_span = body.get_span(); + let body = body_span.as_str(); + let name = name.span.as_str().strip_suffix("()").unwrap(); + write_logic(|l| l.insert_func(name, body)); + Ok(()) + } + pub fn exec_func(&mut self, func: Node<'t>) -> ShResult<()> { + let blame: ErrSpan = func.get_span().into(); + // TODO: Find a way to store functions as pre-parsed nodes so we don't have to re-parse them + let NdRule::Command { assignments, mut argv } = func.class else { + unreachable!() + }; + + self.set_assignments(assignments, AssignBehavior::Export); + + let mut io_frame = self.io_stack.pop_frame(); + io_frame.extend(func.redirs); + + let func_name = argv.remove(0).span.as_str().to_string(); + if let Some(func_body) = read_logic(|l| l.get_func(&func_name)) { + let saved_sh_args = read_vars(|v| v.sh_argv().clone()); + write_vars(|v| { + v.clear_args(); + for arg in argv { + v.bpush_arg(arg.to_string()); + } + }); + + let result = exec_input(&func_body, Some(io_frame)); + + write_vars(|v| *v.sh_argv_mut() = saved_sh_args); + Ok(result?) + } else { + Err( + ShErr::full( + ShErrKind::InternalErr, + format!("Failed to find function '{}'",func_name), + blame + ) + ) + } + } + pub fn exec_brc_grp(&mut self, brc_grp: Node<'t>) -> ShResult<()> { + let NdRule::BraceGrp { body } = brc_grp.class else { + unreachable!() + }; + let mut io_frame = self.io_stack.pop_frame(); + io_frame.extend(brc_grp.redirs); + + for node in body { + self.io_stack.push_frame(io_frame.clone()); + self.dispatch_node(node)?; + } + + Ok(()) + } pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> { let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else { unreachable!(); @@ -413,3 +477,10 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option,Option)> { } stack } + +pub fn is_func<'t>(tk: Option>) -> bool { + let Some(tk) = tk else { + return false + }; + read_logic(|l| l.get_func(&tk.to_string())).is_some() +} diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 90c2323..1b3cfff 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -76,6 +76,8 @@ pub enum TkRule { Bg, Sep, Redir, + BraceGrpStart, + BraceGrpEnd, Expanded { exp: Vec }, Comment, EOI, // End-of-Input @@ -156,6 +158,7 @@ bitflags! { const FRESH = 0b00010000; /// The lexer has no more tokens to produce const STALE = 0b00100000; + const IN_BRC_GRP = 0b01000000; } } @@ -192,13 +195,26 @@ impl<'t> LexStream<'t> { pub fn slice_from_cursor(&self) -> Option<&'t str> { self.slice(self.cursor..) } - /// The next string token is a command name - pub fn next_is_cmd(&mut self) { - self.flags |= LexFlags::NEXT_IS_CMD; + pub fn in_brc_grp(&self) -> bool { + self.flags.contains(LexFlags::IN_BRC_GRP) } - /// The next string token is not a command name - pub fn next_is_not_cmd(&mut self) { - self.flags &= !LexFlags::NEXT_IS_CMD; + pub fn set_in_brc_grp(&mut self, is: bool) { + if is { + self.flags |= LexFlags::IN_BRC_GRP; + } else { + self.flags &= !LexFlags::IN_BRC_GRP; + } + } + pub fn next_is_cmd(&self) -> bool { + self.flags.contains(LexFlags::NEXT_IS_CMD) + } + /// Set whether the next string token is a command name + pub fn set_next_is_cmd(&mut self, is: bool) { + if is { + self.flags |= LexFlags::NEXT_IS_CMD; + } else { + self.flags &= !LexFlags::NEXT_IS_CMD; + } } pub fn read_redir(&mut self) -> Option>> { assert!(self.cursor <= self.source.len()); @@ -299,6 +315,24 @@ impl<'t> LexStream<'t> { pos += 1; } } + '{' if pos == self.cursor && self.next_is_cmd() => { + pos += 1; + let mut tk = self.get_token(self.cursor..pos, TkRule::BraceGrpStart); + tk.flags |= TkFlags::IS_CMD; + self.set_in_brc_grp(true); + self.set_next_is_cmd(true); + + self.cursor = pos; + return Ok(tk) + } + '}' if pos == self.cursor && self.in_brc_grp() => { + pos += 1; + let tk = self.get_token(self.cursor..pos, TkRule::BraceGrpEnd); + self.set_in_brc_grp(false); + self.set_next_is_cmd(true); + self.cursor = pos; + return Ok(tk) + } '"' | '\'' => { self.in_quote = true; quote_pos = Some(pos); @@ -340,7 +374,9 @@ impl<'t> LexStream<'t> { ); } if self.flags.contains(LexFlags::NEXT_IS_CMD) { - if KEYWORDS.contains(&new_tk.span.as_str()) { + flog!(DEBUG,&new_tk.span.as_str()); + if is_keyword(&new_tk.span.as_str()) { + flog!(DEBUG,"is keyword"); new_tk.flags |= TkFlags::KEYWORD; } else if is_assignment(&new_tk.span.as_str()) { new_tk.flags |= TkFlags::ASSIGN; @@ -351,7 +387,7 @@ impl<'t> LexStream<'t> { new_tk.flags |= TkFlags::BUILTIN; } flog!(TRACE, new_tk.flags); - self.next_is_not_cmd(); + self.set_next_is_cmd(false); } } self.cursor = pos; @@ -411,7 +447,7 @@ impl<'t> Iterator for LexStream<'t> { '\r' | '\n' | ';' => { let ch_idx = self.cursor; self.cursor += 1; - self.next_is_cmd(); + self.set_next_is_cmd(true); while let Some(ch) = get_char(self.source, self.cursor) { if is_hard_sep(ch) { // Combine consecutive separators into one, including whitespace @@ -438,7 +474,7 @@ impl<'t> Iterator for LexStream<'t> { '|' => { let ch_idx = self.cursor; self.cursor += 1; - self.next_is_cmd(); + self.set_next_is_cmd(true); let tk_type = if let Some('|') = get_char(self.source, self.cursor) { self.cursor += 1; @@ -455,7 +491,7 @@ impl<'t> Iterator for LexStream<'t> { '&' => { let ch_idx = self.cursor; self.cursor += 1; - self.next_is_cmd(); + self.set_next_is_cmd(true); let tk_type = if let Some('&') = get_char(self.source, self.cursor) { self.cursor += 1; @@ -467,7 +503,7 @@ impl<'t> Iterator for LexStream<'t> { } _ => { if let Some(tk) = self.read_redir() { - self.next_is_not_cmd(); + self.set_next_is_cmd(false); match tk { Ok(tk) => tk, Err(e) => return Some(Err(e)) @@ -516,3 +552,8 @@ pub fn is_hard_sep(ch: char) -> bool { pub fn is_field_sep(ch: char) -> bool { matches!(ch, ' ' | '\t') } + +pub fn is_keyword(slice: &str) -> bool { + KEYWORDS.contains(&slice) || + (slice.ends_with("()") && !slice.ends_with("\\()")) +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 9a51c54..67c22ff 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -9,6 +9,18 @@ use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, pre pub mod lex; pub mod execute; +/// Try to match a specific parsing rule +/// +/// # Notes +/// * If the match fails, execution continues. +/// * If the match succeeds, the matched node is returned. +macro_rules! try_match { + ($expr:expr) => { + if let Some(node) = $expr { + return Ok(Some(node)) + } + }; +} #[derive(Clone,Debug)] pub struct Node<'t> { @@ -244,6 +256,8 @@ pub enum NdRule<'t> { Pipeline { cmds: Vec>, pipe_err: bool }, Conjunction { elements: Vec> }, Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> }, + BraceGrp { body: Vec> }, + FuncDef { name: Tk<'t>, body: Box> } } #[derive(Debug)] @@ -270,6 +284,9 @@ impl<'t> ParseStream<'t> { &TkRule::Null } } + fn peek_tk(&self) -> Option<&Tk<'t>> { + self.tokens.first() + } fn next_tk(&mut self) -> Option> { if !self.tokens.is_empty() { if *self.next_tk_class() == TkRule::EOI { @@ -301,6 +318,7 @@ impl<'t> ParseStream<'t> { TkRule::Or | TkRule::Bg | TkRule::And | + TkRule::BraceGrpEnd | TkRule::Pipe => Ok(()), TkRule::Sep => { @@ -371,25 +389,127 @@ impl<'t> ParseStream<'t> { /// This tries to match on different stuff that can appear in a command position /// Matches shell commands like if-then-fi, pipelines, etc. /// 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 + /// The check_pipelines parameter is used to prevent left-recursion issues in self.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)) - } + try_match!(self.parse_func_def()?); + try_match!(self.parse_brc_grp(false /* from_func_def */)?); + try_match!(self.parse_loop()?); + try_match!(self.parse_if()?); if check_pipelines { - if let Some(node) = self.parse_pipeline()? { - return Ok(Some(node)) - } + try_match!(self.parse_pipeline()?); } else { - if let Some(node) = self.parse_cmd()? { - return Ok(Some(node)) - } + try_match!(self.parse_cmd()?); } Ok(None) } + fn parse_func_def(&mut self) -> ShResult>> { + let mut node_tks: Vec = vec![]; + let name; + let body; + + if !is_func_name(self.peek_tk()) { + return Ok(None) + } + let name_tk = self.next_tk().unwrap(); + node_tks.push(name_tk.clone()); + name = name_tk; + + let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else { + return Err(parse_err_full( + "Expected a brace group after function name", + &node_tks.get_span().unwrap() + ) + ) + }; + body = Box::new(brc_grp); + + let node = Node { + class: NdRule::FuncDef { name, body }, + flags: NdFlags::empty(), + redirs: vec![], + tokens: node_tks + }; + flog!(DEBUG,node); + + Ok(Some(node)) + } + fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult>> { + let mut node_tks: Vec = vec![]; + let mut body: Vec = vec![]; + let mut redirs: Vec = vec![]; + + if *self.next_tk_class() != TkRule::BraceGrpStart { + return Ok(None) + } + node_tks.push(self.next_tk().unwrap()); + + loop { + if *self.next_tk_class() == TkRule::BraceGrpEnd { + node_tks.push(self.next_tk().unwrap()); + break + } + if let Some(node) = self.parse_block(true)? { + node_tks.extend(node.tokens.clone()); + body.push(node); + } + if !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected a closing brace for this brace group", + &node_tks.get_span().unwrap() + ) + ) + } + } + + if !from_func_def { + while self.check_redir() { + let tk = self.next_tk().unwrap(); + node_tks.push(tk.clone()); + let redir_bldr = tk.span.as_str().parse::().unwrap(); + if redir_bldr.io_mode.is_none() { + let path_tk = self.next_tk(); + + if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) { + self.flags |= ParseFlags::ERROR; + return Err( + ShErr::full( + ShErrKind::ParseErr, + "Expected a filename after this redirection", + tk.span.clone().into() + ) + ) + }; + + let path_tk = path_tk.unwrap(); + node_tks.push(path_tk.clone()); + let redir_class = redir_bldr.class.unwrap(); + let pathbuf = PathBuf::from(path_tk.span.as_str()); + + let Ok(file) = get_redir_file(redir_class, pathbuf) else { + self.flags |= ParseFlags::ERROR; + return Err(parse_err_full( + "Error opening file for redirection", + &path_tk.span + )); + }; + + let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file); + let redir_bldr = redir_bldr.with_io_mode(io_mode); + let redir = redir_bldr.build(); + redirs.push(redir); + } + } + } + + let node = Node { + class: NdRule::BraceGrp { body }, + flags: NdFlags::empty(), + redirs, + tokens: node_tks + }; + flog!(DEBUG, node); + Ok(Some(node)) + } fn parse_if(&mut self) -> ShResult>> { // Needs at last one 'if-then', // Any number of 'elif-then', @@ -647,6 +767,7 @@ impl<'t> ParseStream<'t> { TkRule::EOI | TkRule::Pipe | TkRule::And | + TkRule::BraceGrpEnd | TkRule::Or => { break } @@ -878,3 +999,10 @@ fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr { blame.clone().into() ) } + +fn is_func_name<'t>(tk: Option<&Tk<'t>>) -> bool { + tk.is_some_and(|tk| { + tk.flags.contains(TkFlags::KEYWORD) && + (tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()")) + }) +} diff --git a/src/state.rs b/src/state.rs index 4c8c0ca..fc398ab 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,11 +1,43 @@ -use std::{collections::{HashMap, VecDeque}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}}; +use std::{cell::RefCell, collections::{HashMap, VecDeque}, ops::Range, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}}; -use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::lex::get_char, prelude::*}; +use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, Node}, prelude::*}; pub static JOB_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(JobTab::new())); pub static VAR_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(VarTab::new())); +pub static LOGIC_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(LogTab::new())); + +thread_local! { + pub static LAST_INPUT: RefCell = RefCell::new(String::new()); +} + +/// The logic table for the shell +/// +/// Contains aliases and functions +pub struct LogTab { + // TODO: Find a way to store actual owned nodes instead of strings that must be re-parsed + functions: HashMap, + aliases: HashMap +} + +impl LogTab { + pub fn new() -> Self { + Self { functions: HashMap::new(), aliases: HashMap::new() } + } + pub fn insert_func(&mut self, name: &str, body: &str) { + self.functions.insert(name.into(), body.into()); + } + pub fn get_func(&self, name: &str) -> Option { + self.functions.get(name).cloned() + } + pub fn insert_alias(&mut self, name: &str, body: &str) { + self.aliases.insert(name.into(), body.into()); + } + pub fn get_alias(&self, name: &str) -> Option { + self.aliases.get(name).cloned() + } +} pub struct VarTab { vars: HashMap, @@ -35,6 +67,15 @@ impl VarTab { self.bpush_arg(arg); } } + pub fn sh_argv(&self) -> &VecDeque { + &self.sh_argv + } + pub fn sh_argv_mut(&mut self) -> &mut VecDeque { + &mut self.sh_argv + } + pub fn clear_args(&mut self) { + self.sh_argv.clear() + } fn update_arg_params(&mut self) { self.set_param('@', &self.sh_argv.clone().to_vec().join(" ")); self.set_param('#', &self.sh_argv.len().to_string()); @@ -131,6 +172,33 @@ pub fn write_vars) -> T>(f: F) -> T { f(lock) } +/// Read from the logic table +pub fn read_logic) -> T>(f: F) -> T { + let lock = LOGIC_TABLE.read().unwrap(); + f(lock) +} + +/// Write to the logic table +pub fn write_logic) -> T>(f: F) -> T { + let lock = &mut LOGIC_TABLE.write().unwrap(); + f(lock) +} + +pub fn set_last_input(input: &str) { + LAST_INPUT.with(|input_ref| { + let mut last_input = input_ref.borrow_mut(); + last_input.clear(); + last_input.push_str(input); + }) +} + +pub fn slice_last_input(range: Range) -> String { + LAST_INPUT.with(|input_ref| { + let input = input_ref.borrow(); + input[range].to_string() + }) +} + pub fn get_status() -> i32 { read_vars(|v| v.get_param('?')).parse::().unwrap() } @@ -145,6 +213,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> { let mut buf = String::new(); file.read_to_string(&mut buf)?; - exec_input(&buf)?; + exec_input(&buf, None)?; Ok(()) }