Various improvements

This commit is contained in:
2025-03-07 03:36:25 -05:00
parent d35ff7bc6f
commit 11576e9e08
15 changed files with 305 additions and 175 deletions

View File

@@ -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",
];

15
src/builtin/source.rs Normal file
View File

@@ -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(())
}

View File

@@ -9,7 +9,6 @@ pub mod shellcmd;
pub fn exec_input<S: Into<String>>(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<S: Into<String>>(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<S: Into<String>>(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<CmdGuard>, 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 {
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 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<Token>, shenv: &mut ShEnv) -> (Vec<String>, Vec<String>
log!(DEBUG, argv_s);
let mut envp = vec![];
let env_vars = shenv.vars().env().clone();
let mut entries = env_vars.iter().collect::<VecDeque<(&String,&String)>>();
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);

View File

@@ -3,27 +3,26 @@ use crate::{parse::lex::SEPARATORS, prelude::*};
pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> {
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 !done {
done = true;
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 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());
for token in new_tokens.iter_mut() {
work_stack.bpush(token.clone());
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);
@@ -32,6 +31,10 @@ pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> {
tokens.push(token);
}
}
if !done {
work_stack.extend(tokens.drain(..));
}
}
tokens
}

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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() {
match token.rule() {
TkRule::Ident => {
node_toks.push(token.clone());
tokens = &tokens[1..];
match token.rule() {
TkRule::Whitespace => continue,
TkRule::Ident => {
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,16 +523,27 @@ 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 }
match token.rule() {
_ if token.as_raw(shenv) == "in" => break,
TkRule::Ident => {
vars.push(token.clone());
} else {
}
_ 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() {
let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop vars",span,shenv))
@@ -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() {
match token.rule() {
TkRule::Sep => break,
TkRule::Ident => {
arr.push(token.clone());
} else {
}
_ 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 },

View File

@@ -121,6 +121,7 @@ pub use crate::{
alias::alias,
control_flow::sh_flow,
export::export,
source::source,
jobctl::{
continue_job,
jobs

View File

@@ -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);
}
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);

View File

@@ -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))
}
}

View File

@@ -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<TkRule>> = 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> {

View File

@@ -65,6 +65,9 @@ impl ExecCtx {
clone.redirs = body_redirs;
clone
}
pub fn redirs(&self) -> &Vec<Redir> {
&self.redirs
}
pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) {
let mut cond_redirs = vec![];
let mut body_redirs = vec![];

View File

@@ -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);

View File

@@ -31,6 +31,24 @@ impl ShEnv {
pub fn input_slice(&self, span: Rc<RefCell<Span>>) -> &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<RefCell<Span>>) -> Vec<Token> {
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<()> {
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)?;
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);
}
Ok(())
}
pub fn reset_io(&mut self) -> ShResult<()> {

View File

@@ -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
}