use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration}; use nix::unistd::{gethostname, getppid, User}; use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, ConjunctNode, NdRule, Node, ParsedSrc}, 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 META_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(MetaTab::new())); thread_local! { pub static LOGIC_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(LogTab::new())); } /// A shell function /// /// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to /// The Node must be stored with the ParsedSrc because the tokens of the node contain an Rc /// Which refers to the String held in ParsedSrc /// /// Can be dereferenced to pull out the wrapped Node #[derive(Clone,Debug)] pub struct ShFunc(Node); impl ShFunc { pub fn new(mut src: ParsedSrc) -> Self { let body = Self::extract_brc_grp_hack(src.extract_nodes()); Self(body) } fn extract_brc_grp_hack(mut tree: Vec) -> Node { // FIXME: find a better way to do this let conjunction = tree.pop().unwrap(); let NdRule::Conjunction { mut elements } = conjunction.class else { unreachable!() }; let conjunct_node = elements.pop().unwrap(); let ConjunctNode { cmd, operator: _ } = conjunct_node; *cmd } } impl Deref for ShFunc { type Target = Node; fn deref(&self) -> &Self::Target { &self.0 } } /// The logic table for the shell /// /// Contains aliases and functions pub struct LogTab { 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, src: ShFunc) { self.functions.insert(name.into(), src); } pub fn get_func(&self, name: &str) -> Option { self.functions.get(name).cloned() } pub fn funcs(&self) -> &HashMap { &self.functions } pub fn aliases(&self) -> &HashMap { &self.aliases } 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() } } #[derive(Clone)] pub struct VarTab { vars: HashMap, params: HashMap, sh_argv: VecDeque, // Using a VecDeque makes the implementation of `shift` straightforward } impl VarTab { pub fn new() -> Self { let vars = HashMap::new(); let params = Self::init_params(); Self::init_env(); let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() }; var_tab.init_sh_argv(); var_tab } fn init_params() -> HashMap { let mut params = HashMap::new(); params.insert('?', "0".into()); // Last command exit status params.insert('#', "0".into()); // Number of positional parameters params.insert('0', std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell params.insert('$', Pid::this().to_string()); // PID of the shell params.insert('!', "".into()); // PID of the last background job (if any) params } fn init_env() { let pathbuf_to_string = |pb: Result| pb.unwrap_or_default().to_string_lossy().to_string(); // First, inherit any env vars from the parent process let term = { if isatty(1).unwrap() { if let Ok(term) = std::env::var("TERM") { term } else { "linux".to_string() } } else { "xterm-256color".to_string() } }; let home; let username; let uid; if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() { home = user.dir; username = user.name; uid = user.uid; } else { home = PathBuf::new(); username = "unknown".into(); uid = 0.into(); } let home = pathbuf_to_string(Ok(home)); let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default(); env::set_var("IFS", " \t\n"); env::set_var("HOSTNAME", hostname); env::set_var("UID", uid.to_string()); env::set_var("PPID", getppid().to_string()); env::set_var("TMPDIR", "/tmp"); env::set_var("TERM", term); env::set_var("LANG", "en_US.UTF-8"); env::set_var("USER", username.clone()); env::set_var("LOGNAME", username); env::set_var("PWD", pathbuf_to_string(std::env::current_dir())); env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir())); env::set_var("HOME", home.clone()); env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); env::set_var("FERN_HIST",format!("{}/.fernhist",home)); env::set_var("FERN_RC",format!("{}/.fernrc",home)); } pub fn init_sh_argv(&mut self) { for arg in env::args() { 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(); // Push the current exe again // This makes sure that $0 is always the current shell, no matter what // It also updates the arg parameters '@' and '#' as well self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string()); } fn update_arg_params(&mut self) { self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" ")); self.set_param('#', &(self.sh_argv.len() - 1).to_string()); } /// Push an arg to the front of the arg deque pub fn fpush_arg(&mut self, arg: String) { self.sh_argv.push_front(arg); self.update_arg_params(); } /// Push an arg to the back of the arg deque pub fn bpush_arg(&mut self, arg: String) { self.sh_argv.push_back(arg); self.update_arg_params(); } /// Pop an arg from the front of the arg deque pub fn fpop_arg(&mut self) -> Option { let arg = self.sh_argv.pop_front(); self.update_arg_params(); arg } /// Pop an arg from the back of the arg deque pub fn bpop_arg(&mut self) -> Option { let arg = self.sh_argv.pop_back(); self.update_arg_params(); arg } pub fn vars(&self) -> &HashMap { &self.vars } pub fn vars_mut(&mut self) -> &mut HashMap { &mut self.vars } pub fn params(&self) -> &HashMap { &self.params } pub fn params_mut(&mut self) -> &mut HashMap { &mut self.params } pub fn get_var(&self, var: &str) -> String { if var.chars().count() == 1 { let param = self.get_param(get_char(var, 0).unwrap()); if !param.is_empty() { return param } } if let Some(var) = self.vars.get(var).map(|s| s.to_string()) { var } else { std::env::var(var).unwrap_or_default() } } pub fn new_var(&mut self, var: &str, val: &str) { self.vars.insert(var.to_string(), val.to_string()); } pub fn set_param(&mut self, param: char, val: &str) { self.params.insert(param,val.to_string()); } pub fn get_param(&self, param: char) -> String { if param.is_ascii_digit() { let argv_idx = param .to_string() .parse::() .unwrap(); let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default(); arg } else if param == '?' { self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into()) } else { self.params.get(¶m).map(|s| s.to_string()).unwrap_or_default() } } } /// A table of metadata for the shell pub struct MetaTab { runtime_start: Option } impl MetaTab { pub fn new() -> Self { Self { runtime_start: None } } pub fn start_timer(&mut self) { self.runtime_start = Some(Instant::now()); } pub fn stop_timer(&mut self) -> Option { self.runtime_start .take() // runtime_start returns to None .map(|start| start.elapsed()) // return the duration, if any } } /// Read from the job table pub fn read_jobs) -> T>(f: F) -> T { let lock = JOB_TABLE.read().unwrap(); f(lock) } /// Write to the job table pub fn write_jobs) -> T>(f: F) -> T { let lock = &mut JOB_TABLE.write().unwrap(); f(lock) } /// Read from the variable table pub fn read_vars) -> T>(f: F) -> T { let lock = VAR_TABLE.read().unwrap(); f(lock) } /// Write to the variable table pub fn write_vars) -> T>(f: F) -> T { let lock = &mut VAR_TABLE.write().unwrap(); f(lock) } pub fn read_meta) -> T>(f: F) -> T { let lock = META_TABLE.read().unwrap(); f(lock) } /// Write to the variable table pub fn write_meta) -> T>(f: F) -> T { let lock = &mut META_TABLE.write().unwrap(); f(lock) } /// Read from the logic table pub fn read_logic) -> T>(f: F) -> T { LOGIC_TABLE.with(|log| { let lock = log.read().unwrap(); f(lock) }) } /// Write to the logic table pub fn write_logic) -> T>(f: F) -> T { LOGIC_TABLE.with(|log| { let lock = &mut log.write().unwrap(); f(lock) }) } pub fn get_status() -> i32 { read_vars(|v| v.get_param('?')).parse::().unwrap() } pub fn set_status(code: i32) { write_vars(|v| v.set_param('?', &code.to_string())) } pub fn source_rc() -> ShResult<()> { let path = if let Ok(path) = env::var("FERN_RC") { PathBuf::from(&path) } else { let home = env::var("HOME").unwrap(); PathBuf::from(format!("{home}/.fernrc")) }; if !path.exists() { return Err( ShErr::simple(ShErrKind::InternalErr, ".fernrc not found") ) } source_file(path) } pub fn source_file(path: PathBuf) -> ShResult<()> { let mut file = OpenOptions::new() .read(true) .open(path)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; exec_input(buf)?; Ok(()) }