Implemented case statements

This commit is contained in:
2025-03-06 15:32:28 -05:00
parent d2554777f5
commit 3034f6c8d2
17 changed files with 1528 additions and 1138 deletions

View File

@@ -1,4 +1,4 @@
use crate::{parse::parse::{Node, NdRule}, prelude::*};
use crate::prelude::*;
pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();

View File

@@ -1,6 +1,6 @@
use shellenv::jobs::{ChildProc, JobBldr};
use crate::{libsh::utils::ArgVec, parse::parse::{Node, NdRule}, prelude::*};
use crate::prelude::*;
pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();

View File

@@ -1,6 +1,6 @@
use shellenv::jobs::{ChildProc, JobBldr};
use crate::{parse::parse::{Node, NdRule}, prelude::*};
use crate::prelude::*;
pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();

View File

@@ -1,27 +0,0 @@
use crate::prelude::*;
pub fn exec_if(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::IfThen { cond_blocks, else_block, redirs } = rule {
shenv.collect_redirs(redirs);
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
}
let mut cond_blocks = cond_blocks.into_iter();
while let Some(block) = cond_blocks.next() {
let cond = block.0;
let body = block.1;
let ret = shenv.exec_as_cond(cond)?;
if ret == 0 {
shenv.exec_as_body(body)?;
return Ok(())
}
}
if let Some(block) = else_block {
shenv.exec_as_body(block)?;
}
} else { unreachable!() }
Ok(())
}

View File

@@ -1,48 +0,0 @@
use crate::{parse::parse::LoopKind, prelude::*};
pub fn exec_loop(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Loop { kind, cond, body, redirs } = rule {
shenv.collect_redirs(redirs);
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
}
loop {
let ret = shenv.exec_as_cond(cond.clone())?;
match kind {
LoopKind::While => {
if ret == 0 {
match shenv.exec_as_body(body.clone()) {
Ok(_) => continue,
Err(e) => {
match e.kind() {
ShErrKind::LoopContinue => continue,
ShErrKind::LoopBreak => break,
_ => return Err(e.into())
}
}
}
} else { break }
}
LoopKind::Until => {
if ret != 0 {
match shenv.exec_as_body(body.clone()) {
Ok(_) => continue,
Err(e) => {
match e.kind() {
ShErrKind::LoopContinue => continue,
ShErrKind::LoopBreak => break,
_ => return Err(e.into())
}
}
}
} else { break }
}
}
}
} else { unreachable!() }
Ok(())
}

View File

@@ -1,11 +1,10 @@
use std::os::fd::AsRawFd;
use crate::prelude::*;
use shellenv::jobs::{ChildProc, JobBldr};
use crate::{builtin::export::export, libsh::{error::Blame, sys::{execvpe, get_bin_path}, utils::{ArgVec, StrOps}}, parse::{lex::Token, parse::{CmdGuard, NdFlag, Node, NdRule, SynTree}}, prelude::*};
pub mod shellcmd;
pub mod ifthen;
pub mod loops;
pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()> {
let input = input.into();
@@ -43,6 +42,7 @@ impl<'a> Executor<'a> {
}
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() {
if let NdRule::CmdList { cmds } = node.clone().into_rule() {
@@ -51,6 +51,8 @@ impl<'a> Executor<'a> {
} else { unreachable!() }
}
self.shenv.ctx_mut().ascend();
self.shenv.inputman_mut().load_state();
log!(DEBUG, "passed");
Ok(())
}
}
@@ -79,8 +81,10 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
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 {..} => ifthen::exec_if(cmd, shenv).try_blame(cmd_raw, span)?,
NdRule::Loop {..} => loops::exec_loop(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)?,

149
src/execute/shellcmd.rs Normal file
View File

