diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs deleted file mode 100644 index dab3021..0000000 --- a/src/builtin/alias.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::prelude::*; - -pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs: _ } = rule { - let argv = argv.drop_first(); - let mut argv_iter = argv.into_iter(); - while let Some(arg) = argv_iter.next() { - let arg_raw = shenv.input_slice(arg.span()).to_string(); - if let Some((alias,body)) = arg_raw.split_once('=') { - let clean_body = clean_string(&body); - shenv.logic_mut().set_alias(alias, &clean_body); - } else { - return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone())) - } - } - } else { unreachable!() } - Ok(()) -} diff --git a/src/builtin/cd.rs b/src/builtin/cd.rs deleted file mode 100644 index 99d4b0c..0000000 --- a/src/builtin/cd.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::prelude::*; - -pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs: _ } = rule { - let mut argv_iter = argv.into_iter(); - argv_iter.next(); // Ignore 'cd' - let dir_raw = argv_iter.next().map(|arg| shenv.input_slice(arg.span()).into()).unwrap_or(std::env::var("HOME")?); - let dir = PathBuf::from(&dir_raw); - std::env::set_current_dir(dir)?; - let new_dir = std::env::current_dir()?; - shenv.vars_mut().export("PWD",new_dir.to_str().unwrap()); - shenv.set_code(0); - } - Ok(()) -} diff --git a/src/builtin/control_flow.rs b/src/builtin/control_flow.rs deleted file mode 100644 index 09536f8..0000000 --- a/src/builtin/control_flow.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::prelude::*; - -pub fn sh_flow(node: Node, shenv: &mut ShEnv, kind: ShErrKind) -> ShResult<()> { - let rule = node.into_rule(); - let mut code: i32 = 0; - if let NdRule::Command { argv, redirs } = rule { - let mut argv_iter = argv.into_iter(); - while let Some(arg) = argv_iter.next() { - if let Ok(code_arg) = shenv.input_slice(arg.span()).parse() { - code = code_arg - } - } - } else { unreachable!() } - shenv.set_code(code); - // Our control flow keywords are used as ShErrKinds - // This design will halt the execution flow and start heading straight back upward - // Function returns and loop breaks/continues will be caught in the proper context to allow - // Execution to continue at the proper return point. - Err(ShErr::simple(kind, "")) -} diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs deleted file mode 100644 index 2faa719..0000000 --- a/src/builtin/echo.rs +++ /dev/null @@ -1,86 +0,0 @@ -use shellenv::jobs::{ChildProc, JobBldr}; - -use crate::prelude::*; - -bitflags! { - #[derive(Debug,Clone,Copy)] - pub struct EchoFlags: u32 { - const USE_ESCAPE = 0b0001; - const NO_ESCAPE = 0b0010; - const STDERR = 0b0100; - const NO_NEWLINE = 0b1000; - } -} - -pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - - if let NdRule::Command { argv, redirs } = rule { - let mut argv_iter = argv.into_iter().skip(1).peekable(); - let mut echo_flags = EchoFlags::empty(); - while let Some(arg) = argv_iter.peek() { - let blame = arg.span(); - let raw = arg.as_raw(shenv); - if raw.starts_with('-') { - let _ = argv_iter.next(); - let mut options = raw.strip_prefix('-').unwrap().chars(); - while let Some(opt) = options.next() { - match opt { - 'r' => echo_flags |= EchoFlags::STDERR, - 'n' => echo_flags |= EchoFlags::NO_NEWLINE, - 'e' => { - if echo_flags.contains(EchoFlags::NO_ESCAPE) { - return Err( - ShErr::full( - ShErrKind::ExecFail, - "the 'e' and 'E' flags are mutually exclusive", - shenv.get_input(), - blame - ) - ) - } - echo_flags |= EchoFlags::USE_ESCAPE; - } - 'E' => { - if echo_flags.contains(EchoFlags::USE_ESCAPE) { - return Err( - ShErr::full( - ShErrKind::ExecFail, - "the 'e' and 'E' flags are mutually exclusive", - shenv.get_input(), - blame - ) - ) - } - echo_flags |= EchoFlags::NO_ESCAPE; - } - _ => return Err( - ShErr::full( - ShErrKind::ExecFail, - format!("Unrecognized echo option"), - shenv.get_input(), - blame - ) - ) - } - } - } else { - break - } - } - let mut argv = argv_iter.collect::>().as_strings(shenv); - argv.retain(|arg| arg != "\n"); - log!(DEBUG,argv); - let mut formatted = argv.join(" "); - if !echo_flags.contains(EchoFlags::NO_NEWLINE) { - formatted.push('\n'); - } - - shenv.collect_redirs(redirs); - log!(DEBUG,"{:?}",shenv.ctx().redirs()); - shenv.ctx_mut().activate_rdrs()?; - write_out(formatted)?; - - } else { unreachable!() } - Ok(()) -} diff --git a/src/builtin/export.rs b/src/builtin/export.rs deleted file mode 100644 index d847348..0000000 --- a/src/builtin/export.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::prelude::*; - -pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs: _ } = rule { - let mut argv_iter = argv.into_iter(); - argv_iter.next(); // Ignore 'export' - while let Some(arg) = argv_iter.next() { - let arg_raw = arg.as_raw(shenv); - if let Some((var,val)) = arg_raw.split_once('=') { - shenv.vars_mut().export(var, &clean_string(val)); - } else { - eprintln!("Expected an assignment in export args, found this: {}", arg_raw) - } - } - } else { unreachable!() } - Ok(()) -} diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs deleted file mode 100644 index f0419be..0000000 --- a/src/builtin/jobctl.rs +++ /dev/null @@ -1,170 +0,0 @@ -use shellenv::jobs::JobCmdFlags; - -use crate::prelude::*; - -pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> { - let blame = node.span(); - let cmd = if fg { "fg" } else { "bg" }; - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs } = rule { - let mut argv_s = argv.drop_first().as_strings(shenv).into_iter(); - - if read_jobs(|j| j.get_fg().is_some()) { - return Err( - ShErr::full( - ShErrKind::InternalErr, - format!("Somehow called {} with an existing foreground job",cmd), - shenv.get_input(), - blame - ) - ) - } - - let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) { - id - } else { - return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", shenv.get_input(), blame)) - }; - - let tabid = match argv_s.next() { - Some(arg) => parse_job_id(&arg, blame.clone(),shenv)?, - None => curr_job_id - }; - - let mut job = write_jobs(|j| { - let id = JobID::TableID(tabid); - let query_result = j.query(id.clone()); - if query_result.is_some() { - Ok(j.remove_job(id.clone()).unwrap()) - } else { - Err( - ShErr::full( - ShErrKind::ExecFail, - format!("Job id `{}' not found", tabid), - shenv.get_input(), - blame - ) - ) - } - })?; - - job.killpg(Signal::SIGCONT)?; - - if fg { - write_jobs(|j| j.new_fg(job))?; - } else { - let job_order = read_jobs(|j| j.order().to_vec()); - write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?; - write_jobs(|j| j.insert_job(job, true))?; - } - shenv.set_code(0); - } else { unreachable!() } - Ok(()) -} - -fn parse_job_id(arg: &str, blame: Rc>, shenv: &mut ShEnv) -> ShResult { - if arg.starts_with('%') { - let arg = arg.strip_prefix('%').unwrap(); - if arg.chars().all(|ch| ch.is_ascii_digit()) { - Ok(arg.parse::().unwrap()) - } else { - let result = write_jobs(|j| { - let query_result = j.query(JobID::Command(arg.into())); - query_result.map(|job| job.tabid().unwrap()) - }); - match result { - Some(id) => Ok(id), - None => Err( - ShErr::full( - ShErrKind::InternalErr, - "Found a job but no table id in parse_job_id()", - shenv.get_input(), - blame - ) - ) - } - } - } else if arg.chars().all(|ch| ch.is_ascii_digit()) { - let result = write_jobs(|j| { - let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::().unwrap()))); - if let Some(job) = pgid_query_result { - return Some(job.tabid().unwrap()) - } - - if arg.parse::().unwrap() > 0 { - let table_id_query_result = j.query(JobID::TableID(arg.parse::().unwrap())); - return table_id_query_result.map(|job| job.tabid().unwrap()); - } - - None - }); - - match result { - Some(id) => Ok(id), - None => Err( - ShErr::full( - ShErrKind::InternalErr, - "Found a job but no table id in parse_job_id()", - shenv.get_input(), - blame - ) - ) - } - } else { - Err( - ShErr::full( - ShErrKind::SyntaxErr, - format!("Invalid fd arg: {}", arg), - shenv.get_input(), - blame - ) - ) - } -} - -pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs } = rule { - let mut argv = argv.drop_first().into_iter(); - - let mut flags = JobCmdFlags::empty(); - while let Some(arg) = argv.next() { - let arg_s = shenv.input_slice(arg.span()); - let mut chars = arg_s.chars().peekable(); - if chars.peek().is_none_or(|ch| *ch != '-') { - return Err( - ShErr::full( - ShErrKind::SyntaxErr, - "Invalid flag in jobs call", - shenv.get_input(), - arg.span() - ) - ) - } - chars.next(); - while let Some(ch) = chars.next() { - let flag = match ch { - 'l' => JobCmdFlags::LONG, - 'p' => JobCmdFlags::PIDS, - 'n' => JobCmdFlags::NEW_ONLY, - 'r' => JobCmdFlags::RUNNING, - 's' => JobCmdFlags::STOPPED, - _ => return Err( - ShErr::full( - ShErrKind::SyntaxErr, - "Invalid flag in jobs call", - shenv.get_input(), - arg.span() - ) - ) - - }; - flags |= flag - } - } - write_jobs(|j| j.print_jobs(flags))?; - shenv.set_code(0); - } else { unreachable!() } - - Ok(()) -} diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs deleted file mode 100644 index 7ca97f4..0000000 --- a/src/builtin/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub mod echo; -pub mod cd; -pub mod pwd; -pub mod export; -pub mod jobctl; -pub mod read; -pub mod alias; -pub mod control_flow; -pub mod source; - -pub const BUILTINS: [&str;14] = [ - "echo", - "cd", - "pwd", - "export", - "fg", - "bg", - "jobs", - "read", - "alias", - "exit", - "continue", - "return", - "break", - "source", -]; diff --git a/src/builtin/pwd.rs b/src/builtin/pwd.rs deleted file mode 100644 index 8a22f96..0000000 --- a/src/builtin/pwd.rs +++ /dev/null @@ -1,17 +0,0 @@ -use shellenv::jobs::{ChildProc, JobBldr}; - -use crate::prelude::*; - -pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv: _, redirs } = rule { - let mut pwd = shenv.vars().get_var("PWD").to_string(); - pwd.push('\n'); - - shenv.collect_redirs(redirs); - shenv.ctx_mut().activate_rdrs()?; - write_out(pwd)?; - - } else { unreachable!() } - Ok(()) -} diff --git a/src/builtin/read.rs b/src/builtin/read.rs deleted file mode 100644 index 9e1a772..0000000 --- a/src/builtin/read.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::prelude::*; - -pub fn read_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs: _ } = rule { - let argv = argv.drop_first(); - let mut argv_iter = argv.iter(); - // TODO: properly implement redirections - // using activate_redirs() was causing issues, may require manual handling - - let mut buf = vec![0u8; 1024]; - let bytes_read = read(0, &mut buf)?; - buf.truncate(bytes_read); - - let read_input = String::from_utf8_lossy(&buf).trim_end().to_string(); - - if let Some(var) = argv_iter.next() { - /* - let words: Vec<&str> = read_input.split_whitespace().collect(); - - for (var, value) in argv_iter.zip(words.iter().chain(std::iter::repeat(&""))) { - shenv.vars_mut().set_var(&var.to_string(), value); - } - - // Assign the rest of the string to the first variable if there's only one - if argv.len() == 1 { - shenv.vars_mut().set_var(&first_var.to_string(), &read_input); - } - */ - let var_name = shenv.input_slice(var.span()).to_string(); - shenv.vars_mut().set_var(&var_name, &read_input); - } - } else { - unreachable!() - } - - log!(TRACE, "leaving read"); - shenv.set_code(0); - Ok(()) -} diff --git a/src/builtin/source.rs b/src/builtin/source.rs deleted file mode 100644 index 0f5d337..0000000 --- a/src/builtin/source.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::prelude::*; - -pub fn source(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs } = rule { - shenv.collect_redirs(redirs); - let mut argv_iter = argv.into_iter().skip(1); - while let Some(arg) = argv_iter.next() { - let arg_raw = arg.as_raw(shenv); - let arg_path = PathBuf::from(arg_raw); - shenv.source_file(arg_path)?; - } - } else { unreachable!() } - Ok(()) -} diff --git a/src/execute/mod.rs b/src/execute/mod.rs deleted file mode 100644 index 43c4e06..0000000 --- a/src/execute/mod.rs +++ /dev/null @@ -1,514 +0,0 @@ -use crate::{expand::{arithmetic::expand_arith_string, tilde::expand_tilde_string, vars::expand_string}, prelude::*}; -use shellenv::jobs::{ChildProc, JobBldr}; - -pub mod shellcmd; - - -pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> { - let input = input.into(); - shenv.new_input(&input); - let total_time = std::time::Instant::now(); - - let token_time = std::time::Instant::now(); - let token_stream = Lexer::new(input,shenv).lex(); - - let token_stream = expand_aliases(token_stream, shenv); - for token in &token_stream { - log!(TRACE, token); - log!(TRACE, "{}",token.as_raw(shenv)); - } - log!(INFO, "Tokenizing done in {:?}", token_time.elapsed()); - - let parse_time = std::time::Instant::now(); - let syn_tree = Parser::new(token_stream,shenv).parse()?; - log!(TRACE,syn_tree); - log!(INFO, "Parsing done in {:?}", parse_time.elapsed()); - if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) { - shenv.save_io()?; - } - - let exec_time = std::time::Instant::now(); - if let Err(e) = Executor::new(syn_tree, shenv).walk() { - if let ShErrKind::CleanExit = e.kind() { - let code = shenv.get_code(); - sh_quit(code); - } else { - if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) { - shenv.reset_io()?; - } - return Err(e.into()) - } - } - log!(INFO, "Executing done in {:?}", exec_time.elapsed()); - log!(INFO, "Total time spent: {:?}", total_time.elapsed()); - if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) { - shenv.reset_io()?; - } - log!(INFO, "Io reset"); - Ok(()) -} - -pub struct Executor<'a> { - ast: SynTree, - shenv: &'a mut ShEnv -} - -impl<'a> Executor<'a> { - pub fn new(ast: SynTree, shenv: &'a mut ShEnv) -> Self { - Self { ast, shenv } - } - pub fn walk(&mut self) -> ShResult<()> { - self.shenv.inputman_mut().push_state(); - log!(TRACE, "Starting walk"); - while let Some(node) = self.ast.next_node() { - if let NdRule::CmdList { cmds } = node.clone().into_rule() { - log!(TRACE, "{:?}", cmds); - exec_list(cmds, self.shenv).try_blame(node.as_raw(self.shenv),node.span())? - } else { unreachable!() } - } - self.shenv.inputman_mut().pop_state(); - log!(TRACE, "passed"); - Ok(()) - } -} - -fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult<()> { - log!(TRACE, "Executing list"); - let mut list = VecDeque::from(list); - while let Some(cmd_info) = list.fpop() { - let guard = cmd_info.0; - let cmd = cmd_info.1; - - if let Some(guard) = guard { - let code = shenv.get_code(); - match guard { - CmdGuard::And => { - if code != 0 { break; } - } - CmdGuard::Or => { - if code == 0 { break; } - } - } - } - dispatch_node(cmd, shenv)?; - } - Ok(()) -} - -fn dispatch_node(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let node_raw = node.as_raw(shenv); - let span = node.span(); - match *node.rule() { - NdRule::Command {..} | - NdRule::Subshell {..} | - NdRule::Assignment {..} => dispatch_command(node, shenv).try_blame(node_raw, span)?, - NdRule::IfThen {..} => shellcmd::exec_if(node, shenv).try_blame(node_raw, span)?, - NdRule::Loop {..} => shellcmd::exec_loop(node, shenv).try_blame(node_raw, span)?, - NdRule::ForLoop {..} => shellcmd::exec_for(node, shenv).try_blame(node_raw, span)?, - NdRule::Case {..} => shellcmd::exec_case(node, shenv).try_blame(node_raw, span)?, - NdRule::FuncDef {..} => exec_funcdef(node,shenv).try_blame(node_raw, span)?, - NdRule::Pipeline {..} => exec_pipeline(node, shenv).try_blame(node_raw, span)?, - _ => unimplemented!("No support for NdRule::{:?} yet", node.rule()) - } - Ok(()) -} - -fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let mut is_builtin = false; - let mut is_func = false; - let mut is_subsh = false; - let mut is_assign = false; - if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() { - if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) { - *argv = expand_argv(argv.to_vec(), shenv)?; - } - let cmd = argv.first().unwrap().as_raw(shenv); - if shenv.logic().get_function(&cmd).is_some() { - is_func = true; - } else if node.flags().contains(NdFlag::BUILTIN) { - is_builtin = true; - } - } else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() { - if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) { - *argv = expand_argv(argv.to_vec(), shenv)?; - } - is_subsh = true; - } else if let NdRule::Assignment { assignments: _, cmd: _ } = node.rule() { - is_assign = true; - } else { unreachable!() } - - if is_builtin { - exec_builtin(node, shenv)?; - } else if is_func { - exec_func(node, shenv)?; - } else if is_subsh { - exec_subshell(node, shenv)?; - } else if is_assign { - exec_assignment(node, shenv)?; - } else { - exec_cmd(node, shenv)?; - } - Ok(()) -} - -fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::Command { argv, redirs } = rule { - let mut argv_iter = argv.into_iter(); - let func_name = argv_iter.next().unwrap().as_raw(shenv); - let body = shenv.logic().get_function(&func_name).unwrap().to_string(); - let snapshot = shenv.clone(); - shenv.vars_mut().reset_params(); - shenv.ctx_mut().set_flag(ExecFlags::IN_FUNC); - while let Some(arg) = argv_iter.next() { - let arg_raw = shenv.input_slice(arg.span()).to_string(); - shenv.vars_mut().bpush_arg(&arg_raw); - } - shenv.collect_redirs(redirs); - - match exec_input(body, shenv) { - Ok(()) => { - *shenv = snapshot; - return Ok(()) - } - Err(e) if e.kind() == ShErrKind::FuncReturn => { - let code = shenv.get_code(); - *shenv = snapshot; - shenv.set_code(code); - return Ok(()) - } - Err(e) => { - *shenv = snapshot; - return Err(e.into()) - } - } - } - Ok(()) -} - -fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::FuncDef { name, body } = rule { - let name_raw = name.as_raw(shenv); - let name = name_raw.trim_end_matches("()"); - let body_raw = body.as_raw(shenv); - let body = body_raw[1..body_raw.len() - 1].trim(); - - shenv.logic_mut().set_function(name, body); - } else { unreachable!() } - Ok(()) -} - -fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let snapshot = shenv.clone(); - shenv.vars_mut().reset_params(); - let is_bg = node.flags().contains(NdFlag::BACKGROUND); - let rule = node.into_rule(); - if let NdRule::Subshell { body, argv, redirs } = rule { - if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { - shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); // Allow sub-forks in this case - shenv.collect_redirs(redirs); - if let Err(e) = shenv.ctx_mut().activate_rdrs() { - write_err(e)?; - exit(1); - } - for arg in argv { - let arg_raw = &arg.as_raw(shenv); - shenv.vars_mut().bpush_arg(arg_raw); - } - let body_raw = body.as_raw(shenv); - - match exec_input(body_raw, shenv) { - Ok(()) => exit(0), - Err(e) => { - eprintln!("{}",e); - exit(1); - } - } - } else { - match unsafe { fork()? } { - Child => { - shenv.collect_redirs(redirs); - if let Err(e) = shenv.ctx_mut().activate_rdrs() { - write_err(e)?; - exit(1); - } - for arg in argv { - let arg_raw = &arg.as_raw(shenv); - shenv.vars_mut().bpush_arg(arg_raw); - } - let body_raw = body.as_raw(shenv); - match exec_input(body_raw, shenv) { - Ok(()) => exit(0), - Err(e) => { - eprintln!("{}",e); - exit(1); - } - } - } - Parent { child } => { - *shenv = snapshot; - let children = vec![ - ChildProc::new(child, Some("anonymous subshell"), Some(child))? - ]; - let job = JobBldr::new() - .with_children(children) - .with_pgid(child) - .build(); - dispatch_job(job, is_bg, shenv)?; - } - } - } - } else { unreachable!() } - Ok(()) -} - -fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(TRACE, "Executing builtin"); - let command = if let NdRule::Command { argv, redirs: _ } = node.rule() { - argv.first().unwrap().as_raw(shenv) - } else { unreachable!() }; - - log!(TRACE, "{}", command.as_str()); - match command.as_str() { - "echo" => echo(node, shenv)?, - "cd" => cd(node,shenv)?, - "pwd" => pwd(node, shenv)?, - "export" => export(node, shenv)?, - "jobs" => jobs(node, shenv)?, - "fg" => continue_job(node, shenv, true)?, - "bg" => continue_job(node, shenv, false)?, - "read" => read_builtin(node, shenv)?, - "alias" => alias(node, shenv)?, - "exit" => sh_flow(node, shenv, ShErrKind::CleanExit)?, - "return" => sh_flow(node, shenv, ShErrKind::FuncReturn)?, - "break" => sh_flow(node, shenv, ShErrKind::LoopBreak)?, - "continue" => sh_flow(node, shenv, ShErrKind::LoopContinue)?, - "source" => source(node, shenv)?, - _ => unimplemented!("Have not yet implemented support for builtin `{}'",command) - } - log!(TRACE, "done"); - Ok(()) -} - -fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(TRACE, "Executing assignment"); - let rule = node.into_rule(); - if let NdRule::Assignment { assignments, cmd } = rule { - log!(TRACE, "Assignments: {:?}", assignments); - log!(TRACE, "Command: {:?}", cmd); - let mut assigns = assignments.into_iter(); - if let Some(cmd) = cmd { - let saved_env = shenv.vars().env().clone(); - while let Some(token) = assigns.next() { - let raw = token.as_raw(shenv); - if let Some((var,val)) = raw.split_once('=') { - let val_rule = Lexer::get_rule(&val); - if EXPANSIONS.contains(&val_rule) { - let exp = match val_rule { - TkRule::ArithSub => expand_arith_string(val,shenv)?, - TkRule::DQuote => expand_string(val, shenv)?, - TkRule::TildeSub => expand_tilde_string(val), - TkRule::VarSub => { - let val = shenv.vars().get_var(var); - val.to_string() - } - _ => unimplemented!() - }; - shenv.vars_mut().export(var, &exp); - } else { - shenv.vars_mut().export(var, val); - } - } - } - dispatch_command(*cmd, shenv)?; - *shenv.vars_mut().env_mut() = saved_env; - } else { - while let Some(token) = assigns.next() { - let raw = token.as_raw(shenv); - if let Some((var,val)) = raw.split_once('=') { - let val_rule = Lexer::get_rule(&val); - if EXPANSIONS.contains(&val_rule) { - let exp = match val_rule { - TkRule::ArithSub => expand_arith_string(val,shenv)?, - TkRule::DQuote => expand_string(val, shenv)?, - TkRule::TildeSub => expand_tilde_string(val), - TkRule::VarSub => { - let val = shenv.vars().get_var(var); - val.to_string() - } - _ => unimplemented!() - }; - shenv.vars_mut().set_var(var, &exp); - } else { - shenv.vars_mut().set_var(var, val); - } - } - } - } - } else { unreachable!() } - Ok(()) -} - -fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(TRACE, "Executing pipeline"); - let is_bg = node.flags().contains(NdFlag::BACKGROUND); - let rule = node.into_rule(); - if let NdRule::Pipeline { cmds } = rule { - let mut prev_rpipe: Option = None; - let mut cmds = VecDeque::from(cmds); - let mut pgid = None; - let mut cmd_names = vec![]; - let mut pids = vec![]; - - while let Some(cmd) = cmds.pop_front() { - let (mut r_pipe, mut w_pipe) = if cmds.is_empty() { - // If we are on the last command, don't make new pipes - (None,None) - } else { - let (r_pipe, w_pipe) = c_pipe()?; - (Some(r_pipe),Some(w_pipe)) - }; - if let NdRule::Command { argv, redirs: _ } = cmd.rule() { - let cmd_name = argv.first().unwrap().as_raw(shenv); - cmd_names.push(cmd_name); - } else if let NdRule::Subshell {..} = cmd.rule() { - cmd_names.push("subshell".to_string()); - } else { - cmd_names.push("shell cmd".to_string()); - } - - match unsafe { fork()? } { - Child => { - // Set NO_FORK since we are already in a fork, to prevent unnecessarily forking again - shenv.ctx_mut().set_flag(ExecFlags::NO_FORK); - // We close this r_pipe since it's the one the next command will use, so not useful here - if let Some(r_pipe) = r_pipe.take() { - close(r_pipe)?; - } - - // Create some redirections - if let Some(w_pipe) = w_pipe.take() { - if !cmds.is_empty() { - let wpipe_redir = Redir::output(1, w_pipe); - shenv.ctx_mut().push_rdr(wpipe_redir); - } - } - // Use the r_pipe created in the last iteration - if let Some(prev_rpipe) = prev_rpipe.take() { - let rpipe_redir = Redir::input(0, prev_rpipe); - shenv.ctx_mut().push_rdr(rpipe_redir); - } - - if let Err(e) = dispatch_node(cmd, shenv) { - eprintln!("{}",e); - exit(1); - } - exit(0); - } - Parent { child } => { - // Close the write pipe out here to signal EOF - if let Some(w_pipe) = w_pipe.take() { - close(w_pipe)?; - } - if pgid.is_none() { - pgid = Some(child); - } - pids.push(child); - if let Some(pipe) = prev_rpipe { - close(pipe)?; - } - prev_rpipe = r_pipe; - } - } - } - let mut children = vec![]; - for (i,pid) in pids.iter().enumerate() { - let command = cmd_names.get(i).unwrap(); - let child = ChildProc::new(*pid, Some(&command), pgid)?; - children.push(child); - } - let job = JobBldr::new() - .with_children(children) - .with_pgid(pgid.unwrap()) - .build(); - dispatch_job(job, is_bg, shenv)?; - } else { unreachable!() } - Ok(()) -} - -fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - log!(TRACE, "Executing command"); - let blame = node.span(); - let is_bg = node.flags().contains(NdFlag::BACKGROUND); - let rule = node.into_rule(); - - if let NdRule::Command { argv, redirs } = rule { - let (argv,envp) = prep_execve(argv, shenv); - let command = argv.first().unwrap().to_string(); - if get_bin_path(&command, shenv).is_some() { - - log!(TRACE, "{:?}",shenv.ctx().flags()); - if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { - log!(TRACE, "Not forking"); - shenv.collect_redirs(redirs); - log!(TRACE, "{:?}",shenv.ctx().redirs()); - if let Err(e) = shenv.ctx_mut().activate_rdrs() { - eprintln!("{:?}",e); - exit(1); - } - if let Err(errno) = execvpe(command, argv, envp) { - if errno != Errno::EFAULT { - exit(errno as i32); - } - } - } else { - log!(TRACE, "Forking"); - match unsafe { fork()? } { - Child => { - log!(TRACE, redirs); - shenv.collect_redirs(redirs); - if let Err(e) = shenv.ctx_mut().activate_rdrs() { - eprintln!("{:?}",e); - exit(1); - } - execvpe(command, argv, envp)?; - exit(1); - } - Parent { child } => { - let children = vec![ - ChildProc::new(child, Some(&command), Some(child))? - ]; - let job = JobBldr::new() - .with_children(children) - .with_pgid(child) - .build(); - log!(TRACE, "New job: {:?}", job); - dispatch_job(job, is_bg, shenv)?; - } - } - } - } else { - return Err(ShErr::full(ShErrKind::CmdNotFound, format!("{}", command), shenv.get_input(), blame)) - } - } else { unreachable!("Found this rule in exec_cmd: {:?}", rule) } - Ok(()) -} - -fn prep_execve(argv: Vec, shenv: &mut ShEnv) -> (Vec, Vec) { - log!(TRACE, "Preparing execvpe args"); - let argv_s = argv.as_strings(shenv); - log!(TRACE, argv_s); - - let mut envp = vec![]; - let mut env_vars = shenv.vars().env().iter(); - while let Some(entry) = env_vars.next() { - let key = entry.0; - let val = entry.1; - let formatted = format!("{}={}",key,val); - envp.push(formatted); - } - log!(TRACE, argv_s); - log!(DEBUG, argv_s); - (argv_s, envp) -} diff --git a/src/execute/shellcmd.rs b/src/execute/shellcmd.rs deleted file mode 100644 index 0e236f4..0000000 --- a/src/execute/shellcmd.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::prelude::*; - -pub fn exec_if(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - if let NdRule::IfThen { cond_blocks, else_block, redirs } = rule { - shenv.collect_redirs(redirs); - if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { - shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); - } - let mut cond_blocks = cond_blocks.into_iter(); - - while let Some(block) = cond_blocks.next() { - let cond = block.0; - let body = block.1; - let ret = shenv.exec_as_cond(cond)?; - if ret == 0 { - shenv.exec_as_body(body)?; - return Ok(()) - } - } - - if let Some(block) = else_block { - shenv.exec_as_body(block)?; - } - } else { unreachable!() } - Ok(()) -} - -pub fn exec_loop(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - - if let NdRule::Loop { kind, cond, body, redirs } = rule { - shenv.collect_redirs(redirs); - - if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { - shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); - } - - loop { - let ret = shenv.exec_as_cond(cond.clone())?; - match kind { - LoopKind::While => { - if ret == 0 { - match shenv.exec_as_body(body.clone()) { - Ok(_) => continue, - Err(e) => { - match e.kind() { - ShErrKind::LoopContinue => continue, - ShErrKind::LoopBreak => break, - _ => return Err(e.into()) - } - } - } - } else { break } - } - LoopKind::Until => { - if ret != 0 { - match shenv.exec_as_body(body.clone()) { - Ok(_) => continue, - Err(e) => { - match e.kind() { - ShErrKind::LoopContinue => continue, - ShErrKind::LoopBreak => break, - _ => return Err(e.into()) - } - } - } - } else { break } - } - } - } - } else { unreachable!() } - Ok(()) -} - -pub fn exec_for(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - - if let NdRule::ForLoop { vars, arr, body, redirs } = rule { - shenv.collect_redirs(redirs); - let saved_vars = shenv.vars().clone(); - - if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { - shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); - } - log!(DEBUG, vars); - log!(DEBUG, arr); - - for chunk in arr.chunks(vars.len()) { - log!(DEBUG, "input: {}", shenv.get_input()); - for (var,value) in vars.iter().zip(chunk.iter()) { - let var = var.as_raw(shenv); - let val = value.as_raw(shenv); - log!(DEBUG,var); - log!(DEBUG,val); - shenv.vars_mut().set_var(&var, &val); - } - - if chunk.len() < vars.len() { - for var in &vars[chunk.len()..] { // If 'vars' is longer than the chunk, then unset the orphaned vars - let var = var.as_raw(shenv); - log!(DEBUG, "unsetting"); - log!(DEBUG, var); - shenv.vars_mut().unset_var(&var); - } - } - - shenv.exec_as_body(body.clone())?; - } - *shenv.vars_mut() = saved_vars; - - } else { unreachable!() } - Ok(()) -} - -pub fn exec_case(node: Node, shenv: &mut ShEnv) -> ShResult<()> { - let rule = node.into_rule(); - - if let NdRule::Case { pat, blocks, redirs } = rule { - shenv.collect_redirs(redirs); - let mut blocks_iter = blocks.into_iter(); - let pat_raw = expand_token(pat, shenv)? - .iter() - .map(|tk| tk.as_raw(shenv)) - .collect::>() - .join(" "); - - while let Some((block_pat, block)) = blocks_iter.next() { - let block_pat_raw = block_pat.as_raw(shenv); - let block_pat_raw = block_pat_raw.trim_end_matches(')'); - if block_pat_raw == "*" { - let _ret = shenv.exec_as_body(block)?; - return Ok(()) - } else if block_pat_raw.contains('|') { - let pats = block_pat_raw.split('|'); - for pat in pats { - if pat_raw.trim() == pat.trim() { - let _ret = shenv.exec_as_body(block)?; - return Ok(()) - } - } - } else if pat_raw.trim() == block_pat_raw.trim() { - let _ret = shenv.exec_as_body(block)?; - return Ok(()) - } - } - } else { unreachable!() } - Ok(()) -} diff --git a/src/expand.rs b/src/expand.rs new file mode 100644 index 0000000..9506257 --- /dev/null +++ b/src/expand.rs @@ -0,0 +1,102 @@ +use crate::{libsh::error::{ShErr, ShErrKind}, parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkErr, TkFlags, TkState, TkRule}, prelude::*, state::read_vars}; + +/// Variable substitution marker +pub const VAR_SUB: char = '\u{fdd0}'; + +impl<'t> Tk<'t> { + /// Create a new expanded token + /// + /// params + /// tokens: A vector of raw tokens lexed from the expansion result + /// span: The span of the original token that is being expanded + /// flags: some TkFlags + pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self { + let exp = Expander::new(self).expand(); + let class = TkRule::Expanded { exp }; + Self { class, span, err_span: None, flags, err: TkErr::Null } + } + pub fn get_words(&self) -> Vec { + match &self.class { + TkRule::Expanded { exp } => exp.clone(), + _ => vec![self.to_string()] + } + } +} + +pub struct Expander { + raw: String, +} + +impl<'t> Expander { + pub fn new(raw: Tk<'t>) -> Self { + let unescaped = unescape_str(raw.span.as_str()); + Self { raw: unescaped } + } + pub fn expand(&'t mut self) -> Vec { + self.raw = self.expand_raw(); + let tokens: Vec<_> = LexStream::new(&self.raw, LexFlags::RAW) + .filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI)) + .map(|tk| tk.to_string()) + .collect(); + tokens + } + pub fn expand_raw(&self) -> String { + let mut chars = self.raw.chars(); + let mut result = String::new(); + let mut var_name = String::new(); + let mut in_brace = false; + + // TODO: implement error handling for unclosed braces + while let Some(ch) = chars.next() { + match ch { + VAR_SUB => { + while let Some(ch) = chars.next() { + match ch { + '{' => in_brace = true, + '}' if in_brace => { + let var_val = read_vars(|v| v.get_var(&var_name)); + result.push_str(&var_val); + var_name.clear(); + break + } + _ if is_hard_sep(ch) => { + let var_val = read_vars(|v| v.get_var(&var_name)); + result.push_str(&var_val); + result.push(ch); + var_name.clear(); + break + } + _ => var_name.push(ch), + } + } + if !var_name.is_empty() { + let var_val = read_vars(|v| v.get_var(&var_name)); + result.push_str(&var_val); + var_name.clear(); + } + } + _ => result.push(ch) + } + } + result + } +} + +/// Clean up a single layer of escape characters, and then replace control characters like '$' with a non-character unicode representation that is unmistakable by the rest of the code +pub fn unescape_str(raw: &str) -> String { + let mut chars = raw.chars(); + let mut result = String::new(); + + while let Some(ch) = chars.next() { + match ch { + '\\' => { + if let Some(next_ch) = chars.next() { + result.push(next_ch) + } + } + '$' => result.push(VAR_SUB), + _ => result.push(ch) + } + } + result +} diff --git a/src/expand/alias.rs b/src/expand/alias.rs deleted file mode 100644 index c434550..0000000 --- a/src/expand/alias.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::{parse::lex::SEPARATORS, prelude::*}; - -pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec { - let mut tokens = vec![]; - let mut work_stack = VecDeque::new(); - let logic = shenv.logic().clone(); - let mut done = false; - - // Start with the candidate token in the work queue - work_stack.bpush(candidate); - - // Process until there are no more tokens in the queue - while !done { - done = true; - while let Some(token) = work_stack.fpop() { - if token.rule() == TkRule::Ident { - let cand_str = token.as_raw(shenv); - if let Some(alias) = logic.get_alias(&cand_str) { - done = false; - if !token.span().borrow().expanded { - let mut new_tokens = shenv.expand_input(alias, token.span()); - new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace); - for token in &new_tokens { - tokens.push(token.clone()); - } - } - } else { - tokens.push(token); - } - } else { - tokens.push(token); - } - } - if !done { - work_stack.extend(tokens.drain(..)); - } - } - tokens -} - -pub fn expand_aliases(tokens: Vec, shenv: &mut ShEnv) -> Vec { - let mut stream = tokens.iter(); - let mut processed = vec![]; - let mut is_command = true; - while let Some(token) = stream.next() { - match token.rule() { - _ if SEPARATORS.contains(&token.rule()) => { - is_command = true; - processed.push(token.clone()); - } - TkRule::Case | TkRule::For => { - processed.push(token.clone()); - while let Some(token) = stream.next() { - processed.push(token.clone()); - if token.rule() == TkRule::Sep { - break - } - } - } - TkRule::Ident if is_command => { - is_command = false; - let mut alias_tokens = expand_alias(token.clone(), shenv); - if !alias_tokens.is_empty() { - processed.append(&mut alias_tokens); - } else { - processed.push(token.clone()); - } - } - _ => processed.push(token.clone()), - } - } - processed -} diff --git a/src/expand/arithmetic.rs b/src/expand/arithmetic.rs deleted file mode 100644 index d2d97f3..0000000 --- a/src/expand/arithmetic.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::prelude::*; - -use super::vars::expand_string; - -#[derive(Clone,PartialEq,Debug)] -pub enum ExprToken { - Number(f64), - Operator(Op), - OpenParen, - CloseParen -} - -#[derive(Clone,PartialEq,Debug)] -pub enum Op { - Add, - Sub, - Mul, - Div, - IntDiv, - Mod, - Pow -} - -impl Op { - pub fn precedence(&self) -> u8 { - match self { - Op::Add | Op::Sub => 1, - Op::Mul | Op::Div | Op::IntDiv | Op::Mod => 2, - Op::Pow => 3 - } - } - pub fn is_left_associative(&self) -> bool { - *self != Op::Pow - } -} - -fn tokenize_expr(expr: &str) -> ShResult> { - let mut chars = expr.chars().peekable(); - let mut tokens = vec![]; - - while let Some(ch) = chars.next() { - match ch { - '+' => tokens.push(ExprToken::Operator(Op::Add)), - '-' => tokens.push(ExprToken::Operator(Op::Sub)), - '*' => { - if chars.peek() == Some(&'*') { - chars.next(); - tokens.push(ExprToken::Operator(Op::Pow)); - } else { - tokens.push(ExprToken::Operator(Op::Mul)); - } - } - '/' => { - if chars.peek() == Some(&'/') { - chars.next(); - tokens.push(ExprToken::Operator(Op::IntDiv)); - } else { - tokens.push(ExprToken::Operator(Op::Div)); - } - } - '%' => tokens.push(ExprToken::Operator(Op::Mod)), - '(' => tokens.push(ExprToken::OpenParen), - ')' => tokens.push(ExprToken::CloseParen), - '0'..='9' => { - let mut number = ch.to_string(); - while let Some(next_ch) = chars.peek() { - if next_ch.is_ascii_digit() { - number.push(chars.next().unwrap()); - } else { - break; - } - } - let value = number.parse::().unwrap(); - tokens.push(ExprToken::Number(value)); - } - ' ' | '\t' => continue, // Skip whitespace - _ => return Err(ShErr::simple(ShErrKind::ParseErr, format!("Unexpected character in arithmetic expansion: {}",ch))), // Handle unexpected characters - } - } - - Ok(tokens) -} - -fn shunting_yard(tokens: Vec) -> ShResult> { - let mut sorted = vec![]; - let mut operators = vec![]; - - for token in tokens { - match token { - ExprToken::Number(_) => sorted.push(token.clone()), - ExprToken::Operator(ref op) => { - while let Some(top) = operators.last() { - if let ExprToken::Operator(top_op) = top { - if (op.is_left_associative() && op.precedence() <= top_op.precedence()) - || (!op.is_left_associative() && op.precedence() < top_op.precedence()) - { - sorted.push(operators.pop().unwrap()) - } else { - break - } - } else { - break - } - } - operators.push(token.clone()) - } - ExprToken::OpenParen => operators.push(token.clone()), - ExprToken::CloseParen => { - while let Some(top) = operators.pop() { - if matches!(top, ExprToken::OpenParen) { - break; - } - sorted.push(top); - } - } - } - } - - while let Some(op) = operators.pop() { - if matches!(op, ExprToken::OpenParen | ExprToken::CloseParen) { - return Err(ShErr::simple(ShErrKind::ParseErr, "Mismatched parenthesis in arithmetic expansion")) - } - sorted.push(op); - } - - Ok(sorted) -} - -pub fn eval_rpn(tokens: Vec) -> ShResult { - let mut stack = vec![]; - - for token in tokens { - match token { - ExprToken::Number(num) => stack.push(num), - ExprToken::Operator(op) => { - if stack.len() < 2 { - return Err(ShErr::simple(ShErrKind::ParseErr, "Not enough operands in arithmetic expansion")) - } - let rhs = stack.pop().unwrap(); - let lhs = stack.pop().unwrap(); - let result = match op { - Op::Add => lhs + rhs, - Op::Sub => lhs - rhs, - Op::Mul => lhs * rhs, - Op::Mod => lhs % rhs, - Op::Pow => lhs.powf(rhs), - Op::Div => { - if rhs == 0.0 { - return Err(ShErr::simple(ShErrKind::ParseErr, "Attempt to divide by zero in arithmetic expansion")) - } - lhs / rhs - } - Op::IntDiv => { - if rhs == 0.0 { - return Err(ShErr::simple(ShErrKind::ParseErr, "Attempt to divide by zero in arithmetic expansion")) - } - (lhs as i64 / rhs as i64) as f64 - } - }; - stack.push(result); - } - ExprToken::OpenParen => todo!(), - ExprToken::CloseParen => todo!(), - } - } - - Ok(stack.pop().unwrap()) -} - -pub fn expand_arith_token(token: Token, shenv: &mut ShEnv) -> ShResult { - // I mean hey it works - let token_raw = token.as_raw(shenv); - - let arith_raw = token_raw.trim_matches('`'); - - let result = expand_arith_string(arith_raw,shenv)?; - - let mut final_expansion = shenv.expand_input(&result, token.span()); - - Ok(final_expansion.pop().unwrap_or(token)) -} - -pub fn expand_arith_string(s: &str,shenv: &mut ShEnv) -> ShResult { - let mut exp = expand_string(s,shenv)?; - if exp.starts_with('`') && s.ends_with('`') { - exp = exp[1..exp.len() - 1].to_string(); - } - let expr_tokens = shunting_yard(tokenize_expr(&exp)?)?; - log!(DEBUG,expr_tokens); - let result = eval_rpn(expr_tokens)?.to_string(); - Ok(result) -} diff --git a/src/expand/brace.rs b/src/expand/brace.rs deleted file mode 100644 index 9de4c45..0000000 --- a/src/expand/brace.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::{expand::vars::expand_string, prelude::*}; - -pub fn expand_brace_token(token: Token, shenv: &mut ShEnv) -> ShResult> { - let raw = token.as_raw(shenv); - let raw_exp = expand_string(&raw, shenv)?; - log!(DEBUG, raw_exp); - let expanded = expand_brace_string(&raw_exp); - log!(DEBUG, expanded); - let mut new_tokens = shenv.expand_input(&expanded, token.span()); - new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace); - log!(DEBUG, new_tokens); - Ok(new_tokens) -} - -pub fn expand_brace_string(raw: &str) -> String { - let mut result = VecDeque::new(); - let mut stack = vec![]; - stack.push(raw.to_string()); - - while let Some(current) = stack.pop() { - if let Some((prefix,braces,suffix)) = get_brace_positions(¤t) { - let expanded = expand_brace_inner(&braces); - for part in expanded { - let formatted = format!("{prefix}{part}{suffix}"); - stack.push(formatted); - } - } else { - result.fpush(current); - } - } - - result.into_iter().collect::>().join(" ") -} - -pub fn get_brace_positions(slice: &str) -> Option<(String, String, String)> { - let mut chars = slice.chars().enumerate(); - let mut start = None; - let mut brc_count = 0; - while let Some((i,ch)) = chars.next() { - match ch { - '{' => { - if brc_count == 0 { - start = Some(i); - } - brc_count += 1; - } - '}' => { - brc_count -= 1; - if brc_count == 0 { - if let Some(start) = start { - let prefix = slice[..start].to_string(); - let braces = slice[start+1..i].to_string(); - let suffix = slice[i+1..].to_string(); - return Some((prefix,braces,suffix)) - } - } - } - _ => continue - } - } - None -} - -fn expand_brace_inner(inner: &str) -> Vec { - if inner.split_once("..").is_some() && !inner.contains(['{','}']) { - expand_range(inner) - } else { - split_list(inner) - } -} - -fn split_list(list: &str) -> Vec { - log!(DEBUG, list); - let mut chars = list.chars(); - let mut items = vec![]; - let mut curr_item = String::new(); - let mut brc_count = 0; - - while let Some(ch) = chars.next() { - match ch { - ',' if brc_count == 0 => { - if !curr_item.is_empty() { - items.push(std::mem::take(&mut curr_item)); - } - } - '{' => { - brc_count += 1; - curr_item.push(ch); - } - '}' => { - if brc_count == 0 { - return vec![list.to_string()]; - } - brc_count -= 1; - curr_item.push(ch); - } - _ => curr_item.push(ch), - } - } - if !curr_item.is_empty() { - items.push(std::mem::take(&mut curr_item)) - } - log!(DEBUG,items); - items -} - -fn expand_range(range: &str) -> Vec { - if let Some((left,right)) = range.split_once("..") { - // I know, I know - // This is checking to see if the range looks like "a..b" or "A..B" - // one character on both sides, both are letters, AND (both are uppercase OR both are lowercase) - if (left.len() == 1 && right.len() == 1) && - (left.chars().all(|ch| ch.is_ascii_alphanumeric() && right.chars().all(|ch| ch.is_ascii_alphanumeric()))) && - ( - (left.chars().all(|ch| ch.is_uppercase()) && right.chars().all(|ch| ch.is_uppercase())) || - (left.chars().all(|ch| ch.is_lowercase()) && right.chars().all(|ch| ch.is_lowercase())) - ) - { - expand_range_alpha(left, right) - } - else if right.chars().all(|ch| ch.is_ascii_digit()) && left.chars().all(|ch| ch.is_ascii_digit()) - { - expand_range_numeric(left, right) - } - else - { - vec![range.to_string()] - } - } else { - vec![range.to_string()] - } -} - -fn expand_range_alpha(left: &str, right: &str) -> Vec { - let start = left.chars().next().unwrap() as u8; - let end = right.chars().next().unwrap() as u8; - - if start > end { - (end..=start).rev().map(|c| (c as char).to_string()).collect() - } else { - (start..=end).map(|c| (c as char).to_string()).collect() - } -} - -fn expand_range_numeric(left: &str, right: &str) -> Vec { - let start = left.parse::().unwrap(); - let end = right.parse::().unwrap(); - (start..=end).map(|i| i.to_string()).collect() -} diff --git a/src/expand/cmdsub.rs b/src/expand/cmdsub.rs deleted file mode 100644 index adfe15b..0000000 --- a/src/expand/cmdsub.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::prelude::*; - -pub fn expand_cmdsub_token(token: Token, shenv: &mut ShEnv) -> ShResult> { - let cmdsub_raw = token.as_raw(shenv); - let output = expand_cmdsub_string(&cmdsub_raw, shenv)?; - let new_tokens = shenv.expand_input(&output, token.span()); - - Ok(new_tokens) -} - -pub fn expand_cmdsub_string(mut s: &str, shenv: &mut ShEnv) -> ShResult { - if s.starts_with("$(") && s.ends_with(')') { - s = &s[2..s.len() - 1]; // From '$(this)' to 'this' - } - - let (r_pipe,w_pipe) = c_pipe()?; - let pipe_redir = Redir::output(1, w_pipe); - let mut sub_shenv = shenv.clone(); - sub_shenv.ctx_mut().set_flag(ExecFlags::NO_FORK); - sub_shenv.collect_redirs(vec![pipe_redir]); - - match unsafe { fork()? } { - Child => { - close(r_pipe).ok(); - exec_input(s, &mut sub_shenv).abort_if_err(); - exit(0); - } - Parent { child: _ } => { - close(w_pipe).ok(); - } - } - let result = read_to_string(r_pipe); - close(r_pipe)?; - Ok(result?) -} diff --git a/src/expand/mod.rs b/src/expand/mod.rs deleted file mode 100644 index 8d3c001..0000000 --- a/src/expand/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -pub mod vars; -pub mod tilde; -pub mod alias; -pub mod cmdsub; -pub mod arithmetic; -pub mod prompt; -pub mod brace; - -use arithmetic::expand_arith_token; -use brace::expand_brace_token; -use cmdsub::expand_cmdsub_token; -use vars::{expand_string, expand_var}; -use tilde::expand_tilde_token; - -use crate::prelude::*; - -pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> ShResult> { - let mut processed = vec![]; - for arg in argv { - log!(TRACE, "{}",arg.as_raw(shenv)); - log!(TRACE, processed); - let mut expanded = expand_token(arg, shenv)?; - processed.append(&mut expanded); - } - Ok(processed) -} - -pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult> { - let mut processed = vec![]; - match token.rule() { - TkRule::DQuote => { - let dquote_exp = expand_string(&token.as_raw(shenv), shenv)?; - let mut expanded = shenv.expand_input(&dquote_exp, token.span()); - processed.append(&mut expanded); - } - TkRule::VarSub => { - let mut varsub_exp = expand_var(token.clone(), shenv); - processed.append(&mut varsub_exp); - } - TkRule::TildeSub => { - let tilde_exp = expand_tilde_token(token.clone(), shenv); - processed.push(tilde_exp); - } - TkRule::ArithSub => { - let arith_exp = expand_arith_token(token.clone(), shenv)?; - processed.push(arith_exp); - } - TkRule::BraceExp => { - let mut brace_exp = expand_brace_token(token, shenv)?; - processed.append(&mut brace_exp); - } - TkRule::CmdSub => { - let mut cmdsub_exp = expand_cmdsub_token(token.clone(), shenv)?; - processed.append(&mut cmdsub_exp); - } - _ => { - if token.rule() != TkRule::Ident { - log!(WARN, "found this in expand_token: {:?}", token.rule()); - } - processed.push(token.clone()) - } - } - Ok(processed) -} diff --git a/src/expand/prompt.rs b/src/expand/prompt.rs deleted file mode 100644 index 5948220..0000000 --- a/src/expand/prompt.rs +++ /dev/null @@ -1,385 +0,0 @@ -use crate::prelude::*; - -#[derive(Debug)] -pub enum PromptTk { - AsciiOct(i32), - Text(String), - AnsiSeq(String), - VisGrp, - UserSeq, - Runtime, - Weekday, - Dquote, - Squote, - Return, - Newline, - Pwd, - PwdShort, - Hostname, - HostnameShort, - ShellName, - Username, - PromptSymbol, - ExitCode, - SuccessSymbol, - FailureSymbol, - JobCount -} - -pub fn format_cmd_runtime(dur: std::time::Duration) -> String { - const ETERNITY: u128 = f32::INFINITY as u128; - let mut micros = dur.as_micros(); - let mut millis = 0; - let mut seconds = 0; - let mut minutes = 0; - let mut hours = 0; - let mut days = 0; - let mut weeks = 0; - let mut months = 0; - let mut years = 0; - let mut decades = 0; - let mut centuries = 0; - let mut millennia = 0; - let mut epochs = 0; - let mut aeons = 0; - let mut eternities = 0; - - if micros >= 1000 { - millis = micros / 1000; - micros %= 1000; - } - if millis >= 1000 { - seconds = millis / 1000; - millis %= 1000; - } - if seconds >= 60 { - minutes = seconds / 60; - seconds %= 60; - } - if minutes >= 60 { - hours = minutes / 60; - minutes %= 60; - } - if hours >= 24 { - days = hours / 24; - hours %= 24; - } - if days >= 7 { - weeks = days / 7; - days %= 7; - } - if weeks >= 4 { - months = weeks / 4; - weeks %= 4; - } - if months >= 12 { - years = months / 12; - weeks %= 12; - } - if years >= 10 { - decades = years / 10; - years %= 10; - } - if decades >= 10 { - centuries = decades / 10; - decades %= 10; - } - if centuries >= 10 { - millennia = centuries / 10; - centuries %= 10; - } - if millennia >= 1000 { - epochs = millennia / 1000; - millennia %= 1000; - } - if epochs >= 1000 { - aeons = epochs / 1000; - epochs %= aeons; - } - if aeons == ETERNITY { - eternities = aeons / ETERNITY; - aeons %= ETERNITY; - } - - // Format the result - let mut result = Vec::new(); - if eternities > 0 { - let mut string = format!("{} eternit", eternities); - if eternities > 1 { - string.push_str("ies"); - } else { - string.push('y'); - } - result.push(string) - } - if aeons > 0 { - let mut string = format!("{} aeon", aeons); - if aeons > 1 { - string.push('s') - } - result.push(string) - } - if epochs > 0 { - let mut string = format!("{} epoch", epochs); - if epochs > 1 { - string.push('s') - } - result.push(string) - } - if millennia > 0 { - let mut string = format!("{} millenni", millennia); - if millennia > 1 { - string.push_str("um") - } else { - string.push('a') - } - result.push(string) - } - if centuries > 0 { - let mut string = format!("{} centur", centuries); - if centuries > 1 { - string.push_str("ies") - } else { - string.push('y') - } - result.push(string) - } - if decades > 0 { - let mut string = format!("{} decade", decades); - if decades > 1 { - string.push('s') - } - result.push(string) - } - if years > 0 { - let mut string = format!("{} year", years); - if years > 1 { - string.push('s') - } - result.push(string) - } - if months > 0 { - let mut string = format!("{} month", months); - if months > 1 { - string.push('s') - } - result.push(string) - } - if weeks > 0 { - let mut string = format!("{} week", weeks); - if weeks > 1 { - string.push('s') - } - result.push(string) - } - if days > 0 { - let mut string = format!("{} day", days); - if days > 1 { - string.push('s') - } - result.push(string) - } - if hours > 0 { - let string = format!("{}h", hours); - result.push(string); - } - if minutes > 0 { - let string = format!("{}m", minutes); - result.push(string); - } - if seconds > 0 { - let string = format!("{}s", seconds); - result.push(string); - } - if millis > 0 { - let string = format!("{}ms",millis); - result.push(string); - } - if result.is_empty() && micros > 0 { - let string = format!("{}µs",micros); - result.push(string); - } - - result.join(" ") -} - -fn tokenize_prompt(raw: &str) -> Vec { - let mut chars = raw.chars().peekable(); - let mut tk_text = String::new(); - let mut tokens = vec![]; - - while let Some(ch) = chars.next() { - match ch { - '\\' => { - // Push any accumulated text as a token - if !tk_text.is_empty() { - tokens.push(PromptTk::Text(std::mem::take(&mut tk_text))); - } - - // Handle the escape sequence - if let Some(ch) = chars.next() { - match ch { - 'w' => tokens.push(PromptTk::Pwd), - 'W' => tokens.push(PromptTk::PwdShort), - 'h' => tokens.push(PromptTk::Hostname), - 'H' => tokens.push(PromptTk::HostnameShort), - 's' => tokens.push(PromptTk::ShellName), - 'u' => tokens.push(PromptTk::Username), - '$' => tokens.push(PromptTk::PromptSymbol), - 'n' => tokens.push(PromptTk::Text("\n".into())), - 'r' => tokens.push(PromptTk::Text("\r".into())), - 'T' => tokens.push(PromptTk::Runtime), - '\\' => tokens.push(PromptTk::Text("\\".into())), - '"' => tokens.push(PromptTk::Text("\"".into())), - '\'' => tokens.push(PromptTk::Text("'".into())), - 'e' => { - if chars.next() == Some('[') { - let mut params = String::new(); - - // Collect parameters and final character - while let Some(ch) = chars.next() { - match ch { - '0'..='9' | ';' | '?' | ':' => params.push(ch), // Valid parameter characters - 'A'..='Z' | 'a'..='z' => { // Final character (letter) - params.push(ch); - break; - } - _ => { - // Invalid character in ANSI sequence - tokens.push(PromptTk::Text(format!("\x1b[{params}"))); - break; - } - } - } - - tokens.push(PromptTk::AnsiSeq(format!("\x1b[{params}"))); - } else { - // Handle case where 'e' is not followed by '[' - tokens.push(PromptTk::Text("\\e".into())); - } - } - '0'..='7' => { - // Handle octal escape - let mut octal_str = String::new(); - octal_str.push(ch); - - // Collect up to 2 more octal digits - for _ in 0..2 { - if let Some(&next_ch) = chars.peek() { - if next_ch >= '0' && next_ch <= '7' { - octal_str.push(chars.next().unwrap()); - } else { - break; - } - } else { - break; - } - } - - // Parse the octal string into an integer - if let Ok(octal) = i32::from_str_radix(&octal_str, 8) { - tokens.push(PromptTk::AsciiOct(octal)); - } else { - // Fallback: treat as raw text - tokens.push(PromptTk::Text(format!("\\{octal_str}"))); - } - } - _ => { - // Unknown escape sequence: treat as raw text - tokens.push(PromptTk::Text(format!("\\{ch}"))); - } - } - } else { - // Handle trailing backslash - tokens.push(PromptTk::Text("\\".into())); - } - } - _ => { - // Accumulate non-escape characters - tk_text.push(ch); - } - } - } - - // Push any remaining text as a token - if !tk_text.is_empty() { - tokens.push(PromptTk::Text(tk_text)); - } - - tokens -} - -pub fn expand_prompt(raw: &str, shenv: &mut ShEnv) -> ShResult { - let mut tokens = tokenize_prompt(raw).into_iter(); - let mut result = String::new(); - - while let Some(token) = tokens.next() { - match token { - PromptTk::AsciiOct(_) => todo!(), - PromptTk::Text(txt) => result.push_str(&txt), - PromptTk::AnsiSeq(params) => result.push_str(¶ms), - PromptTk::Runtime => { - log!(INFO, "getting runtime"); - if let Some(runtime) = shenv.meta().get_runtime() { - log!(DEBUG, runtime); - let runtime_fmt = format_cmd_runtime(runtime); - result.push_str(&runtime_fmt); - } - } - PromptTk::Pwd => { - let mut pwd = std::env::var("PWD")?; - let home = std::env::var("HOME")?; - if pwd.starts_with(&home) { - pwd = pwd.replacen(&home, "~", 1); - } - result.push_str(&pwd); - } - PromptTk::PwdShort => { - let mut path = std::env::var("PWD")?; - let home = std::env::var("HOME")?; - if path.starts_with(&home) { - path = path.replacen(&home, "~", 1); - } - let pathbuf = PathBuf::from(&path); - let mut segments = pathbuf.iter().count(); - let mut path_iter = pathbuf.into_iter(); - while segments > 4 { - path_iter.next(); - segments -= 1; - } - let path_rebuilt: PathBuf = path_iter.collect(); - let mut path_rebuilt = path_rebuilt.to_str().unwrap().to_string(); - if path_rebuilt.starts_with(&home) { - path_rebuilt = path_rebuilt.replacen(&home, "~", 1); - } - result.push_str(&path_rebuilt); - } - PromptTk::Hostname => { - let hostname = std::env::var("HOSTNAME")?; - result.push_str(&hostname); - } - PromptTk::HostnameShort => todo!(), - PromptTk::ShellName => result.push_str("fern"), - PromptTk::Username => { - let username = std::env::var("USER")?; - result.push_str(&username); - } - PromptTk::PromptSymbol => { - let uid = std::env::var("UID")?; - let symbol = if &uid == "0" { - '#' - } else { - '$' - }; - result.push(symbol); - } - PromptTk::ExitCode => todo!(), - PromptTk::SuccessSymbol => todo!(), - PromptTk::FailureSymbol => todo!(), - PromptTk::JobCount => todo!(), - _ => unimplemented!() - } - } - - Ok(result) -} diff --git a/src/expand/tilde.rs b/src/expand/tilde.rs deleted file mode 100644 index afe3951..0000000 --- a/src/expand/tilde.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::prelude::*; - -pub fn expand_tilde_token(tilde_sub: Token, shenv: &mut ShEnv) -> Token { - let tilde_sub_raw = tilde_sub.as_raw(shenv); - let result = expand_tilde_string(&tilde_sub_raw); - if result == tilde_sub_raw { - return tilde_sub - } - shenv.expand_input(&result, tilde_sub.span()).pop().unwrap_or(tilde_sub) -} - -pub fn expand_tilde_string(s: &str) -> String { - if s.starts_with('~') { - let home = std::env::var("HOME").unwrap_or_default(); - s.replacen('~', &home, 1) - } else { - s.to_string() - } -} diff --git a/src/expand/vars.rs b/src/expand/vars.rs deleted file mode 100644 index 4996d6d..0000000 --- a/src/expand/vars.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::{parse::lex::Token, prelude::*}; - -use super::cmdsub::expand_cmdsub_string; - -pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec { - let var_name = var_sub.as_raw(shenv); - let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']); - let value = shenv.vars().get_var(var_name).to_string(); - - shenv.expand_input(&value, var_sub.span()) -} - -pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult { - log!(DEBUG, s); - let mut result = String::new(); - let mut var_name = String::new(); - let mut chars = s.chars().peekable(); - let mut in_brace = false; - - while let Some(ch) = chars.next() { - match ch { - '\\' => { - result.push(ch); - if let Some(next_ch) = chars.next() { - result.push(next_ch) - } - } - '$' => { - let mut expanded = false; - while let Some(ch) = chars.peek() { - if *ch == '"' || *ch == '`' { - break - } - let ch = chars.next().unwrap(); - log!(DEBUG,var_name); - match ch { - '{' if var_name.is_empty() => { - in_brace = true; - } - '}' if in_brace => { - let value = shenv.vars().get_var(&var_name); - result.push_str(value); - expanded = true; - break - } - '(' if var_name.is_empty() => { - let mut paren_count = 1; - var_name.push_str("$("); - while let Some(ch) = chars.next() { - match ch { - '(' => { - paren_count += 1; - var_name.push(ch); - } - ')' => { - paren_count -= 1; - var_name.push(ch); - if paren_count == 0 { - break - } - } - _ => var_name.push(ch) - } - } - let value = expand_cmdsub_string(&var_name, shenv)?; - result.push_str(&value); - expanded = true; - break - } - _ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => { - var_name.push(ch); - let value = shenv.vars().get_var(&var_name); - result.push_str(value); - expanded = true; - break - } - '@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => { - var_name.push(ch); - let value = shenv.vars().get_var(&var_name); - result.push_str(value); - expanded = true; - break - } - ' ' | '\t' | '\n' | ';' | ',' | '{' => { - let value = shenv.vars().get_var(&var_name); - result.push_str(value); - result.push(ch); - expanded = true; - break - } - _ => var_name.push(ch) - } - } - if !expanded { - let value = shenv.vars().get_var(&var_name); - result.push_str(value); - } - var_name.clear(); - } - _ => result.push(ch) - } - } - Ok(result) -} diff --git a/src/fern.rs b/src/fern.rs new file mode 100644 index 0000000..448fb42 --- /dev/null +++ b/src/fern.rs @@ -0,0 +1,43 @@ +pub mod prelude; +pub mod libsh; +pub mod prompt; +pub mod procio; +pub mod parse; +pub mod expand; +pub mod state; +#[cfg(test)] +pub mod tests; + +use std::process::exit; + +use parse::{execute::{get_pipe_stack, Dispatcher}, lex::{LexFlags, LexStream}, ParseResult, ParseStream}; +use state::write_vars; + +fn main() { + loop { + let input = prompt::read_line().unwrap(); + if input == "quit" { break }; + write_vars(|v| v.new_var("foo", "bar")); + + let mut tokens = vec![]; + for token in LexStream::new(&input, LexFlags::empty()) { + if token.is_err() { + let error = format!("{:?}: {}",token.err,token.err_span.unwrap().as_str()); + panic!("{error}"); + } + tokens.push(token); + } + + let mut nodes = vec![]; + for result in ParseStream::new(tokens) { + match result { + ParseResult::Error(e) => panic!("{}",e), + ParseResult::Match(node) => nodes.push(node), + _ => unreachable!() + } + } + + let mut dispatcher = Dispatcher::new(nodes); + dispatcher.begin_dispatch().unwrap(); + } +} diff --git a/src/filefilefile.txt b/src/filefilefile.txt deleted file mode 100644 index 323fae0..0000000 --- a/src/filefilefile.txt +++ /dev/null @@ -1 +0,0 @@ -foobar diff --git a/src/filewithareallyreallyreallyreallylongname.txt b/src/filewithareallyreallyreallyreallylongname.txt deleted file mode 100644 index 257cc56..0000000 --- a/src/filewithareallyreallyreallyreallylongname.txt +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/src/libsh/collections.rs b/src/libsh/collections.rs deleted file mode 100644 index ab6af83..0000000 --- a/src/libsh/collections.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::VecDeque; - -pub trait VecDequeAliases { - fn fpop(&mut self) -> Option; - fn fpush(&mut self, value: T); - fn bpop(&mut self) -> Option; - fn bpush(&mut self, value: T); - fn to_vec(self) -> Vec; -} - -impl VecDequeAliases for VecDeque { - /// Alias for pop_front() - fn fpop(&mut self) -> Option { - self.pop_front() - } - /// Alias for push_front() - fn fpush(&mut self, value: T) { - self.push_front(value); - } - /// Alias for pop_back() - fn bpop(&mut self) -> Option { - self.pop_back() - } - /// Alias for push_back() - fn bpush(&mut self, value: T) { - self.push_back(value); - } - /// Just turns the deque into a vector - fn to_vec(mut self) -> Vec { - let mut vec = vec![]; - while let Some(item) = self.fpop() { - vec.push(item) - } - vec - } -} diff --git a/src/libsh/error.rs b/src/libsh/error.rs index a236314..ac4df31 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -1,119 +1,81 @@ -use std::fmt::Display; +use std::{fmt::Display, str::FromStr}; -use crate::parse::lex::Span; -use crate::prelude::*; +use crate::{parse::lex::Span, prelude::*}; -pub type ShResult = Result; +pub type ShResult<'s,T> = Result>; -pub trait ResultExt { - fn eprint(self) -> Self; - fn abort_if_err(&self); +#[derive(Debug)] +pub enum ShErr<'s> { + Simple { kind: ShErrKind, msg: String }, + Full { kind: ShErrKind, msg: String, span: Span<'s> } } -#[derive(Clone,Debug)] -pub struct BlamePair { - input: String, - span: Rc> -} - -impl BlamePair { - pub fn new(input: String, span: Rc>) -> Self { - Self { input, span } +impl<'s> ShErr<'s> { + pub fn simple(kind: ShErrKind, msg: impl Into) -> Self { + let msg = msg.into(); + Self::Simple { kind, msg } } - pub fn start(&self) -> usize { - self.span.borrow().start() + pub fn full(kind: ShErrKind, msg: impl Into, span: Span<'s>) -> Self { + let msg = msg.into(); + Self::Full { kind, msg, span } } - pub fn end(&self) -> usize { - self.span.borrow().end() - } - pub fn len(&self) -> usize { - self.input.len() - } -} - -impl Into for BlamePair { - fn into(self) -> String { - self.input - } -} - -impl ResultExt for Result { - fn eprint(self) -> Self { - if let Err(err) = &self { - eprintln!("{}", err); + pub fn unpack(self) -> (ShErrKind,String,Option>) { + match self { + ShErr::Simple { kind, msg } => (kind,msg,None), + ShErr::Full { kind, msg, span } => (kind,msg,Some(span)) } - self } - fn abort_if_err(&self) { - if let Err(err) = &self { - eprintln!("{}", err); - sh_quit(1) + pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self { + let (kind,msg,_) = sherr.unpack(); + Self::Full { kind, msg, span } + } +} + +impl<'s> Display for ShErr<'s> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg), + Self::Full { msg, kind: _, span: _ } => writeln!(f, "{}", msg) } } } -pub trait Blame { - /// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full - /// This will also set the span on a ShErr::Builder - fn blame(self, input: String, span: Rc>) -> Self; - - /// If an error is propagated to this point, then attempt to blame a span. - /// If the error in question has already blamed a span, don't overwrite it. - /// Used as a last resort in higher level contexts in case an error somehow goes unblamed - fn try_blame(self, input: String, span: Rc>) -> Self; -} - -impl From for ShErr { +impl<'s> From for ShErr<'s> { fn from(_: std::io::Error) -> Self { - ShErr::io() + let msg = std::io::Error::last_os_error(); + ShErr::simple(ShErrKind::IoErr, msg.to_string()) } } -impl From for ShErr { +impl<'s> From for ShErr<'s> { fn from(value: std::env::VarError) -> Self { ShErr::simple(ShErrKind::InternalErr, &value.to_string()) } } -impl From for ShErr { +impl<'s> From for ShErr<'s> { fn from(value: rustyline::error::ReadlineError) -> Self { ShErr::simple(ShErrKind::ParseErr, &value.to_string()) } } -impl From for ShErr { +impl<'s> From for ShErr<'s> { fn from(value: Errno) -> Self { ShErr::simple(ShErrKind::Errno, &value.to_string()) } } -impl Blame for Result { - fn blame(self, input: String, span: Rc>) -> Self { - if let Err(mut e) = self { - e.blame(input,span); - Err(e) - } else { - self - } - } - fn try_blame(self, input: String, span: Rc>) -> Self { - if let Err(mut e) = self { - e.try_blame(input,span); - Err(e) - } else { - self - } - } -} - -#[derive(Debug,Copy,Clone,PartialEq,Eq)] +#[derive(Debug)] pub enum ShErrKind { IoErr, SyntaxErr, ParseErr, InternalErr, ExecFail, + ResourceLimitExceeded, + BadPermission, Errno, + FileNotFound, CmdNotFound, CleanExit, FuncReturn, @@ -121,156 +83,3 @@ pub enum ShErrKind { LoopBreak, Null } - -impl Default for ShErrKind { - fn default() -> Self { - Self::Null - } -} - -#[derive(Clone,Debug)] -pub enum ShErr { - Simple { kind: ShErrKind, message: String }, - Full { kind: ShErrKind, message: String, blame: BlamePair }, -} - -impl ShErr { - pub fn simple>(kind: ShErrKind, message: S) -> Self { - Self::Simple { kind, message: message.into() } - } - pub fn io() -> Self { - io::Error::last_os_error().into() - } - pub fn full>(kind: ShErrKind, message: S, input: String, span: Rc>) -> Self { - let blame = BlamePair::new(input.to_string(), span); - Self::Full { kind, message: message.into(), blame } - } - pub fn try_blame(&mut self, input: String, span: Rc>) { - let blame_pair = BlamePair::new(input, span); - match self { - Self::Full {..} => { - /* Do not overwrite */ - } - Self::Simple { kind, message } => { - *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair } - } - } - } - pub fn blame(&mut self, input: String, span: Rc>) { - let blame_pair = BlamePair::new(input, span); - match self { - Self::Full { kind: _, message: _, blame } => { - *blame = blame_pair; - } - Self::Simple { kind, message } => { - *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair } - } - } - } - pub fn with_msg(&mut self, new_message: String) { - match self { - Self::Full { kind: _, message, blame: _ } => { - *message = new_message - } - Self::Simple { kind: _, message } => { - *message = new_message - } - } - } - pub fn kind(&self) -> ShErrKind { - match self { - ShErr::Simple { kind, message: _ } => { - *kind - } - ShErr::Full { kind, message: _, blame: _ } => { - *kind - } - } - } - pub fn with_kind(&mut self, new_kind: ShErrKind) { - match self { - Self::Full { kind, message: _, blame: _ } => { - *kind = new_kind - } - Self::Simple { kind, message: _ } => { - *kind = new_kind - } - } - } - pub fn display_kind(&self) -> String { - match self { - ShErr::Simple { kind, message: _ } | - ShErr::Full { kind, message: _, blame: _ } => { - match kind { - ShErrKind::IoErr => "I/O Error: ".into(), - ShErrKind::SyntaxErr => "Syntax Error: ".into(), - ShErrKind::ParseErr => "Parse Error: ".into(), - ShErrKind::InternalErr => "Internal Error: ".into(), - ShErrKind::ExecFail => "Execution Failed: ".into(), - ShErrKind::Errno => "ERRNO: ".into(), - ShErrKind::CmdNotFound => "Command not found: ".into(), - ShErrKind::CleanExit | - ShErrKind::FuncReturn | - ShErrKind::LoopContinue | - ShErrKind::LoopBreak | - ShErrKind::Null => "".into() - } - } - } - } - pub fn get_line(&self) -> (usize,usize,String) { - if let ShErr::Full { kind: _, message: _, blame } = self { - unsafe { - let mut dist = 0; - let mut line_no = 0; - let window = self.get_window(); - let mut lines = window.lines(); - while let Some(line) = lines.next() { - line_no += 1; - dist += line.len(); - if dist > blame.start() { - dist -= line.len(); - let offset = blame.start() - dist; - return (offset,line_no,line.to_string()) - } - } - } - (0,0,String::new()) - } else { - (0,0,String::new()) - } - } - pub fn get_window(&self) -> String { - if let ShErr::Full { kind: _, message: _, blame } = self.clone() { - let window: String = blame.into(); - window.split_once('\n').unwrap_or((&window,"")).0.to_string() - } else { - String::new() - } - } -} - -impl Display for ShErr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let error_display = match self { - ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message), - ShErr::Full { kind: _, message, blame } => { - let (offset,line_no,line_text) = self.get_line(); - let dist = blame.end().saturating_sub(blame.start()); - let padding = " ".repeat(offset); - let line_inner = "~".repeat(dist.saturating_sub(2)); - let err_kind = &self.display_kind().styled(Style::Red | Style::Bold); - let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message); - let indicator_line = if dist == 1 { - format!("{}^",padding) - } else { - format!("{}^{}^",padding,line_inner) - }; - let error_full = format!("\n{}\n{}\n{}\n",stat_line,line_text,indicator_line); - - error_full - } - }; - write!(f,"{}",error_display) - } -} diff --git a/src/libsh/mod.rs b/src/libsh/mod.rs index dc8a192..704a1bc 100644 --- a/src/libsh/mod.rs +++ b/src/libsh/mod.rs @@ -1,6 +1,2 @@ -pub mod sys; -#[macro_use] -pub mod utils; -pub mod collections; pub mod error; pub mod term; diff --git a/src/libsh/sys.rs b/src/libsh/sys.rs deleted file mode 100644 index 0748d1c..0000000 --- a/src/libsh/sys.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::{fmt::Display, os::{fd::AsRawFd, unix::fs::PermissionsExt}}; - -use nix::sys::termios; - -use crate::prelude::*; - -pub const SIG_EXIT_OFFSET: i32 = 128; - -pub fn get_path_cmds() -> ShResult> { - let mut cmds = vec![]; - let path_var = std::env::var("PATH")?; - let paths = path_var.split(':'); - - for path in paths { - let path = PathBuf::from(&path); - if path.is_dir() { - let path_files = std::fs::read_dir(&path)?; - for file in path_files { - let file_path = file?.path(); - if file_path.is_file() { - if let Ok(meta) = std::fs::metadata(&file_path) { - let perms = meta.permissions(); - if perms.mode() & 0o111 != 0 { - let file_name = file_path.file_name().unwrap(); - cmds.push(file_name.to_str().unwrap().to_string()) - } - } - } - } - } - } - - Ok(cmds) -} - -pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option { - let env = shenv.vars().env(); - let path_var = env.get("PATH")?; - let mut paths = path_var.split(':'); - - let script_check = PathBuf::from(command); - if script_check.is_file() { - return Some(script_check) - } - while let Some(raw_path) = paths.next() { - let mut path = PathBuf::from(raw_path); - path.push(command); - //TODO: handle this unwrap - if path.exists() { - return Some(path) - } - } - None -} - -pub fn write_out(text: impl Display) -> ShResult<()> { - write(borrow_fd(1), text.to_string().as_bytes())?; - Ok(()) -} - -pub fn write_err(text: impl Display) -> ShResult<()> { - write(borrow_fd(2), text.to_string().as_bytes())?; - Ok(()) -} - -/// Return is `readpipe`, `writepipe` -/// Contains all of the necessary boilerplate for grabbing two pipe fds using libc::pipe() -pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> { - let mut pipes: [i32;2] = [0;2]; - let ret = unsafe { libc::pipe(pipes.as_mut_ptr()) }; - if ret < 0 { - return Err(Errno::from_raw(ret)) - } - Ok((pipes[0],pipes[1])) -} - -pub fn sh_quit(code: i32) -> ! { - write_jobs(|j| { - for job in j.jobs_mut().iter_mut().flatten() { - job.killpg(Signal::SIGTERM).ok(); - } - }); - if let Some(termios) = crate::get_saved_termios() { - termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap(); - } - if code == 0 { - write_err("exit\n").ok(); - } else { - write_err(format!("exit {code}\n")).ok(); - } - exit(code); -} - -pub fn read_to_string(fd: i32) -> ShResult { - let mut buf = Vec::with_capacity(4096); - let mut temp_buf = [0u8;1024]; - - loop { - match read(fd, &mut temp_buf) { - Ok(0) => break, // EOF - Ok(n) => buf.extend_from_slice(&temp_buf[..n]), - Err(Errno::EINTR) => continue, // Retry on EINTR - Err(e) => return Err(e.into()), // Return other errors - } - } - - Ok(String::from_utf8_lossy(&buf).to_string()) -} - -pub fn execvpe(cmd: String, argv: Vec, envp: Vec) -> Result<(),Errno> { - let cmd_raw = CString::new(cmd).unwrap(); - - let argv = argv.into_iter().map(|arg| CString::new(arg).unwrap()).collect::>(); - let envp = envp.into_iter().map(|var| CString::new(var).unwrap()).collect::>(); - - nix::unistd::execvpe(&cmd_raw, &argv, &envp).unwrap(); - Ok(()) -} diff --git a/src/libsh/term.rs b/src/libsh/term.rs index edae0f0..8611889 100644 --- a/src/libsh/term.rs +++ b/src/libsh/term.rs @@ -1,9 +1,21 @@ use std::{fmt::Display, ops::BitOr}; +pub trait Styled: Sized + Display { + fn styled>(self, style: S) -> String { + let styles: StyleSet = style.into(); + let reset = Style::Reset; + format!("{styles}{self}{reset}") + } +} + +impl Styled for T {} + /// Enum representing a single ANSI style #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Style { + // Undoes all styles Reset, + // Foreground Colors Black, Red, Green, @@ -20,36 +32,86 @@ pub enum Style { BrightMagenta, BrightCyan, BrightWhite, + RGB(u8, u8, u8), // Custom foreground color + + // Background Colors + BgBlack, + BgRed, + BgGreen, + BgYellow, + BgBlue, + BgMagenta, + BgCyan, + BgWhite, + BgBrightBlack, + BgBrightRed, + BgBrightGreen, + BgBrightYellow, + BgBrightBlue, + BgBrightMagenta, + BgBrightCyan, + BgBrightWhite, + BgRGB(u8, u8, u8), // Custom background color + + // Text Attributes Bold, + Dim, Italic, Underline, + Strikethrough, Reversed, } -impl Style { - pub fn as_str(&self) -> &'static str { +impl Display for Style { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Style::Reset => "\x1b[0m", - Style::Black => "\x1b[30m", - Style::Red => "\x1b[31m", - Style::Green => "\x1b[32m", - Style::Yellow => "\x1b[33m", - Style::Blue => "\x1b[34m", - Style::Magenta => "\x1b[35m", - Style::Cyan => "\x1b[36m", - Style::White => "\x1b[37m", - Style::BrightBlack => "\x1b[90m", - Style::BrightRed => "\x1b[91m", - Style::BrightGreen => "\x1b[92m", - Style::BrightYellow => "\x1b[93m", - Style::BrightBlue => "\x1b[94m", - Style::BrightMagenta => "\x1b[95m", - Style::BrightCyan => "\x1b[96m", - Style::BrightWhite => "\x1b[97m", - Style::Bold => "\x1b[1m", - Style::Italic => "\x1b[3m", - Style::Underline => "\x1b[4m", - Style::Reversed => "\x1b[7m", + Style::Reset => write!(f, "\x1b[0m"), + + // Foreground colors + Style::Black => write!(f, "\x1b[30m"), + Style::Red => write!(f, "\x1b[31m"), + Style::Green => write!(f, "\x1b[32m"), + Style::Yellow => write!(f, "\x1b[33m"), + Style::Blue => write!(f, "\x1b[34m"), + Style::Magenta => write!(f, "\x1b[35m"), + Style::Cyan => write!(f, "\x1b[36m"), + Style::White => write!(f, "\x1b[37m"), + Style::BrightBlack => write!(f, "\x1b[90m"), + Style::BrightRed => write!(f, "\x1b[91m"), + Style::BrightGreen => write!(f, "\x1b[92m"), + Style::BrightYellow => write!(f, "\x1b[93m"), + Style::BrightBlue => write!(f, "\x1b[94m"), + Style::BrightMagenta => write!(f, "\x1b[95m"), + Style::BrightCyan => write!(f, "\x1b[96m"), + Style::BrightWhite => write!(f, "\x1b[97m"), + Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"), + + // Background colors + Style::BgBlack => write!(f, "\x1b[40m"), + Style::BgRed => write!(f, "\x1b[41m"), + Style::BgGreen => write!(f, "\x1b[42m"), + Style::BgYellow => write!(f, "\x1b[43m"), + Style::BgBlue => write!(f, "\x1b[44m"), + Style::BgMagenta => write!(f, "\x1b[45m"), + Style::BgCyan => write!(f, "\x1b[46m"), + Style::BgWhite => write!(f, "\x1b[47m"), + Style::BgBrightBlack => write!(f, "\x1b[100m"), + Style::BgBrightRed => write!(f, "\x1b[101m"), + Style::BgBrightGreen => write!(f, "\x1b[102m"), + Style::BgBrightYellow => write!(f, "\x1b[103m"), + Style::BgBrightBlue => write!(f, "\x1b[104m"), + Style::BgBrightMagenta => write!(f, "\x1b[105m"), + Style::BgBrightCyan => write!(f, "\x1b[106m"), + Style::BgBrightWhite => write!(f, "\x1b[107m"), + Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"), + + // Text attributes + Style::Bold => write!(f, "\x1b[1m"), + Style::Dim => write!(f, "\x1b[2m"), // New + Style::Italic => write!(f, "\x1b[3m"), + Style::Underline => write!(f, "\x1b[4m"), + Style::Strikethrough => write!(f, "\x1b[9m"), // New + Style::Reversed => write!(f, "\x1b[7m"), } } } @@ -62,7 +124,7 @@ pub struct StyleSet { impl StyleSet { pub fn new() -> Self { - Self { styles: Vec::new() } + Self { styles: vec![] } } pub fn add(mut self, style: Style) -> Self { @@ -71,9 +133,14 @@ impl StyleSet { } self } +} - pub fn as_str(&self) -> String { - self.styles.iter().map(|s| s.as_str()).collect::() +impl Display for StyleSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for style in &self.styles { + style.fmt(f)? + } + Ok(()) } } @@ -81,7 +148,7 @@ impl StyleSet { impl BitOr for Style { type Output = StyleSet; - fn bitor(self, rhs: Self) -> StyleSet { + fn bitor(self, rhs: Self) -> Self::Output { StyleSet::new().add(self).add(rhs) } } @@ -90,7 +157,7 @@ impl BitOr for Style { impl BitOr