Implemented scoping for expansions

This commit is contained in:
2025-03-08 01:38:42 -05:00
parent 67977f96eb
commit cdcfb23edb
18 changed files with 271 additions and 141 deletions

View File

@@ -2,52 +2,82 @@ use shellenv::jobs::{ChildProc, JobBldr};
use crate::prelude::*; use crate::prelude::*;
bitflags! {
#[derive(Debug,Clone,Copy)]
pub struct EchoFlags: u32 {
const USE_ESCAPE = 0b0001;
const NO_ESCAPE = 0b0010;
const STDERR = 0b0100;
const NO_NEWLINE = 0b1000;
}
}
pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> { pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule(); let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = rule { if let NdRule::Command { argv, redirs } = rule {
let argv = argv.drop_first().as_strings(shenv); let mut argv_iter = argv.into_iter().skip(1).peekable();
let mut formatted = argv.join(" "); let mut echo_flags = EchoFlags::empty();
formatted.push('\n'); while let Some(arg) = argv_iter.peek() {
let blame = arg.span();
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { let raw = arg.as_raw(shenv);
shenv.collect_redirs(redirs); if raw.starts_with('-') {
if let Err(e) = shenv.ctx_mut().activate_rdrs() { let _ = argv_iter.next();
eprintln!("{:?}",e); let mut options = raw.strip_prefix('-').unwrap().chars();
exit(1); while let Some(opt) = options.next() {
} match opt {
if let Err(e) = write_out(formatted) { 'r' => echo_flags |= EchoFlags::STDERR,
eprintln!("{:?}",e); 'n' => echo_flags |= EchoFlags::NO_NEWLINE,
exit(1); 'e' => {
} if echo_flags.contains(EchoFlags::NO_ESCAPE) {
exit(0); return Err(
} else { ShErr::full(
match unsafe { fork()? } { ShErrKind::ExecFail,
Child => { "the 'e' and 'E' flags are mutually exclusive",
shenv.collect_redirs(redirs); shenv.get_input(),
if let Err(e) = shenv.ctx_mut().activate_rdrs() { blame
eprintln!("{:?}",e); )
exit(1); )
}
echo_flags |= EchoFlags::USE_ESCAPE;
}
'E' => {
if echo_flags.contains(EchoFlags::USE_ESCAPE) {
return Err(
ShErr::full(
ShErrKind::ExecFail,
"the 'e' and 'E' flags are mutually exclusive",
shenv.get_input(),
blame
)
)
}
echo_flags |= EchoFlags::NO_ESCAPE;
}
_ => return Err(
ShErr::full(
ShErrKind::ExecFail,
format!("Unrecognized echo option"),
shenv.get_input(),
blame
)
)
} }
if let Err(e) = write_out(formatted) {
eprintln!("{:?}",e);
exit(1);
}
exit(0);
}
Parent { child } => {
shenv.reset_io()?;
let children = vec![
ChildProc::new(child, Some("echo"), Some(child))?
];
let job = JobBldr::new()
.with_children(children)
.with_pgid(child)
.build();
wait_fg(job, shenv)?;
} }
} else {
break
} }
} }
let argv = argv_iter.collect::<Vec<_>>().as_strings(shenv);
let mut formatted = argv.join(" ");
if !echo_flags.contains(EchoFlags::NO_NEWLINE) {
formatted.push('\n');
}
shenv.collect_redirs(redirs);
shenv.ctx_mut().activate_rdrs()?;
write_out(formatted)?;
} else { unreachable!() } } else { unreachable!() }
Ok(()) Ok(())
} }

View File

@@ -162,7 +162,7 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
flags |= flag flags |= flag
} }
} }
read_jobs(|j| j.print_jobs(flags))?; write_jobs(|j| j.print_jobs(flags))?;
shenv.set_code(0); shenv.set_code(0);
} else { unreachable!() } } else { unreachable!() }

View File