@@ -0,0 +1,149 @@
use crate::prelude::*;
pub fn exec_if(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::IfThen { cond_blocks, else_block, redirs } = rule {
shenv.collect_redirs(redirs);
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
}
let mut cond_blocks = cond_blocks.into_iter();
while let Some(block) = cond_blocks.next() {
let cond = block.0;
let body = block.1;
let ret = shenv.exec_as_cond(cond)?;
if ret == 0 {
shenv.exec_as_body(body)?;
return Ok(())
}
}
if let Some(block) = else_block {
shenv.exec_as_body(block)?;
}
} else { unreachable!() }
Ok(())
}
pub fn exec_loop(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Loop { kind, cond, body, redirs } = rule {
shenv.collect_redirs(redirs);
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
}
loop {
let ret = shenv.exec_as_cond(cond.clone())?;
match kind {
LoopKind::While => {
if ret == 0 {
match shenv.exec_as_body(body.clone()) {
Ok(_) => continue,
Err(e) => {
match e.kind() {
ShErrKind::LoopContinue => continue,
ShErrKind::LoopBreak => break,
_ => return Err(e.into())
}
}
}
} else { break }
}
LoopKind::Until => {
if ret != 0 {
match shenv.exec_as_body(body.clone()) {
Ok(_) => continue,
Err(e) => {
match e.kind() {
ShErrKind::LoopContinue => continue,
ShErrKind::LoopBreak => break,
_ => return Err(e.into())
}
}
}
} else { break }
}
}
}
} else { unreachable!() }
Ok(())
}
pub fn exec_for(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::ForLoop { vars, arr, body, redirs } = rule {
shenv.collect_redirs(redirs);
let saved_vars = shenv.vars().clone();
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
}
log!(DEBUG, vars);
log!(DEBUG, arr);
for chunk in arr.chunks(vars.len()) {
log!(DEBUG, "input: {}", shenv.get_input());
for (var,value) in vars.iter().zip(chunk.iter()) {
let var = var.as_raw(shenv);
let val = value.as_raw(shenv);
log!(DEBUG,var);
log!(DEBUG,val);
shenv.vars_mut().set_var(&var, &val);
}
if chunk.len() < vars.len() {
for var in &vars[chunk.len()..] { // If 'vars' is longer than the chunk, then unset the orphaned vars
let var = var.as_raw(shenv);
log!(DEBUG, "unsetting");
log!(DEBUG, var);
shenv.vars_mut().unset_var(&var);
}
}
shenv.exec_as_body(body.clone())?;
}
*shenv.vars_mut() = saved_vars;
} else { unreachable!() }
Ok(())
}
pub fn exec_case(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Case { pat, blocks, redirs } = rule {
shenv.collect_redirs(redirs);
let mut blocks_iter = blocks.into_iter();
let pat_raw = expand_token(pat, shenv)
.iter()
.map(|tk| tk.as_raw(shenv))
.collect::<Vec<_>>()
.join(" ");
while let Some((block_pat, block)) = blocks_iter.next() {
let block_pat_raw = block_pat.as_raw(shenv);
let block_pat_raw = block_pat_raw.trim_end_matches(')');
if block_pat_raw == "*" {
let _ret = shenv.exec_as_body(block)?;
return Ok(())
} else if block_pat_raw.contains('|') {
let pats = block_pat_raw.split('|');
for pat in pats {
if pat_raw.trim() == pat.trim() {
let _ret = shenv.exec_as_body(block)?;
return Ok(())
}
}
} else if pat_raw.trim() == block_pat_raw.trim() {
let _ret = shenv.exec_as_body(block)?;
return Ok(())
}
}
} else { unreachable!() }
Ok(())
}

View File

