Various improvements
This commit is contained in:
@@ -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
15
src/builtin/source.rs
Normal 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(())
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
103
src/parse/mod.rs
103
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() {
|
||||
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 },
|
||||
|
||||
@@ -121,6 +121,7 @@ pub use crate::{
|
||||
alias::alias,
|
||||
control_flow::sh_flow,
|
||||
export::export,
|
||||
source::source,
|
||||
jobctl::{
|
||||
continue_job,
|
||||
jobs
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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![];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user