diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index b4c1310..7ca97f4 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -6,8 +6,9 @@ pub mod jobctl; pub mod read; pub mod alias; pub mod control_flow; +pub mod source; -pub const BUILTINS: [&str;13] = [ +pub const BUILTINS: [&str;14] = [ "echo", "cd", "pwd", @@ -21,4 +22,5 @@ pub const BUILTINS: [&str;13] = [ "continue", "return", "break", + "source", ]; diff --git a/src/builtin/source.rs b/src/builtin/source.rs new file mode 100644 index 0000000..0f5d337 --- /dev/null +++ b/src/builtin/source.rs @@ -0,0 +1,15 @@ +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 index 3484634..c309c2d 100644 --- a/src/execute/mod.rs +++ b/src/execute/mod.rs @@ -9,7 +9,6 @@ pub mod shellcmd; pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> { let input = input.into(); shenv.new_input(&input); - log!(INFO, "New input: {:?}", input); let token_stream = Lexer::new(input,shenv).lex(); @@ -20,6 +19,7 @@ pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> } let syn_tree = Parser::new(token_stream,shenv).parse()?; + let exec_start = 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(); @@ -28,6 +28,7 @@ pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> return Err(e.into()) } } + log!(INFO, "Executing done in {:?}", exec_start.elapsed()); Ok(()) } @@ -41,7 +42,6 @@ impl<'a> Executor<'a> { Self { ast, shenv } } pub fn walk(&mut self) -> ShResult<()> { - self.shenv.ctx_mut().descend()?; self.shenv.inputman_mut().save_state(); log!(DEBUG, "Starting walk"); while let Some(node) = self.ast.next_node() { @@ -50,7 +50,6 @@ impl<'a> Executor<'a> { exec_list(cmds, self.shenv).try_blame(node.as_raw(self.shenv),node.span())? } else { unreachable!() } } - self.shenv.ctx_mut().ascend(); self.shenv.inputman_mut().load_state(); log!(DEBUG, "passed"); Ok(()) @@ -77,19 +76,25 @@ fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult } } } - log!(TRACE, "{:?}", *cmd.rule()); - match *cmd.rule() { - NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(cmd_raw, span)?, - NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(cmd_raw, span)?, - NdRule::IfThen {..} => shellcmd::exec_if(cmd, shenv).try_blame(cmd_raw, span)?, - NdRule::Loop {..} => shellcmd::exec_loop(cmd, shenv).try_blame(cmd_raw, span)?, - NdRule::ForLoop {..} => shellcmd::exec_for(cmd, shenv).try_blame(cmd_raw, span)?, - NdRule::Case {..} => shellcmd::exec_case(cmd, shenv).try_blame(cmd_raw, span)?, - NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(cmd_raw, span)?, - NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(cmd_raw, span)?, - NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(cmd_raw, span)?, - _ => unimplemented!() - } + 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 {..} => dispatch_command(node, shenv).try_blame(node_raw, span)?, + NdRule::Subshell {..} => exec_subshell(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::Assignment {..} => exec_assignment(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(()) } @@ -189,10 +194,10 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let body_raw = body.as_raw(shenv); match exec_input(body_raw, shenv) { - Ok(()) => sh_quit(0), + Ok(()) => exit(0), Err(e) => { eprintln!("{}",e); - sh_quit(1); + exit(1); } } } else { @@ -209,10 +214,10 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } let body_raw = body.as_raw(shenv); match exec_input(body_raw, shenv) { - Ok(()) => sh_quit(0), + Ok(()) => exit(0), Err(e) => { eprintln!("{}",e); - sh_quit(1); + exit(1); } } } @@ -254,6 +259,7 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { "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"); @@ -302,7 +308,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let mut pids = vec![]; while let Some(cmd) = cmds.pop_front() { - let (r_pipe, w_pipe) = if cmds.is_empty() { + 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 { @@ -314,55 +320,65 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { cmd_names.push(cmd_name); } else if let NdRule::Subshell {..} = cmd.rule() { cmd_names.push("subshell".to_string()); - } else { unimplemented!() } + } 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 { - close(r_pipe.as_raw_fd())?; + if let Some(r_pipe) = r_pipe.take() { + close(r_pipe)?; } // Create some redirections - if let Some(w_pipe) = w_pipe { - let wpipe_redir = Redir::output(1, w_pipe); - shenv.ctx_mut().push_rdr(wpipe_redir); + 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 { + if let Some(prev_rpipe) = prev_rpipe.take() { let rpipe_redir = Redir::input(0, prev_rpipe); shenv.ctx_mut().push_rdr(rpipe_redir); } - dispatch_command(cmd, shenv)?; + 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 { - close(w_pipe.as_raw_fd())?; + 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 children = vec![ - ChildProc::new(*pid, Some(&command), pgid)? - ]; - let job = JobBldr::new() - .with_children(children) - .with_pgid(pgid.unwrap()) - .build(); - wait_fg(job, shenv)?; + let child = ChildProc::new(*pid, Some(&command), pgid)?; + children.push(child); } + let job = JobBldr::new() + .with_children(children) + .with_pgid(pgid.unwrap()) + .build(); + wait_fg(job, shenv)?; } else { unreachable!() } Ok(()) } @@ -370,7 +386,6 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { log!(DEBUG, "Executing command"); let blame = node.span(); - let blame_raw = node.as_raw(shenv); let rule = node.into_rule(); if let NdRule::Command { argv, redirs } = rule { @@ -378,10 +393,11 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let command = argv.first().unwrap().to_string(); if get_bin_path(&command, shenv).is_some() { - shenv.save_io()?; + log!(DEBUG, "{:?}",shenv.ctx().flags()); if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { log!(TRACE, "Not forking"); shenv.collect_redirs(redirs); + log!(DEBUG, "{:?}",shenv.ctx().redirs()); if let Err(e) = shenv.ctx_mut().activate_rdrs() { eprintln!("{:?}",e); exit(1); @@ -430,9 +446,8 @@ fn prep_execve(argv: Vec, shenv: &mut ShEnv) -> (Vec, Vec log!(DEBUG, argv_s); let mut envp = vec![]; - let env_vars = shenv.vars().env().clone(); - let mut entries = env_vars.iter().collect::>(); - while let Some(entry) = entries.fpop() { + 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); diff --git a/src/expand/alias.rs b/src/expand/alias.rs index f969f84..27560a0 100644 --- a/src/expand/alias.rs +++ b/src/expand/alias.rs @@ -3,33 +3,36 @@ 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 mut expanded_aliases = vec![]; 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 let Some(token) = work_stack.fpop() { - if token.rule() == TkRule::Ident { - let candidate_str = token.as_raw(shenv); - if let Some(alias) = logic.get_alias(&candidate_str) { - // Expand the alias only if it hasn't been expanded yet - if !expanded_aliases.contains(&candidate_str) { - expanded_aliases.push(candidate_str); - let mut new_tokens = shenv.expand_input(alias, token.span()); - for token in new_tokens.iter_mut() { - work_stack.bpush(token.clone()); + 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 { - // If already expanded, just add the token to the output tokens.push(token); } } else { tokens.push(token); } - } else { - tokens.push(token); + } + if !done { + work_stack.extend(tokens.drain(..)); } } tokens diff --git a/src/main.rs b/src/main.rs index 7f57785..c1f0eb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,9 @@ use crate::prelude::*; pub fn main() { sig_setup(); let mut shenv = ShEnv::new(); + if let Err(e) = shenv.source_rc() { + eprintln!("Error sourcing rc file: {}", e.to_string()); + } loop { log!(TRACE, "Entered loop"); diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 6b87954..96718ec 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -19,7 +19,15 @@ pub const KEYWORDS: [TkRule;14] = [ TkRule::Esac ]; -pub const SEPARATORS: [TkRule; 7] = [ +pub const OPERATORS: [TkRule;5] = [ + TkRule::AndOp, + TkRule::OrOp, + TkRule::PipeOp, + TkRule::ErrPipeOp, + TkRule::BgOp, +]; + +pub const SEPARATORS: [TkRule;7] = [ TkRule::Sep, TkRule::AndOp, TkRule::OrOp, @@ -94,6 +102,10 @@ impl Token { self.rule } + pub fn rule_mut(&mut self) -> &mut TkRule { + &mut self.rule + } + pub fn as_raw(&self, shenv: &mut ShEnv) -> String { shenv.input_slice(self.span()).to_string() } @@ -241,7 +253,7 @@ impl TkRule { try_match!(While,input); try_match!(Until,input); try_match!(For,input); - try_match!(In,input); + //try_match!(In,input); try_match!(Select,input); try_match!(Do,input); try_match!(Done,input); @@ -547,6 +559,7 @@ tkrule_def!(For, |input: &str| { None } }); +/* tkrule_def!(In, |input: &str| { if input.starts_with("in") { match input.chars().nth(2) { @@ -558,6 +571,7 @@ tkrule_def!(In, |input: &str| { None } }); +*/ tkrule_def!(Select, |input: &str| { if input.starts_with("select") { match input.chars().nth(6) { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 3ef328f..810e8c9 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -4,7 +4,7 @@ use std::{iter::Peekable, str::FromStr}; use crate::prelude::*; -use lex::{Span, TkRule, Token, KEYWORDS}; +use lex::{Span, TkRule, Token, KEYWORDS, OPERATORS, SEPARATORS}; bitflags! { #[derive(Debug,Clone,Copy,PartialEq,Eq)] @@ -315,8 +315,8 @@ ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| { ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| { try_rules!(tokens, shenv, - ShellCmd, Pipeline, + ShellCmd, Subshell, Assignment, Command @@ -365,10 +365,16 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { tokens = &tokens[1..]; match token.rule() { TkRule::Whitespace => continue, - TkRule::Ident => { + TkRule::Ident | TkRule::VarSub => { pat = Some(token.clone()); break } + _ if KEYWORDS.contains(&token.rule()) => { + let mut clone = token.clone(); + *clone.rule_mut() = TkRule::Ident; + pat = Some(clone); + break + } _ => return Err(err("Expected an ident in case statement", token.span(), shenv)) } } @@ -377,28 +383,36 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { return Err(err("Expected an ident in case statement", node_toks.last().unwrap().span(), shenv)) } let pat = pat.unwrap(); + tokens_iter = tokens.iter().peekable(); let mut closed = false; while let Some(token) = tokens_iter.next() { - node_toks.push(token.clone()); - tokens = &tokens[1..]; match token.rule() { - TkRule::Whitespace => continue, TkRule::Ident => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; if token.as_raw(shenv) != "in" { + panic!(); return Err(err("Expected `in` after case statement pattern", token.span(), shenv)) } else { closed = true; } } TkRule::Sep => { - if closed { - break - } + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + _ => { + if closed { break } + log!(ERROR, token); + panic!(); + return Err(err("Expected `in` after case statement pattern", token.span(), shenv)) } - _ => return Err(err("Expected `in` after case statement pattern", token.span(), shenv)) } } + log!(DEBUG,tokens); + tokens_iter = tokens.iter().peekable(); if tokens_iter.peek().is_none() { return Err(err("Expected `in` after case statement pattern", node_toks.last().unwrap().span(), shenv)) @@ -452,6 +466,9 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { break } } + _ if OPERATORS.contains(&token.rule()) => { + if closed { break } + } TkRule::RedirOp if closed => { node_toks.push(token.clone()); tokens = &tokens[1..]; @@ -506,14 +523,25 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { } else { return Ok(None) } while let Some(token) = tokens_iter.next() { + log!(DEBUG, token); node_toks.push(token.clone()); tokens = &tokens[1..]; - if let TkRule::Ident = token.rule() { - if token.as_raw(shenv) == "in" { break } - vars.push(token.clone()); - } else { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected an ident in for loop vars",span,shenv)) + match token.rule() { + _ if token.as_raw(shenv) == "in" => break, + TkRule::Ident => { + vars.push(token.clone()); + } + _ if KEYWORDS.contains(&token.rule()) => { + let mut clone = token.clone(); + *clone.rule_mut() = TkRule::Ident; + vars.push(clone); + } + _ => { + log!(ERROR,"{:?}",token.rule()); + log!(ERROR,token); + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop vars",span,shenv)) + } } } if vars.is_empty() { @@ -523,15 +551,26 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { while let Some(token) = tokens_iter.next() { node_toks.push(token.clone()); tokens = &tokens[1..]; - if token.rule() == TkRule::Sep { break } - if let TkRule::Ident = token.rule() { - arr.push(token.clone()); - } else { - let span = get_span(&node_toks, shenv)?; - return Err(err("Expected an ident in for loop array",span,shenv)) + match token.rule() { + TkRule::Sep => break, + TkRule::Ident => { + arr.push(token.clone()); + } + _ if KEYWORDS.contains(&token.rule()) => { + let mut clone = token.clone(); + *clone.rule_mut() = TkRule::Ident; + arr.push(clone); + } + _ => { + log!(ERROR,"{:?}",token.rule()); + log!(ERROR,token); + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop array",span,shenv)) + } } } if arr.is_empty() { + log!(ERROR,node_toks); let span = get_span(&node_toks, shenv)?; return Err(err("Expected an ident in for loop array",span,shenv)) } @@ -578,6 +617,9 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { tokens = &tokens[1..]; if closed { break } } + _ if OPERATORS.contains(&token.rule()) => { + if closed { break } + } TkRule::RedirOp if closed => { node_toks.push(token.clone()); tokens = &tokens[1..]; @@ -763,6 +805,9 @@ ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { tokens = &tokens[used..]; redirs.push(redir); } + _ if OPERATORS.contains(&token.rule()) => { + if closed { break } + } _ => { let span = get_span(&node_toks, shenv)?; return Err(err(&format!("Unexpected token in if statement: {:?}",token.rule()),span,shenv)) @@ -928,6 +973,12 @@ ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| { return Ok(None) } + if let Some(token) = tokens_iter.next() { + if let TkRule::Sep = token.rule() { + node_toks.push(token.clone()); + } + } + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::FuncDef { name, body }, @@ -1005,7 +1056,11 @@ ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { while let Some(token) = tokens_iter.peek() { match token.rule() { - TkRule::AndOp | TkRule::OrOp => { + _ if SEPARATORS.contains(&token.rule()) => { + if token.rule() == TkRule::Sep { + let token = tokens_iter.next().unwrap(); + node_toks.push(token.clone()); + } // If there are no commands or only one, this isn't a pipeline match cmds.len() { 0 | 1 => return Ok(None), @@ -1158,6 +1213,12 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { if let Some(ref cmd) = cmd { node_toks.extend(cmd.tokens().clone()); } + if let Some(token) = tokens_slice.first() { + let token = token.clone(); + if token.rule() == TkRule::Sep { + node_toks.push(token.clone()); + } + } let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Assignment { assignments, cmd }, @@ -1167,6 +1228,14 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { }; return Ok(Some(node)) } else { + log!(DEBUG, tokens); + if let Some(token) = tokens.next() { + if token.rule() == TkRule::Sep { + node_toks.push(token.clone()); + } + } + log!(DEBUG, node_toks); + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Assignment { assignments, cmd: None }, diff --git a/src/prelude.rs b/src/prelude.rs index ea59526..b9aa133 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -121,6 +121,7 @@ pub use crate::{ alias::alias, control_flow::sh_flow, export::export, + source::source, jobctl::{ continue_job, jobs diff --git a/src/prompt/highlight.rs b/src/prompt/highlight.rs index b270b6f..90daac0 100644 --- a/src/prompt/highlight.rs +++ b/src/prompt/highlight.rs @@ -14,6 +14,7 @@ impl<'a> Highlighter for SynHelper<'a> { let mut tokens = Lexer::new(line.to_string(),&mut shenv_clone).lex().into_iter(); let mut is_command = true; let mut in_array = false; + let mut in_case = false; while let Some(token) = tokens.next() { let raw = token.as_raw(&mut shenv_clone); @@ -32,19 +33,45 @@ impl<'a> Highlighter for SynHelper<'a> { let styled = &raw.styled(Style::Cyan); result.push_str(&styled); } + TkRule::CasePat => { + let pat = raw.trim_end_matches(')'); + let len_delta = raw.len().saturating_sub(pat.len()); + let parens = ")".repeat(len_delta); + let styled = pat.styled(Style::Magenta); + let rebuilt = format!("{styled}{parens}"); + result.push_str(&rebuilt); + } TkRule::FuncName => { let name = raw.strip_suffix("()").unwrap_or(&raw); let styled = name.styled(Style::Cyan); let rebuilt = format!("{styled}()"); result.push_str(&rebuilt); } - _ if KEYWORDS.contains(&token.rule()) => { - if &raw == "for" { - in_array = true; - } - let styled = &raw.styled(Style::Yellow); + TkRule::DQuote | TkRule::SQuote => { + let styled = raw.styled(Style::BrightYellow); result.push_str(&styled); } + _ if KEYWORDS.contains(&token.rule()) => { + if in_array || in_case { + if &raw == "in" { + let styled = &raw.styled(Style::Yellow); + result.push_str(&styled); + if in_case { in_case = false }; + } else { + let styled = &raw.styled(Style::Magenta); + result.push_str(&styled); + } + } else { + if &raw == "for" { + in_array = true; + } + if &raw == "case" { + in_case = true; + } + let styled = &raw.styled(Style::Yellow); + result.push_str(&styled); + } + } TkRule::BraceGrp => { let body = &raw[1..raw.len() - 1]; let highlighted = self.highlight(body, 0).to_string(); @@ -65,16 +92,30 @@ impl<'a> Highlighter for SynHelper<'a> { is_command = false; result.push_str(&rebuilt); } + TkRule::VarSub => { + let styled = raw.styled(Style::Magenta); + result.push_str(&styled); + } + TkRule::Assign => { + let (var,val) = raw.split_once('=').unwrap(); + let var_styled = var.styled(Style::Magenta); + let val_styled = val.styled(Style::Cyan); + let rebuilt = vec![var_styled,val_styled].join("="); + result.push_str(&rebuilt); + } TkRule::Ident => { - if in_array { + if in_array || in_case { if &raw == "in" { let styled = &raw.styled(Style::Yellow); result.push_str(&styled); + if in_case { in_case = false }; } else { let styled = &raw.styled(Style::Magenta); result.push_str(&styled); } - + } else if raw.starts_with(['"','\'']) { + let styled = &raw.styled(Style::BrightYellow); + result.push_str(&styled); } else if &raw == "{" || &raw == "}" { result.push_str(&raw); diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index 7112d13..ae841cc 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -78,7 +78,7 @@ impl<'a> Hinter for SynHelper<'a> { } let history = ctx.history(); let result = self.hist_search(line, history)?; - let window = result[line.len()..].to_string(); + let window = result[line.len()..].trim_end().to_string(); Some(SynHint::new(window)) } } diff --git a/src/prompt/validate.rs b/src/prompt/validate.rs index 911e35f..9919c06 100644 --- a/src/prompt/validate.rs +++ b/src/prompt/validate.rs @@ -7,23 +7,26 @@ use super::readline::SynHelper; pub fn check_delims(line: &str) -> bool { let mut delim_stack = vec![]; let mut chars = line.chars(); - let mut in_case = false; + let mut case_depth: u64 = 0; let mut case_check = String::new(); let mut in_quote = None; // Tracks which quote type is open (`'` or `"`) while let Some(ch) = chars.next() { case_check.push(ch); + if case_check.len() > 4 { + case_check = case_check[1..].to_string(); + } if case_check.ends_with("case") { - in_case = true; + case_depth += 1; } if case_check.ends_with("esac") { - in_case = false; + case_depth = case_depth.saturating_sub(1); } match ch { '{' | '(' | '[' if in_quote.is_none() => delim_stack.push(ch), '}' if in_quote.is_none() && delim_stack.pop() != Some('{') => return false, ')' if in_quote.is_none() && delim_stack.pop() != Some('(') => { - if !in_case { + if case_depth == 0 { return false } } @@ -44,75 +47,9 @@ pub fn check_delims(line: &str) -> bool { } pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool { - use TkRule::*; - let mut expecting: Vec> = vec![]; - let mut tokens = Lexer::new(line.to_string(),shenv).lex().into_iter(); - - while let Some(token) = tokens.next() { - match token.rule() { - If => { - expecting.push(vec![Then]); - } - Then => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Then) { - expecting.push(vec![Elif, Else, Fi]) - } else { return false } - } else { return false } - } - Elif => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Elif) { - expecting.push(vec![Then]) - } else { return false } - } else { return false } - } - Else => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Else) { - expecting.push(vec![Fi]) - } else { return false } - } else { return false } - } - Fi => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Fi) { - /* Do nothing */ - } else { return false } - } else { return false } - } - While | Until | For | Select => { - expecting.push(vec![Do]) - } - Do => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Do) { - expecting.push(vec![Done]) - } else { return false } - } else { return false } - } - Done => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Done) { - /* Do nothing */ - } else { return false } - } else { return false } - } - Case => { - expecting.push(vec![Esac]) - } - Esac => { - if let Some(frame) = expecting.pop() { - if frame.contains(&Esac) { - /* Do nothing */ - } else { return false } - } else { return false } - } - _ => { /* Do nothing */ } - } - } - - expecting.is_empty() + shenv.new_input(line); + let tokens = Lexer::new(line.to_string(),shenv).lex(); + Parser::new(tokens, shenv).parse().is_ok() } impl<'a> Validator for SynHelper<'a> { diff --git a/src/shellenv/exec_ctx.rs b/src/shellenv/exec_ctx.rs index 48690b8..cd5510f 100644 --- a/src/shellenv/exec_ctx.rs +++ b/src/shellenv/exec_ctx.rs @@ -65,6 +65,9 @@ impl ExecCtx { clone.redirs = body_redirs; clone } + pub fn redirs(&self) -> &Vec { + &self.redirs + } pub fn sort_redirs(&self) -> (Vec,Vec) { let mut cond_redirs = vec![]; let mut body_redirs = vec![]; diff --git a/src/shellenv/mod.rs b/src/shellenv/mod.rs index b5b87e1..e6038c0 100644 --- a/src/shellenv/mod.rs +++ b/src/shellenv/mod.rs @@ -75,7 +75,9 @@ pub fn enable_reaping() -> ShResult<()> { } pub fn attach_tty(pgid: Pid) -> ShResult<()> { - if !isatty(0).unwrap_or(false) || pgid == term_ctlr() { + // If we aren't attached to a terminal, the pgid already controls it, or the process group does not exist + // Then return ok + if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() { return Ok(()) } log!(DEBUG, "Attaching tty to pgid: {}",pgid); diff --git a/src/shellenv/shenv.rs b/src/shellenv/shenv.rs index ceee364..9d5b2fa 100644 --- a/src/shellenv/shenv.rs +++ b/src/shellenv/shenv.rs @@ -31,6 +31,24 @@ impl ShEnv { pub fn input_slice(&self, span: Rc>) -> &str { &self.input_man.get_slice(span).unwrap_or_default() } + pub fn source_file(&mut self, path: PathBuf) -> ShResult<()> { + if path.is_file() { + log!(DEBUG, "sourcing {}", path.to_str().unwrap()); + let mut file = std::fs::File::open(path)?; + let mut buf = String::new(); + file.read_to_string(&mut buf)?; + + exec_input(buf, self)?; + } + Ok(()) + } + pub fn source_rc(&mut self) -> ShResult<()> { + log!(DEBUG, "sourcing rc"); + let path_raw = std::env::var("FERN_RC")?; + let path = PathBuf::from(path_raw); + self.source_file(path)?; + Ok(()) + } pub fn expand_input(&mut self, new: &str, repl_span: Rc>) -> Vec { log!(DEBUG,repl_span); if repl_span.borrow().expanded { @@ -131,17 +149,22 @@ impl ShEnv { &mut self.logic } pub fn save_io(&mut self) -> ShResult<()> { - let ctx = self.ctx_mut(); - let stdin = ctx.masks().stdin().get_fd(); - let stdout = ctx.masks().stdout().get_fd(); - let stderr = ctx.masks().stderr().get_fd(); + if self.ctx_mut().saved_io().is_none() { + let ctx = self.ctx_mut(); + let stdin = ctx.masks().stdin().get_fd(); + let stdout = ctx.masks().stdout().get_fd(); + let stderr = ctx.masks().stderr().get_fd(); - let saved_in = dup(stdin)?; - let saved_out = dup(stdout)?; - let saved_err = dup(stderr)?; + let saved_in = dup(stdin)?; + log!(DEBUG, saved_in); + let saved_out = dup(stdout)?; + log!(DEBUG, saved_out); + let saved_err = dup(stderr)?; + log!(DEBUG, saved_err); - let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err); - *ctx.saved_io() = Some(saved_io); + let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err); + *ctx.saved_io() = Some(saved_io); + } Ok(()) } pub fn reset_io(&mut self) -> ShResult<()> { diff --git a/src/shellenv/vars.rs b/src/shellenv/vars.rs index 2e44a8f..6a06ec5 100644 --- a/src/shellenv/vars.rs +++ b/src/shellenv/vars.rs @@ -94,6 +94,8 @@ impl VarTab { env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home)); env::set_var("FERN_HIST",format!("{}/.fern_hist",home)); + env_vars.insert("FERN_RC".into(),format!("{}/.fernrc",home)); + env::set_var("FERN_RC",format!("{}/.fernrc",home)); env_vars }