@@ -13,25 +13,32 @@ pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
for arg in argv {
log!(DEBUG, "{}",arg.as_raw(shenv));
log!(DEBUG, processed);
match arg.rule() {
TkRule::DQuote => {
let dquote_exp = expand_dquote(arg.clone(), shenv);
processed.push(dquote_exp);
}
TkRule::VarSub => {
let mut varsub_exp = expand_var(arg.clone(), shenv);
processed.append(&mut varsub_exp);
}
TkRule::TildeSub => {
let tilde_exp = expand_tilde(arg.clone(), shenv);
processed.push(tilde_exp);
}
_ => {
if arg.rule() != TkRule::Ident {
log!(WARN, "found this in expand_argv: {:?}", arg.rule());
}
processed.push(arg.clone())
let mut expanded = expand_token(arg, shenv);
processed.append(&mut expanded);
}
processed
}
pub fn expand_token(token: Token, shenv: &mut ShEnv) -> Vec<Token> {
let mut processed = vec![];
match token.rule() {
TkRule::DQuote => {
let dquote_exp = expand_dquote(token.clone(), shenv);
processed.push(dquote_exp);
}
TkRule::VarSub => {
let mut varsub_exp = expand_var(token.clone(), shenv);
processed.append(&mut varsub_exp);
}
TkRule::TildeSub => {
let tilde_exp = expand_tilde(token.clone(), shenv);
processed.push(tilde_exp);
}
_ => {
if token.rule() != TkRule::Ident {
log!(WARN, "found this in expand_token: {:?}", token.rule());
}
processed.push(token.clone())
}
}
processed

View File

@@ -219,7 +219,7 @@ impl ShErr {
}
}
pub fn get_line(&self) -> (usize,usize,String) {
if let ShErr::Full { kind, message, blame } = self {
if let ShErr::Full { kind: _, message: _, blame } = self {
unsafe {
let mut dist = 0;
let mut line_no = 0;
@@ -256,8 +256,7 @@ impl Display for ShErr {
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
ShErr::Full { kind: _, message, blame } => {
let (offset,line_no,line_text) = self.get_line();
log!(DEBUG, blame);
let dist = blame.len();
let dist = blame.end().saturating_sub(blame.start());
let padding = " ".repeat(offset);
let line_inner = "~".repeat(dist.saturating_sub(2));
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);

View File

@@ -19,13 +19,14 @@ pub const KEYWORDS: [TkRule;14] = [
TkRule::Esac
];
pub const SEPARATORS: [TkRule; 6] = [
pub const SEPARATORS: [TkRule; 7] = [
TkRule::Sep,
TkRule::AndOp,
TkRule::OrOp,
TkRule::PipeOp,
TkRule::ErrPipeOp,
TkRule::BgOp,
TkRule::CasePat
];
pub trait LexRule {
@@ -105,15 +106,16 @@ impl Debug for Token {
}
}
#[derive(Debug,Clone)]
#[derive(PartialEq,Debug,Clone)]
pub struct Span {
start: usize,
end: usize
end: usize,
pub expanded: bool
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
Self { start, end, expanded: false }
}
pub fn start(&self) -> usize {
self.start
@@ -181,6 +183,7 @@ pub enum TkRule {
ProcSub,
VarSub,
TildeSub,
ArithSub,
Subshell,
CmdSub,
DQuote,
@@ -199,6 +202,7 @@ pub enum TkRule {
Done,
Case,
Esac,
CasePat,
Assign,
Ident,
Sep,
@@ -213,6 +217,7 @@ impl TkRule {
try_match!(VarSub,input);
try_match!(ProcSub,input);
try_match!(CmdSub,input);
try_match!(ArithSub,input);
try_match!(AndOp,input);
try_match!(OrOp,input);
try_match!(PipeOp,input);
@@ -225,6 +230,7 @@ impl TkRule {
try_match!(BraceGrp,input);
try_match!(TildeSub,input);
try_match!(Subshell,input);
try_match!(CasePat,input);
try_match!(Sep,input);
try_match!(Assign,input);
try_match!(If,input);
@@ -269,6 +275,16 @@ tkrule_def!(Whitespace, |input: &str| {
let mut len = 0;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
len += 1;
if let Some(ch) = chars.next() {
if matches!(ch, ' ' | '\t' | '\n') {
len += 1;
} else {
return None
}
}
}
' ' | '\t' => len += 1,
_ => {
match len {
@@ -284,6 +300,71 @@ tkrule_def!(Whitespace, |input: &str| {
}
});
tkrule_def!(CasePat, |input:&str| {
let mut chars = input.chars();
let mut len = 0;
let mut is_casepat = false;
while let Some(ch) = chars.next() {
len += 1;
match ch {
'\\' => {
if chars.next().is_some() {
len += 1;
}
}
')' => {
while let Some(ch) = chars.next() {
if ch == ')' {
len += 1;
} else {
break
}
}
is_casepat = true;
break
}
_ if ch.is_whitespace() => return None,
_ => { /* Continue */ }
}
}
if is_casepat { Some(len) } else { None }
});
tkrule_def!(ArithSub, |input: &str| {
let mut chars = input.chars();
let mut len = 0;
let mut is_arith_sub = false;
while let Some(ch) = chars.next() {
len += 1;
match ch {
'\\' => {
if chars.next().is_some() {
len += 1;
}
}
'`' => {
while let Some(ch) = chars.next() {
len += 1;
match ch {
'\\' => {
if chars.next().is_some() {
len += 1;
}
}
'`' => {
is_arith_sub = true;
break
}
_ => { /* Continue */ }
}
}
}
_ => { /* Continue */ }
}
}
if is_arith_sub { Some(len) } else { None }
});
tkrule_def!(TildeSub, |input: &str| {
let mut chars = input.chars();
let mut len = 0;
@@ -567,8 +648,14 @@ tkrule_def!(Sep, |input: &str| {
while let Some(ch) = chars.next() {
match ch {
'\\' => {
chars.next();
len += 2;
return None
}
' ' | '\t' => {
if len == 0 {
return None
} else {
len += 1;
}
}
';' | '\n' => len += 1,
_ => {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -97,6 +97,7 @@ pub use crate::{
},
sys::{
self,
get_bin_path,
sh_quit,
read_to_string,
write_err,
@@ -106,6 +107,7 @@ pub use crate::{
},
error::{
ResultExt,
Blame,
ShErrKind,
ShErr,
ShResult
@@ -118,6 +120,7 @@ pub use crate::{
read::read_builtin,
alias::alias,
control_flow::sh_flow,
export::export,
jobctl::{
continue_job,
jobs
@@ -126,6 +129,7 @@ pub use crate::{
},
expand::{
expand_argv,
expand_token,
alias::expand_aliases
},
shellenv::{
@@ -149,13 +153,14 @@ pub use crate::{
Executor,
},
parse::{
parse::{
SynTree,
Node,
NdRule,
Parser,
ParseRule
},
SynTree,
LoopKind,
Node,
CmdGuard,
NdFlag,
NdRule,
Parser,
ParseRule,
lex::{
Span,
Token,

View File

@@ -7,13 +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_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.ends_with("case") {
in_case = true;
}
if case_check.ends_with("esac") {
in_case = false;
}
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('(') => return false,
')' if in_quote.is_none() && delim_stack.pop() != Some('(') => {
if !in_case {
return false
}
}
']' if in_quote.is_none() && delim_stack.pop() != Some('[') => return false,
'"' | '\'' => {
if in_quote == Some(ch) {

View File

@@ -1,15 +1,16 @@
use crate::prelude::*;
#[derive(Clone,Debug)]
#[derive(Clone,Debug,PartialEq)]
pub struct InputMan {
input: Option<String>,
saved_input: Option<String>,
spans: Vec<Rc<RefCell<Span>>>,
saved_spans: Vec<Span>
}
impl InputMan {
pub fn new() -> Self {
Self { input: None, spans: vec![] }
Self { input: None, saved_input: None, spans: vec![], saved_spans: vec![] }
}
pub fn clear(&mut self) {
*self = Self::new();
@@ -23,6 +24,24 @@ impl InputMan {
pub fn get_input_mut(&mut self) -> Option<&mut String> {
self.input.as_mut()
}
pub fn save_state(&mut self) {
self.saved_input = self.input.clone();
self.saved_spans.clear();
for span in &self.spans {
self.saved_spans.push(span.borrow().clone());
}
}
pub fn load_state(&mut self) {
if self.saved_input.is_some() {
self.input = self.saved_input.take();
for (span, saved_span) in self.spans.iter_mut().zip(self.saved_spans.iter()) {
*span.borrow_mut() = saved_span.clone();
}
self.saved_spans.clear();
}
}
pub fn new_span(&mut self, start: usize, end: usize) -> Rc<RefCell<Span>> {
if let Some(_input) = &self.input {
let span = Rc::new(RefCell::new(Span::new(start, end)));

View File

@@ -32,6 +32,11 @@ impl ShEnv {
&self.input_man.get_slice(span).unwrap_or_default()
}
pub fn expand_input(&mut self, new: &str, repl_span: Rc<RefCell<Span>>) -> Vec<Token> {
log!(DEBUG,repl_span);
if repl_span.borrow().expanded {
return vec![];
}
repl_span.borrow_mut().expanded = true;
let saved_spans = self.input_man.spans_mut().clone();
let mut new_tokens = Lexer::new(new.to_string(), self).lex();
*self.input_man.spans_mut() = saved_spans;
@@ -45,9 +50,10 @@ impl ShEnv {
let repl_end = repl_span.borrow().end();
let range = repl_start..repl_end;
if let Some(ref mut input) = self.input_man.get_input_mut() {
if let Some(input) = self.input_man.get_input_mut() {
let old = &input[range.clone()];
let delta: isize = new.len() as isize - old.len() as isize;
log!(DEBUG, input);
input.replace_range(range, new);
let expanded = input.clone();
log!(DEBUG, expanded);
@@ -74,6 +80,11 @@ impl ShEnv {
new_tokens
}
}
/// Executes a group of command lists, and only uses redirections that operate on input
/// For instance:
/// `if cat; then echo foo; fi < file.txt > otherfile.txt`
/// `cat` will be executed as a condition, meaning the input from file.txt will be the only
/// redirection used.
pub fn exec_as_cond(&mut self, nodes: Vec<Node>) -> ShResult<i32> {
let saved = self.ctx().clone();
self.ctx = self.ctx().as_cond();
@@ -82,6 +93,11 @@ impl ShEnv {
self.ctx = saved;
Ok(self.get_code())
}
/// Executes a group of command lists, and only uses redirections that operate on output
/// For instance:
/// `if cat; then echo foo; fi < file.txt > otherfile.txt`
/// `echo foo` will be executed as a body, meaning the output to otherfile.txt will be the only
/// redirection used.
pub fn exec_as_body(&mut self, nodes: Vec<Node>) -> ShResult<i32> {
let saved = self.ctx().clone();
self.ctx = self.ctx().as_body();
@@ -96,7 +112,7 @@ impl ShEnv {
}
pub fn get_input(&self) -> String {
let input = self.input_man.get_input().map(|s| s.to_string()).unwrap_or_default();
log!(DEBUG, input);
log!(TRACE, input);
input
}
pub fn inputman(&self) -> &shellenv::input::InputMan {

View File

@@ -155,6 +155,9 @@ impl VarTab {
pub fn set_var(&mut self, var: &str, val: &str) {
self.vars.insert(var.to_string(), val.to_string());
}
pub fn unset_var(&mut self, var: &str) {
self.vars.remove(var);
}
pub fn export(&mut self, var: &str, val: &str) {
self.env.insert(var.to_string(),val.to_string());
std::env::set_var(var, val);