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 read;
pub mod alias; pub mod alias;
pub mod control_flow; pub mod control_flow;
pub mod source;
pub const BUILTINS: [&str;13] = [ pub const BUILTINS: [&str;14] = [
"echo", "echo",
"cd", "cd",
"pwd", "pwd",
@@ -21,4 +22,5 @@ pub const BUILTINS: [&str;13] = [
"continue", "continue",
"return", "return",
"break", "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<()> { pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()> {
let input = input.into(); let input = input.into();
shenv.new_input(&input); shenv.new_input(&input);
log!(INFO, "New input: {:?}", input);
let token_stream = Lexer::new(input,shenv).lex(); 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 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 Err(e) = Executor::new(syn_tree, shenv).walk() {
if let ShErrKind::CleanExit = e.kind() { if let ShErrKind::CleanExit = e.kind() {
let code = shenv.get_code(); 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()) return Err(e.into())
} }
} }
log!(INFO, "Executing done in {:?}", exec_start.elapsed());
Ok(()) Ok(())
} }
@@ -41,7 +42,6 @@ impl<'a> Executor<'a> {
Self { ast, shenv } Self { ast, shenv }
} }
pub fn walk(&mut self) -> ShResult<()> { pub fn walk(&mut self) -> ShResult<()> {
self.shenv.ctx_mut().descend()?;
self.shenv.inputman_mut().save_state(); self.shenv.inputman_mut().save_state();
log!(DEBUG, "Starting walk"); log!(DEBUG, "Starting walk");
while let Some(node) = self.ast.next_node() { 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())? exec_list(cmds, self.shenv).try_blame(node.as_raw(self.shenv),node.span())?
} else { unreachable!() } } else { unreachable!() }
} }
self.shenv.ctx_mut().ascend();
self.shenv.inputman_mut().load_state(); self.shenv.inputman_mut().load_state();
log!(DEBUG, "passed"); log!(DEBUG, "passed");
Ok(()) Ok(())
@@ -77,19 +76,25 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
} }
} }
} }
log!(TRACE, "{:?}", *cmd.rule()); dispatch_node(cmd, shenv)?;
match *cmd.rule() { }
NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(cmd_raw, span)?, Ok(())
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)?, fn dispatch_node(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
NdRule::ForLoop {..} => shellcmd::exec_for(cmd, shenv).try_blame(cmd_raw, span)?, let node_raw = node.as_raw(shenv);
NdRule::Case {..} => shellcmd::exec_case(cmd, shenv).try_blame(cmd_raw, span)?, let span = node.span();
NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(cmd_raw, span)?, match *node.rule() {
NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(cmd_raw, span)?, NdRule::Command {..} => dispatch_command(node, shenv).try_blame(node_raw, span)?,
NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(cmd_raw, span)?, NdRule::Subshell {..} => exec_subshell(node,shenv).try_blame(node_raw, span)?,
_ => unimplemented!() 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(()) Ok(())
} }
@@ -189,10 +194,10 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let body_raw = body.as_raw(shenv); let body_raw = body.as_raw(shenv);
match exec_input(body_raw, shenv) { match exec_input(body_raw, shenv) {
Ok(()) => sh_quit(0), Ok(()) => exit(0),
Err(e) => { Err(e) => {
eprintln!("{}",e); eprintln!("{}",e);
sh_quit(1); exit(1);
} }
} }
} else { } else {
@@ -209,10 +214,10 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
} }
let body_raw = body.as_raw(shenv); let body_raw = body.as_raw(shenv);
match exec_input(body_raw, shenv) { match exec_input(body_raw, shenv) {
Ok(()) => sh_quit(0), Ok(()) => exit(0),
Err(e) => { Err(e) => {
eprintln!("{}",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)?, "return" => sh_flow(node, shenv, ShErrKind::FuncReturn)?,
"break" => sh_flow(node, shenv, ShErrKind::LoopBreak)?, "break" => sh_flow(node, shenv, ShErrKind::LoopBreak)?,
"continue" => sh_flow(node, shenv, ShErrKind::LoopContinue)?, "continue" => sh_flow(node, shenv, ShErrKind::LoopContinue)?,
"source" => source(node, shenv)?,
_ => unimplemented!("Have not yet implemented support for builtin `{}'",command) _ => unimplemented!("Have not yet implemented support for builtin `{}'",command)
} }
log!(TRACE, "done"); log!(TRACE, "done");
@@ -302,7 +308,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let mut pids = vec![]; let mut pids = vec![];
while let Some(cmd) = cmds.pop_front() { 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 // If we are on the last command, don't make new pipes
(None,None) (None,None)
} else { } else {
@@ -314,55 +320,65 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
cmd_names.push(cmd_name); cmd_names.push(cmd_name);
} else if let NdRule::Subshell {..} = cmd.rule() { } else if let NdRule::Subshell {..} = cmd.rule() {
cmd_names.push("subshell".to_string()); cmd_names.push("subshell".to_string());
} else { unimplemented!() } } else {
cmd_names.push("shell cmd".to_string());
}
match unsafe { fork()? } { match unsafe { fork()? } {
Child => { Child => {
// Set NO_FORK since we are already in a fork, to prevent unnecessarily forking again // Set NO_FORK since we are already in a fork, to prevent unnecessarily forking again
shenv.ctx_mut().set_flag(ExecFlags::NO_FORK); 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 // 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 { if let Some(r_pipe) = r_pipe.take() {
close(r_pipe.as_raw_fd())?; close(r_pipe)?;
} }
// Create some redirections // Create some redirections
if let Some(w_pipe) = w_pipe { if let Some(w_pipe) = w_pipe.take() {
let wpipe_redir = Redir::output(1, w_pipe); if !cmds.is_empty() {
shenv.ctx_mut().push_rdr(wpipe_redir); let wpipe_redir = Redir::output(1, w_pipe);
shenv.ctx_mut().push_rdr(wpipe_redir);
}
} }
// Use the r_pipe created in the last iteration // 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); let rpipe_redir = Redir::input(0, prev_rpipe);
shenv.ctx_mut().push_rdr(rpipe_redir); 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); exit(0);
} }
Parent { child } => { Parent { child } => {
// Close the write pipe out here to signal EOF // Close the write pipe out here to signal EOF
if let Some(w_pipe) = w_pipe { if let Some(w_pipe) = w_pipe.take() {
close(w_pipe.as_raw_fd())?; close(w_pipe)?;
} }
if pgid.is_none() { if pgid.is_none() {
pgid = Some(child); pgid = Some(child);
} }
pids.push(child); pids.push(child);
if let Some(pipe) = prev_rpipe {
close(pipe)?;
}
prev_rpipe = r_pipe; prev_rpipe = r_pipe;
} }
} }
} }
let mut children = vec![];
for (i,pid) in pids.iter().enumerate() { for (i,pid) in pids.iter().enumerate() {
let command = cmd_names.get(i).unwrap(); let command = cmd_names.get(i).unwrap();
let children = vec![ let child = ChildProc::new(*pid, Some(&command), pgid)?;
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)?;
} }
let job = JobBldr::new()
.with_children(children)
.with_pgid(pgid.unwrap())
.build();
wait_fg(job, shenv)?;
} else { unreachable!() } } else { unreachable!() }
Ok(()) Ok(())
} }
@@ -370,7 +386,6 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
log!(DEBUG, "Executing command"); log!(DEBUG, "Executing command");
let blame = node.span(); let blame = node.span();
let blame_raw = node.as_raw(shenv);
let rule = node.into_rule(); let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = 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(); let command = argv.first().unwrap().to_string();
if get_bin_path(&command, shenv).is_some() { if get_bin_path(&command, shenv).is_some() {
shenv.save_io()?; log!(DEBUG, "{:?}",shenv.ctx().flags());
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
log!(TRACE, "Not forking"); log!(TRACE, "Not forking");
shenv.collect_redirs(redirs); shenv.collect_redirs(redirs);
log!(DEBUG, "{:?}",shenv.ctx().redirs());
if let Err(e) = shenv.ctx_mut().activate_rdrs() { if let Err(e) = shenv.ctx_mut().activate_rdrs() {
eprintln!("{:?}",e); eprintln!("{:?}",e);
exit(1); exit(1);
@@ -430,9 +446,8 @@ fn prep_execve(argv: Vec<Token>, shenv: &mut ShEnv) -> (Vec<String>, Vec<String>
log!(DEBUG, argv_s); log!(DEBUG, argv_s);
let mut envp = vec![]; let mut envp = vec![];
let env_vars = shenv.vars().env().clone(); let mut env_vars = shenv.vars().env().iter();
let mut entries = env_vars.iter().collect::<VecDeque<(&String,&String)>>(); while let Some(entry) = env_vars.next() {
while let Some(entry) = entries.fpop() {
let key = entry.0; let key = entry.0;
let val = entry.1; let val = entry.1;
let formatted = format!("{}={}",key,val); let formatted = format!("{}={}",key,val);

View File

@@ -3,33 +3,36 @@ use crate::{parse::lex::SEPARATORS, prelude::*};
pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> { pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> {
let mut tokens = vec![]; let mut tokens = vec![];
let mut work_stack = VecDeque::new(); let mut work_stack = VecDeque::new();
let mut expanded_aliases = vec![];
let logic = shenv.logic().clone(); let logic = shenv.logic().clone();
let mut done = false;
// Start with the candidate token in the work queue // Start with the candidate token in the work queue
work_stack.bpush(candidate); work_stack.bpush(candidate);
// Process until there are no more tokens in the queue // Process until there are no more tokens in the queue
while let Some(token) = work_stack.fpop() { while !done {
if token.rule() == TkRule::Ident { done = true;
let candidate_str = token.as_raw(shenv); while let Some(token) = work_stack.fpop() {
if let Some(alias) = logic.get_alias(&candidate_str) { if token.rule() == TkRule::Ident {
// Expand the alias only if it hasn't been expanded yet let cand_str = token.as_raw(shenv);
if !expanded_aliases.contains(&candidate_str) { if let Some(alias) = logic.get_alias(&cand_str) {
expanded_aliases.push(candidate_str); done = false;
let mut new_tokens = shenv.expand_input(alias, token.span()); if !token.span().borrow().expanded {
for token in new_tokens.iter_mut() { let mut new_tokens = shenv.expand_input(alias, token.span());
work_stack.bpush(token.clone()); new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace);
for token in &new_tokens {
tokens.push(token.clone());
}
} }
} else { } else {
// If already expanded, just add the token to the output
tokens.push(token); tokens.push(token);
} }
} else { } else {
tokens.push(token); tokens.push(token);
} }
} else { }
tokens.push(token); if !done {
work_stack.extend(tokens.drain(..));
} }
} }
tokens tokens

View File

@@ -17,6 +17,9 @@ use crate::prelude::*;
pub fn main() { pub fn main() {
sig_setup(); sig_setup();
let mut shenv = ShEnv::new(); let mut shenv = ShEnv::new();
if let Err(e) = shenv.source_rc() {
eprintln!("Error sourcing rc file: {}", e.to_string());
}
loop { loop {
log!(TRACE, "Entered loop"); log!(TRACE, "Entered loop");

View File

@@ -19,7 +19,15 @@ pub const KEYWORDS: [TkRule;14] = [
TkRule::Esac 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::Sep,
TkRule::AndOp, TkRule::AndOp,
TkRule::OrOp, TkRule::OrOp,
@@ -94,6 +102,10 @@ impl Token {
self.rule self.rule
} }
pub fn rule_mut(&mut self) -> &mut TkRule {
&mut self.rule
}
pub fn as_raw(&self, shenv: &mut ShEnv) -> String { pub fn as_raw(&self, shenv: &mut ShEnv) -> String {
shenv.input_slice(self.span()).to_string() shenv.input_slice(self.span()).to_string()
} }
@@ -241,7 +253,7 @@ impl TkRule {
try_match!(While,input); try_match!(While,input);
try_match!(Until,input); try_match!(Until,input);
try_match!(For,input); try_match!(For,input);
try_match!(In,input); //try_match!(In,input);
try_match!(Select,input); try_match!(Select,input);
try_match!(Do,input); try_match!(Do,input);
try_match!(Done,input); try_match!(Done,input);
@@ -547,6 +559,7 @@ tkrule_def!(For, |input: &str| {
None None
} }
}); });
/*
tkrule_def!(In, |input: &str| { tkrule_def!(In, |input: &str| {
if input.starts_with("in") { if input.starts_with("in") {
match input.chars().nth(2) { match input.chars().nth(2) {
@@ -558,6 +571,7 @@ tkrule_def!(In, |input: &str| {
None None
} }
}); });
*/
tkrule_def!(Select, |input: &str| { tkrule_def!(Select, |input: &str| {
if input.starts_with("select") { if input.starts_with("select") {
match input.chars().nth(6) { match input.chars().nth(6) {

View File

@@ -4,7 +4,7 @@ use std::{iter::Peekable, str::FromStr};
use crate::prelude::*; use crate::prelude::*;
use lex::{Span, TkRule, Token, KEYWORDS}; use lex::{Span, TkRule, Token, KEYWORDS, OPERATORS, SEPARATORS};
bitflags! { bitflags! {
#[derive(Debug,Clone,Copy,PartialEq,Eq)] #[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| { ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
try_rules!(tokens, shenv, try_rules!(tokens, shenv,
ShellCmd,
Pipeline, Pipeline,
ShellCmd,
Subshell, Subshell,
Assignment, Assignment,
Command Command
@@ -365,10 +365,16 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
tokens = &tokens[1..]; tokens = &tokens[1..];
match token.rule() { match token.rule() {
TkRule::Whitespace => continue, TkRule::Whitespace => continue,
TkRule::Ident => { TkRule::Ident | TkRule::VarSub => {
pat = Some(token.clone()); pat = Some(token.clone());
break 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)) _ => 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)) return Err(err("Expected an ident in case statement", node_toks.last().unwrap().span(), shenv))
} }
let pat = pat.unwrap(); let pat = pat.unwrap();
tokens_iter = tokens.iter().peekable();
let mut closed = false; let mut closed = false;
while let Some(token) = tokens_iter.next() { while let Some(token) = tokens_iter.next() {
node_toks.push(token.clone());
tokens = &tokens[1..];
match token.rule() { match token.rule() {
TkRule::Whitespace => continue,
TkRule::Ident => { TkRule::Ident => {
node_toks.push(token.clone());
tokens = &tokens[1..];
if token.as_raw(shenv) != "in" { if token.as_raw(shenv) != "in" {
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))
} else { } else {
closed = true; closed = true;
} }
} }
TkRule::Sep => { TkRule::Sep => {
if closed { node_toks.push(token.clone());
break 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() { if tokens_iter.peek().is_none() {
return Err(err("Expected `in` after case statement pattern", node_toks.last().unwrap().span(), shenv)) 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 break
} }
} }
_ if OPERATORS.contains(&token.rule()) => {
if closed { break }
}
TkRule::RedirOp if closed => { TkRule::RedirOp if closed => {
node_toks.push(token.clone()); node_toks.push(token.clone());
tokens = &tokens[1..]; tokens = &tokens[1..];
@@ -506,14 +523,25 @@ ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
} else { return Ok(None) } } else { return Ok(None) }
while let Some(token) = tokens_iter.next() { while let Some(token) = tokens_iter.next() {
log!(DEBUG, token);
node_toks.push(token.clone()); node_toks.push(token.clone());
tokens = &tokens[1..]; tokens = &tokens[1..];
if let TkRule::Ident = token.rule() { match token.rule() {
if token.as_raw(shenv) == "in" { break } _ if token.as_raw(shenv) == "in" => break,
vars.push(token.clone()); TkRule::Ident => {
} else { vars.push(token.clone());
let span = get_span(&node_toks, shenv)?; }
return Err(err("Expected an ident in for loop vars",span,shenv)) _ 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() { 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() { while let Some(token) = tokens_iter.next() {
node_toks.push(token.clone()); node_toks.push(token.clone());
tokens = &tokens[1..]; tokens = &tokens[1..];
if token.rule() == TkRule::Sep { break } match token.rule() {
if let TkRule::Ident = token.rule() { TkRule::Sep => break,
arr.push(token.clone()); TkRule::Ident => {
} else { arr.push(token.clone());
let span = get_span(&node_toks, shenv)?; }
return Err(err("Expected an ident in for loop array",span,shenv)) _ 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() { if arr.is_empty() {
log!(ERROR,node_toks);
let span = get_span(&node_toks, shenv)?; let span = get_span(&node_toks, shenv)?;
return Err(err("Expected an ident in for loop array",span,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..]; tokens = &tokens[1..];
if closed { break } if closed { break }
} }
_ if OPERATORS.contains(&token.rule()) => {
if closed { break }
}
TkRule::RedirOp if closed => { TkRule::RedirOp if closed => {
node_toks.push(token.clone()); node_toks.push(token.clone());
tokens = &tokens[1..]; tokens = &tokens[1..];
@@ -763,6 +805,9 @@ ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
tokens = &tokens[used..]; tokens = &tokens[used..];
redirs.push(redir); redirs.push(redir);
} }
_ if OPERATORS.contains(&token.rule()) => {
if closed { break }
}
_ => { _ => {
let span = get_span(&node_toks, shenv)?; let span = get_span(&node_toks, shenv)?;
return Err(err(&format!("Unexpected token in if statement: {:?}",token.rule()),span,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) 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 span = get_span(&node_toks,shenv)?;
let node = Node { let node = Node {
node_rule: NdRule::FuncDef { name, body }, 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() { while let Some(token) = tokens_iter.peek() {
match token.rule() { 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 // If there are no commands or only one, this isn't a pipeline
match cmds.len() { match cmds.len() {
0 | 1 => return Ok(None), 0 | 1 => return Ok(None),
@@ -1158,6 +1213,12 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
if let Some(ref cmd) = cmd { if let Some(ref cmd) = cmd {
node_toks.extend(cmd.tokens().clone()); 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 span = get_span(&node_toks,shenv)?;
let node = Node { let node = Node {
node_rule: NdRule::Assignment { assignments, cmd }, node_rule: NdRule::Assignment { assignments, cmd },
@@ -1167,6 +1228,14 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
}; };
return Ok(Some(node)) return Ok(Some(node))
} else { } 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 span = get_span(&node_toks,shenv)?;
let node = Node { let node = Node {
node_rule: NdRule::Assignment { assignments, cmd: None }, node_rule: NdRule::Assignment { assignments, cmd: None },

View File

@@ -121,6 +121,7 @@ pub use crate::{
alias::alias, alias::alias,
control_flow::sh_flow, control_flow::sh_flow,
export::export, export::export,
source::source,
jobctl::{ jobctl::{
continue_job, continue_job,
jobs 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 tokens = Lexer::new(line.to_string(),&mut shenv_clone).lex().into_iter();
let mut is_command = true; let mut is_command = true;
let mut in_array = false; let mut in_array = false;
let mut in_case = false;
while let Some(token) = tokens.next() { while let Some(token) = tokens.next() {
let raw = token.as_raw(&mut shenv_clone); let raw = token.as_raw(&mut shenv_clone);
@@ -32,19 +33,45 @@ impl<'a> Highlighter for SynHelper<'a> {
let styled = &raw.styled(Style::Cyan); let styled = &raw.styled(Style::Cyan);
result.push_str(&styled); 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 => { TkRule::FuncName => {
let name = raw.strip_suffix("()").unwrap_or(&raw); let name = raw.strip_suffix("()").unwrap_or(&raw);
let styled = name.styled(Style::Cyan); let styled = name.styled(Style::Cyan);
let rebuilt = format!("{styled}()"); let rebuilt = format!("{styled}()");
result.push_str(&rebuilt); result.push_str(&rebuilt);
} }
_ if KEYWORDS.contains(&token.rule()) => { TkRule::DQuote | TkRule::SQuote => {
if &raw == "for" { let styled = raw.styled(Style::BrightYellow);
in_array = true;
}
let styled = &raw.styled(Style::Yellow);
result.push_str(&styled); 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 => { TkRule::BraceGrp => {
let body = &raw[1..raw.len() - 1]; let body = &raw[1..raw.len() - 1];
let highlighted = self.highlight(body, 0).to_string(); let highlighted = self.highlight(body, 0).to_string();
@@ -65,16 +92,30 @@ impl<'a> Highlighter for SynHelper<'a> {
is_command = false; is_command = false;
result.push_str(&rebuilt); 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 => { TkRule::Ident => {
if in_array { if in_array || in_case {
if &raw == "in" { if &raw == "in" {
let styled = &raw.styled(Style::Yellow); let styled = &raw.styled(Style::Yellow);
result.push_str(&styled); result.push_str(&styled);
if in_case { in_case = false };
} else { } else {
let styled = &raw.styled(Style::Magenta); let styled = &raw.styled(Style::Magenta);
result.push_str(&styled); result.push_str(&styled);
} }
} else if raw.starts_with(['"','\'']) {
let styled = &raw.styled(Style::BrightYellow);
result.push_str(&styled);
} else if &raw == "{" || &raw == "}" { } else if &raw == "{" || &raw == "}" {
result.push_str(&raw); result.push_str(&raw);

View File

@@ -78,7 +78,7 @@ impl<'a> Hinter for SynHelper<'a> {
} }
let history = ctx.history(); let history = ctx.history();
let result = self.hist_search(line, 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)) Some(SynHint::new(window))
} }
} }

View File

@@ -7,23 +7,26 @@ use super::readline::SynHelper;
pub fn check_delims(line: &str) -> bool { pub fn check_delims(line: &str) -> bool {
let mut delim_stack = vec![]; let mut delim_stack = vec![];
let mut chars = line.chars(); let mut chars = line.chars();
let mut in_case = false; let mut case_depth: u64 = 0;
let mut case_check = String::new(); let mut case_check = String::new();
let mut in_quote = None; // Tracks which quote type is open (`'` or `"`) let mut in_quote = None; // Tracks which quote type is open (`'` or `"`)
while let Some(ch) = chars.next() { while let Some(ch) = chars.next() {
case_check.push(ch); case_check.push(ch);
if case_check.len() > 4 {
case_check = case_check[1..].to_string();
}
if case_check.ends_with("case") { if case_check.ends_with("case") {
in_case = true; case_depth += 1;
} }
if case_check.ends_with("esac") { if case_check.ends_with("esac") {
in_case = false; case_depth = case_depth.saturating_sub(1);
} }
match ch { match ch {
'{' | '(' | '[' if in_quote.is_none() => delim_stack.push(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('{') => return false,
')' if in_quote.is_none() && delim_stack.pop() != Some('(') => { ')' if in_quote.is_none() && delim_stack.pop() != Some('(') => {
if !in_case { if case_depth == 0 {
return false return false
} }
} }
@@ -44,75 +47,9 @@ pub fn check_delims(line: &str) -> bool {
} }
pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool { pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool {
use TkRule::*; shenv.new_input(line);
let mut expecting: Vec<Vec<TkRule>> = vec![]; let tokens = Lexer::new(line.to_string(),shenv).lex();
let mut tokens = Lexer::new(line.to_string(),shenv).lex().into_iter(); Parser::new(tokens, shenv).parse().is_ok()
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()
} }
impl<'a> Validator for SynHelper<'a> { impl<'a> Validator for SynHelper<'a> {

View File

@@ -65,6 +65,9 @@ impl ExecCtx {
clone.redirs = body_redirs; clone.redirs = body_redirs;
clone clone
} }
pub fn redirs(&self) -> &Vec<Redir> {
&self.redirs
}
pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) { pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) {
let mut cond_redirs = vec![]; let mut cond_redirs = vec![];
let mut body_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<()> { 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(()) return Ok(())
} }
log!(DEBUG, "Attaching tty to pgid: {}",pgid); 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 { pub fn input_slice(&self, span: Rc<RefCell<Span>>) -> &str {
&self.input_man.get_slice(span).unwrap_or_default() &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> { pub fn expand_input(&mut self, new: &str, repl_span: Rc<RefCell<Span>>) -> Vec<Token> {
log!(DEBUG,repl_span); log!(DEBUG,repl_span);
if repl_span.borrow().expanded { if repl_span.borrow().expanded {
@@ -131,17 +149,22 @@ impl ShEnv {
&mut self.logic &mut self.logic
} }
pub fn save_io(&mut self) -> ShResult<()> { pub fn save_io(&mut self) -> ShResult<()> {
let ctx = self.ctx_mut(); if self.ctx_mut().saved_io().is_none() {
let stdin = ctx.masks().stdin().get_fd(); let ctx = self.ctx_mut();
let stdout = ctx.masks().stdout().get_fd(); let stdin = ctx.masks().stdin().get_fd();
let stderr = ctx.masks().stderr().get_fd(); let stdout = ctx.masks().stdout().get_fd();
let stderr = ctx.masks().stderr().get_fd();
let saved_in = dup(stdin)?; let saved_in = dup(stdin)?;
let saved_out = dup(stdout)?; log!(DEBUG, saved_in);
let saved_err = dup(stderr)?; 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); let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err);
*ctx.saved_io() = Some(saved_io); *ctx.saved_io() = Some(saved_io);
}
Ok(()) Ok(())
} }
pub fn reset_io(&mut self) -> ShResult<()> { 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::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home)); env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home));
env::set_var("FERN_HIST",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 env_vars
} }