@@ -21,15 +21,18 @@ 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(); let exec_start = std::time::Instant::now();
shenv.save_io()?;
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();
sh_quit(code); sh_quit(code);
} else { } else {
shenv.reset_io()?;
return Err(e.into()) return Err(e.into())
} }
} }
log!(INFO, "Executing done in {:?}", exec_start.elapsed()); log!(INFO, "Executing done in {:?}", exec_start.elapsed());
shenv.reset_io()?;
Ok(()) Ok(())
} }
@@ -43,7 +46,7 @@ impl<'a> Executor<'a> {
Self { ast, shenv } Self { ast, shenv }
} }
pub fn walk(&mut self) -> ShResult<()> { pub fn walk(&mut self) -> ShResult<()> {
self.shenv.inputman_mut().save_state(); self.shenv.inputman_mut().push_state();
log!(TRACE, "Starting walk"); log!(TRACE, "Starting walk");
while let Some(node) = self.ast.next_node() { while let Some(node) = self.ast.next_node() {
if let NdRule::CmdList { cmds } = node.clone().into_rule() { if let NdRule::CmdList { cmds } = node.clone().into_rule() {
@@ -51,7 +54,7 @@ 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.inputman_mut().load_state(); self.shenv.inputman_mut().pop_state();
log!(TRACE, "passed"); log!(TRACE, "passed");
Ok(()) Ok(())
} }
@@ -288,7 +291,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
if EXPANSIONS.contains(&val_rule) { if EXPANSIONS.contains(&val_rule) {
let exp = match val_rule { let exp = match val_rule {
TkRule::ArithSub => expand_arith_string(val,shenv)?, TkRule::ArithSub => expand_arith_string(val,shenv)?,
TkRule::DQuote => expand_string(val, shenv), TkRule::DQuote => expand_string(val, shenv)?,
TkRule::TildeSub => expand_tilde_string(val), TkRule::TildeSub => expand_tilde_string(val),
TkRule::VarSub => { TkRule::VarSub => {
let val = shenv.vars().get_var(var); let val = shenv.vars().get_var(var);
@@ -312,7 +315,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
if EXPANSIONS.contains(&val_rule) { if EXPANSIONS.contains(&val_rule) {
let exp = match val_rule { let exp = match val_rule {
TkRule::ArithSub => expand_arith_string(val,shenv)?, TkRule::ArithSub => expand_arith_string(val,shenv)?,
TkRule::DQuote => expand_string(val, shenv), TkRule::DQuote => expand_string(val, shenv)?,
TkRule::TildeSub => expand_tilde_string(val), TkRule::TildeSub => expand_tilde_string(val),
TkRule::VarSub => { TkRule::VarSub => {
let val = shenv.vars().get_var(var); let val = shenv.vars().get_var(var);

View File

@@ -169,7 +169,7 @@ pub fn expand_arith_token(token: Token, shenv: &mut ShEnv) -> ShResult<Token> {
} }
pub fn expand_arith_string(s: &str,shenv: &mut ShEnv) -> ShResult<String> { pub fn expand_arith_string(s: &str,shenv: &mut ShEnv) -> ShResult<String> {
let mut exp = expand_string(s,shenv); let mut exp = expand_string(s,shenv)?;
if exp.starts_with('`') && s.ends_with('`') { if exp.starts_with('`') && s.ends_with('`') {
exp = exp[1..exp.len() - 1].to_string(); exp = exp[1..exp.len() - 1].to_string();
} }

View File

@@ -1,9 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
pub fn expand_cmdsub(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> { pub fn expand_cmdsub_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
let new_tokens = vec![];
let cmdsub_raw = token.as_raw(shenv); let cmdsub_raw = token.as_raw(shenv);
let body = &cmdsub_raw[2..cmdsub_raw.len() - 1].to_string(); // From '$(this)' to 'this' let output = expand_cmdsub_string(&cmdsub_raw, shenv)?;
let new_tokens = shenv.expand_input(&output, token.span());
Ok(new_tokens)
}
pub fn expand_cmdsub_string(mut s: &str, shenv: &mut ShEnv) -> ShResult<String> {
if s.starts_with("$(") && s.ends_with(')') {
s = &s[2..s.len() - 1]; // From '$(this)' to 'this'
}
let (r_pipe,w_pipe) = c_pipe()?; let (r_pipe,w_pipe) = c_pipe()?;
let pipe_redir = Redir::output(1, w_pipe); let pipe_redir = Redir::output(1, w_pipe);
@@ -14,17 +22,12 @@ pub fn expand_cmdsub(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
match unsafe { fork()? } { match unsafe { fork()? } {
Child => { Child => {
close(r_pipe).ok(); close(r_pipe).ok();
exec_input(body, shenv).abort_if_err(); exec_input(s, &mut sub_shenv).abort_if_err();
sh_quit(0); exit(0);
} }
Parent { child } => { Parent { child: _ } => {
close(w_pipe).ok(); close(w_pipe).ok();
} }
} }
let output = read_to_string(r_pipe)?; Ok(read_to_string(r_pipe)?)
if !output.is_empty() {
let lex_input = Rc::new(output);
}
Ok(new_tokens)
} }

View File

@@ -5,6 +5,7 @@ pub mod cmdsub;
pub mod arithmetic; pub mod arithmetic;
use arithmetic::expand_arith_token; use arithmetic::expand_arith_token;
use cmdsub::expand_cmdsub_token;
use vars::{expand_string, expand_var}; use vars::{expand_string, expand_var};
use tilde::expand_tilde_token; use tilde::expand_tilde_token;
@@ -27,7 +28,7 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
log!(INFO, "rule: {:?}", token.rule()); log!(INFO, "rule: {:?}", token.rule());
match token.rule() { match token.rule() {
TkRule::DQuote => { TkRule::DQuote => {
let dquote_exp = expand_string(&token.as_raw(shenv), shenv); let dquote_exp = expand_string(&token.as_raw(shenv), shenv)?;
let mut expanded = shenv.expand_input(&dquote_exp, token.span()); let mut expanded = shenv.expand_input(&dquote_exp, token.span());
processed.append(&mut expanded); processed.append(&mut expanded);
} }
@@ -43,6 +44,10 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
let arith_exp = expand_arith_token(token.clone(), shenv)?; let arith_exp = expand_arith_token(token.clone(), shenv)?;
processed.push(arith_exp); processed.push(arith_exp);
} }
TkRule::CmdSub => {
let mut cmdsub_exp = expand_cmdsub_token(token.clone(), shenv)?;
processed.append(&mut cmdsub_exp);
}
_ => { _ => {
if token.rule() != TkRule::Ident { if token.rule() != TkRule::Ident {
log!(WARN, "found this in expand_token: {:?}", token.rule()); log!(WARN, "found this in expand_token: {:?}", token.rule());

View File

@@ -1,5 +1,7 @@
use crate::{parse::lex::Token, prelude::*}; use crate::{parse::lex::Token, prelude::*};
use super::cmdsub::expand_cmdsub_string;
pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> { pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
let var_name = var_sub.as_raw(shenv); let var_name = var_sub.as_raw(shenv);
let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']); let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']);
@@ -8,7 +10,7 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
shenv.expand_input(&value, var_sub.span()) shenv.expand_input(&value, var_sub.span())
} }
pub fn expand_string(s: &str, shenv: &mut ShEnv) -> String { pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
let mut result = String::new(); let mut result = String::new();
let mut var_name = String::new(); let mut var_name = String::new();
let mut chars = s.chars().peekable(); let mut chars = s.chars().peekable();
@@ -38,6 +40,30 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> String {
expanded = true; expanded = true;
break break
} }
'(' if var_name.is_empty() => {
let mut paren_count = 1;
var_name.push_str("$(");
while let Some(ch) = chars.next() {
match ch {
'(' => {
paren_count += 1;
var_name.push(ch);
}
')' => {
paren_count -= 1;
var_name.push(ch);
if paren_count == 0 {
break
}
}
_ => var_name.push(ch)
}
}
let value = expand_cmdsub_string(&var_name, shenv)?;
result.push_str(&value);
expanded = true;
break
}
_ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => { _ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => {
var_name.push(ch); var_name.push(ch);
let value = shenv.vars().get_var(&var_name); let value = shenv.vars().get_var(&var_name);
@@ -72,5 +98,5 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> String {
_ => result.push(ch) _ => result.push(ch)
} }
} }
result Ok(result)
} }

View File

@@ -1,4 +1,6 @@
use std::fmt::Display; use std::{fmt::Display, os::fd::AsRawFd};
use nix::sys::termios;
use crate::prelude::*; use crate::prelude::*;
@@ -46,6 +48,9 @@ pub fn sh_quit(code: i32) -> ! {
job.killpg(Signal::SIGTERM).ok(); job.killpg(Signal::SIGTERM).ok();
} }
}); });
if let Some(termios) = crate::get_saved_termios() {
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
}
exit(code); exit(code);
} }

View File

@@ -9,13 +9,46 @@ pub mod signal;
pub mod prompt; pub mod prompt;
pub mod builtin; pub mod builtin;
pub mod expand; pub mod expand;
pub mod tests;
use std::os::fd::AsRawFd;
use nix::sys::termios::{self, LocalFlags, Termios};
use signal::sig_setup; use signal::sig_setup;
use crate::prelude::*; use crate::prelude::*;
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
pub fn save_termios() {
unsafe {
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
Some(termios)
} else {
None
});
}
}
pub fn get_saved_termios() -> Option<Termios> {
unsafe {
SAVED_TERMIOS.clone().flatten()
}
}
fn set_termios() {
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
termios.local_flags &= !LocalFlags::ECHOCTL;
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
}
}
pub fn main() { pub fn main() {
sig_setup(); sig_setup();
save_termios();
set_termios();
let mut shenv = ShEnv::new(); let mut shenv = ShEnv::new();
if let Err(e) = shenv.source_rc() { if let Err(e) = shenv.source_rc() {
eprintln!("Error sourcing rc file: {}", e.to_string()); eprintln!("Error sourcing rc file: {}", e.to_string());
@@ -23,13 +56,14 @@ pub fn main() {
loop { loop {
log!(TRACE, "Entered loop"); log!(TRACE, "Entered loop");
let line = match prompt::read_line(&mut shenv) { match prompt::read_line(&mut shenv) {
Ok(line) => line, Ok(line) => {
let _ = exec_input(line, &mut shenv).eprint();
}
Err(e) => { Err(e) => {
eprintln!("{}",e); eprintln!("{}",e);
continue; continue;
} }
}; };
let _ = exec_input(line, &mut shenv).eprint();
} }
} }

View File

@@ -175,6 +175,12 @@ impl Span {
pub fn shift_end(&mut self, delta: isize) { pub fn shift_end(&mut self, delta: isize) {
self.end = self.end.saturating_add_signed(delta) self.end = self.end.saturating_add_signed(delta)
} }
pub fn set_start(&mut self, start: usize) {
self.start = start
}
pub fn set_end(&mut self, end: usize) {
self.end = end
}
} }
macro_rules! try_match { macro_rules! try_match {
@@ -251,9 +257,9 @@ impl TkRule {
// Generalized rules come last // Generalized rules come last
try_match!(Whitespace,input); try_match!(Whitespace,input);
try_match!(Comment,input); try_match!(Comment,input);
try_match!(CmdSub,input);
try_match!(VarSub,input); try_match!(VarSub,input);
try_match!(ProcSub,input); try_match!(ProcSub,input);
try_match!(CmdSub,input);
try_match!(ArithSub,input); try_match!(ArithSub,input);
try_match!(AndOp,input); try_match!(AndOp,input);
try_match!(OrOp,input); try_match!(OrOp,input);

View File

@@ -391,7 +391,6 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
node_toks.push(token.clone()); node_toks.push(token.clone());
tokens = &tokens[1..]; 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;
@@ -405,7 +404,6 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
_ => { _ => {
if closed { break } if closed { break }
log!(ERROR, token); 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))
} }
} }
@@ -440,12 +438,8 @@ ndrule_def!(Case, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
let mut lists_iter = lists.iter().peekable(); let mut lists_iter = lists.iter().peekable();
while let Some(list) = lists_iter.next() { while let Some(list) = lists_iter.next() {
node_toks.extend(list.tokens.clone()); node_toks.extend(list.tokens.clone());
if lists_iter.peek().is_none() {
for token in list.tokens() {
}
}
if let Some(token) = list.tokens().last() { if let Some(token) = list.tokens().last() {
if lists_iter.peek().is_none() && (token.rule() != TkRule::Sep || token.as_raw(shenv).trim() != ";;") { if lists_iter.peek().is_none() && (token.rule() != TkRule::Sep || !token.as_raw(shenv).trim().ends_with(";;")) {
log!(ERROR, "{:?}",list.tokens()); log!(ERROR, "{:?}",list.tokens());
log!(ERROR, token); log!(ERROR, token);
log!(ERROR, "{}",token.as_raw(shenv).trim()); log!(ERROR, "{}",token.as_raw(shenv).trim());
@@ -1149,6 +1143,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
TkRule::DQuote | TkRule::DQuote |
TkRule::TildeSub | TkRule::TildeSub |
TkRule::ArithSub | TkRule::ArithSub |
TkRule::CmdSub |
TkRule::VarSub => { TkRule::VarSub => {
argv.push(token.clone()); argv.push(token.clone());
} }

View File

@@ -82,6 +82,16 @@ impl<'a> Highlighter for SynHelper<'a> {
is_command = false; is_command = false;
result.push_str(&rebuilt); result.push_str(&rebuilt);
} }
TkRule::CmdSub => {
let body = &raw[2..raw.len() - 1];
let highlighted = self.highlight(body, 0).to_string();
let styled_o_paren = "$(".styled(Style::BrightBlue);
let styled_c_paren = ")".styled(Style::BrightBlue);
let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}");
is_command = false;
result.push_str(&rebuilt);
}
TkRule::Subshell => { TkRule::Subshell => {
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();

View File

@@ -11,7 +11,6 @@ bitflags! {
pub struct ExecCtx { pub struct ExecCtx {
redirs: Vec<Redir>, redirs: Vec<Redir>,
depth: usize, depth: usize,
state_stack: Vec<Self>,
max_depth: usize, max_depth: usize,
flags: ExecFlags, flags: ExecFlags,
io_masks: IoMasks, io_masks: IoMasks,
@@ -23,36 +22,12 @@ impl ExecCtx {
Self { Self {
redirs: vec![], redirs: vec![],
depth: 0, depth: 0,
state_stack: vec![],
max_depth: 1500, max_depth: 1500,
flags: ExecFlags::empty(), flags: ExecFlags::empty(),
io_masks: IoMasks::new(), io_masks: IoMasks::new(),
saved_io: None saved_io: None
} }
} }
pub fn push_state(&mut self) {
self.state_stack.push(self.clone());
}
pub fn pop_state(&mut self) {
if let Some(state) = self.state_stack.pop() {
*self = state;
}
}
pub fn descend(&mut self) -> ShResult<()> {
self.push_state();
self.depth += 1;
log!(DEBUG, "{}",self.depth);
if self.depth > self.max_depth {
return Err(
ShErr::simple(ShErrKind::ExecFail,"Exceeded maximum execution depth")
)
}
Ok(())
}
pub fn ascend(&mut self) {
self.pop_state();
self.depth = self.depth.saturating_sub(1);
}
pub fn as_cond(&self) -> Self { pub fn as_cond(&self) -> Self {
let mut clone = self.clone(); let mut clone = self.clone();
let (cond_redirs,_) = self.sort_redirs(); let (cond_redirs,_) = self.sort_redirs();

View File

@@ -1,16 +1,41 @@
use crate::prelude::*; use crate::prelude::*;
#[derive(Clone,Debug,PartialEq)]
pub struct SavedSpan {
pointer: Rc<RefCell<Span>>,
start: usize,
end: usize,
expanded: bool
}
impl SavedSpan {
pub fn from_span(pointer: Rc<RefCell<Span>>) -> Self {
let expanded = pointer.borrow().expanded;
let start = pointer.borrow().start();
let end = pointer.borrow().end();
Self { pointer, start, end, expanded }
}
pub fn restore(&self) {
let mut deref = self.pointer.borrow_mut();
deref.set_start(self.start);
deref.set_end(self.end);
deref.expanded = self.expanded
}
pub fn into_span(self) -> Rc<RefCell<Span>> {
self.pointer
}
}
#[derive(Clone,Debug,PartialEq)] #[derive(Clone,Debug,PartialEq)]
pub struct InputMan { pub struct InputMan {
input: Option<String>, input: Option<String>,
saved_input: Option<String>,
spans: Vec<Rc<RefCell<Span>>>, spans: Vec<Rc<RefCell<Span>>>,
saved_spans: Vec<Span> saved_states: Vec<(String,Vec<SavedSpan>)>,
} }
impl InputMan { impl InputMan {
pub fn new() -> Self { pub fn new() -> Self {
Self { input: None, saved_input: None, spans: vec![], saved_spans: vec![] } Self { input: None, spans: vec![], saved_states: vec![] }
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
*self = Self::new(); *self = Self::new();
@@ -24,22 +49,27 @@ impl InputMan {
pub fn get_input_mut(&mut self) -> Option<&mut String> { pub fn get_input_mut(&mut self) -> Option<&mut String> {
self.input.as_mut() self.input.as_mut()
} }
pub fn save_state(&mut self) { pub fn push_state(&mut self) {
self.saved_input = self.input.clone(); if let Some(input) = &self.input {
self.saved_spans.clear(); let saved_input = input.clone();
for span in &self.spans { let mut saved_spans = vec![];
self.saved_spans.push(span.borrow().clone()); for span in &self.spans {
let saved_span = SavedSpan::from_span(span.clone());
saved_spans.push(saved_span);
}
self.saved_states.push((saved_input,saved_spans));
} }
} }
pub fn load_state(&mut self) { pub fn pop_state(&mut self) {
if self.saved_input.is_some() { if let Some((saved_input, saved_spans)) = self.saved_states.pop() {
self.input = self.saved_input.take(); self.input = Some(saved_input);
let mut restored_spans = vec![];
for (span, saved_span) in self.spans.iter_mut().zip(self.saved_spans.iter()) { for saved_span in saved_spans.into_iter() {
*span.borrow_mut() = saved_span.clone(); saved_span.restore();
let span = saved_span.into_span();
restored_spans.push(span);
} }
self.spans = restored_spans;
self.saved_spans.clear();
} }
} }
pub fn new_span(&mut self, start: usize, end: usize) -> Rc<RefCell<Span>> { pub fn new_span(&mut self, start: usize, end: usize) -> Rc<RefCell<Span>> {

View File

@@ -538,7 +538,7 @@ impl JobTab {
None None
} }
} }
pub fn print_jobs(&self, flags: JobCmdFlags) -> ShResult<()> { pub fn print_jobs(&mut self, flags: JobCmdFlags) -> ShResult<()> {
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) { let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
&self.jobs &self.jobs
.iter() .iter()
@@ -551,6 +551,7 @@ impl JobTab {
.map(|job| job.as_ref()) .map(|job| job.as_ref())
.collect::<Vec<Option<&Job>>>() .collect::<Vec<Option<&Job>>>()
}; };
let mut jobs_to_remove = vec![];
for job in jobs.iter().flatten() { for job in jobs.iter().flatten() {
// Skip foreground job // Skip foreground job
let id = job.tabid().unwrap(); let id = job.tabid().unwrap();
@@ -563,6 +564,12 @@ impl JobTab {
} }
// Print the job in the selected format // Print the job in the selected format
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?; write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
if job.get_stats().iter().all(|stat| matches!(stat,WtStat::Exited(_, _))) {
jobs_to_remove.push(JobID::TableID(id));
}
}
for id in jobs_to_remove {
self.remove_job(id);
} }
Ok(()) Ok(())
} }

View File

@@ -1,3 +1,5 @@
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
use crate::prelude::*; use crate::prelude::*;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
@@ -51,12 +53,9 @@ impl ShEnv {
if repl_span.borrow().expanded { if repl_span.borrow().expanded {
return vec![]; return vec![];
} }
log!(INFO, repl_span);
log!(INFO, new);
repl_span.borrow_mut().expanded = true; repl_span.borrow_mut().expanded = true;
let saved_spans = self.input_man.spans_mut().clone(); let saved_spans = self.input_man.spans_mut().clone();
let mut new_tokens = Lexer::new(new.to_string(), self).lex(); let mut new_tokens = Lexer::new(new.to_string(), self).lex();
log!(INFO, new_tokens);
*self.input_man.spans_mut() = saved_spans; *self.input_man.spans_mut() = saved_spans;
let offset = repl_span.borrow().start(); let offset = repl_span.borrow().start();
@@ -71,9 +70,8 @@ impl ShEnv {
if let Some(input) = self.input_man.get_input_mut() { if let Some(input) = self.input_man.get_input_mut() {
let old = &input[range.clone()]; let old = &input[range.clone()];
let delta: isize = new.len() as isize - old.len() as isize; let delta: isize = new.len() as isize - old.len() as isize;
log!(INFO, input);
log!(INFO, range);
input.replace_range(range, new); input.replace_range(range, new);
log!(INFO,input);
for span in self.input_man.spans_mut() { for span in self.input_man.spans_mut() {
let mut span_mut = span.borrow_mut(); let mut span_mut = span.borrow_mut();
@@ -168,11 +166,11 @@ impl ShEnv {
let saved_in = saved.stdin; let saved_in = saved.stdin;
let saved_out = saved.stdout; let saved_out = saved.stdout;
let saved_err = saved.stderr; let saved_err = saved.stderr;
dup2(0,saved_in)?; dup2(saved_in,STDIN_FILENO)?;
close(saved_in)?; close(saved_in)?;
dup2(1,saved_out)?; dup2(saved_out,STDOUT_FILENO)?;
close(saved_out)?; close(saved_out)?;
dup2(2,saved_err)?; dup2(saved_err,STDERR_FILENO)?;
close(saved_err)?; close(saved_err)?;
} }
Ok(()) Ok(())

View File

@@ -53,12 +53,7 @@ pub extern "C" fn ignore_sigchld(_: libc::c_int) {
} }
extern "C" fn handle_sigquit(_: libc::c_int) { extern "C" fn handle_sigquit(_: libc::c_int) {
write_jobs(|j| { sh_quit(0)
for job in j.jobs_mut().iter_mut().flatten() {
job.killpg(Signal::SIGTERM).ok();
}
});
exit(0);
} }
pub extern "C" fn handle_sigchld(_: libc::c_int) { pub extern "C" fn handle_sigchld(_: libc::c_int) {
@@ -125,11 +120,11 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
* We can reasonably assume that if it is not a foreground job, then it exists in the job table * We can reasonably assume that if it is not a foreground job, then it exists in the job table
* If this assumption is incorrect, the code has gone wrong somewhere. * If this assumption is incorrect, the code has gone wrong somewhere.
*/ */
let ( if let Some((
pgid, pgid,
is_fg, is_fg,
is_finished is_finished
) = write_jobs(|j| { )) = write_jobs(|j| {
let fg_pgid = j.get_fg().map(|job| job.pgid()); let fg_pgid = j.get_fg().map(|job| job.pgid());
if let Some(job) = j.query_mut(JobID::Pid(pid)) { if let Some(job) = j.query_mut(JobID::Pid(pid)) {
let pgid = job.pgid(); let pgid = job.pgid();
@@ -141,21 +136,22 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
child.set_stat(status); child.set_stat(status);
} }
Ok((pgid, is_fg, is_finished)) Some((pgid, is_fg, is_finished))
} else { } else {
Err(ShErr::simple(ShErrKind::InternalErr, "Job not found")) None
} }
})?; }) {
if is_finished { if is_finished {
if is_fg { if is_fg {
take_term()?; take_term()?;
} else { } else {
println!(); println!();
let job_order = read_jobs(|j| j.order().to_vec()); let job_order = read_jobs(|j| j.order().to_vec());
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned()); let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
if let Some(job) = result { if let Some(job) = result {
println!("{}",job.display(&job_order,shellenv::jobs::JobCmdFlags::PIDS)) println!("{}",job.display(&job_order,shellenv::jobs::JobCmdFlags::PIDS))
}
} }
} }
} }

7
src/tests.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod lex_test {
}
pub mod parse_test {
}